xtrm-tools 2.4.0 → 2.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -9
- package/cli/dist/index.cjs +774 -240
- package/cli/dist/index.cjs.map +1 -1
- package/cli/package.json +1 -1
- package/config/hooks.json +10 -0
- package/config/pi/extensions/core/adapter.ts +2 -14
- package/config/pi/extensions/core/guard-rules.ts +70 -0
- package/config/pi/extensions/core/session-state.ts +59 -0
- package/config/pi/extensions/main-guard.ts +10 -14
- package/config/pi/extensions/plan-mode/README.md +65 -0
- package/config/pi/extensions/plan-mode/index.ts +340 -0
- package/config/pi/extensions/plan-mode/utils.ts +168 -0
- package/config/pi/extensions/service-skills.ts +51 -7
- package/config/pi/extensions/session-flow.ts +117 -0
- package/hooks/beads-claim-sync.mjs +123 -2
- package/hooks/beads-compact-restore.mjs +41 -9
- package/hooks/beads-compact-save.mjs +36 -5
- package/hooks/beads-gate-messages.mjs +27 -1
- package/hooks/beads-stop-gate.mjs +58 -8
- package/hooks/guard-rules.mjs +86 -0
- package/hooks/hooks.json +28 -18
- package/hooks/main-guard.mjs +3 -21
- package/hooks/quality-check.cjs +1286 -0
- package/hooks/quality-check.py +345 -0
- package/hooks/session-state.mjs +138 -0
- package/package.json +2 -1
- package/project-skills/quality-gates/.claude/settings.json +1 -24
- package/skills/creating-service-skills/SKILL.md +433 -0
- package/skills/creating-service-skills/references/script_quality_standards.md +425 -0
- package/skills/creating-service-skills/references/service_skill_system_guide.md +278 -0
- package/skills/creating-service-skills/scripts/bootstrap.py +326 -0
- package/skills/creating-service-skills/scripts/deep_dive.py +304 -0
- package/skills/creating-service-skills/scripts/scaffolder.py +482 -0
- package/skills/scoping-service-skills/SKILL.md +231 -0
- package/skills/scoping-service-skills/scripts/scope.py +74 -0
- package/skills/sync-docs/SKILL.md +235 -0
- package/skills/sync-docs/evals/evals.json +89 -0
- package/skills/sync-docs/references/doc-structure.md +104 -0
- package/skills/sync-docs/references/schema.md +103 -0
- package/skills/sync-docs/scripts/context_gatherer.py +246 -0
- package/skills/sync-docs/scripts/doc_structure_analyzer.py +495 -0
- package/skills/sync-docs/scripts/validate_doc.py +365 -0
- package/skills/sync-docs-workspace/iteration-1/benchmark.json +293 -0
- package/skills/sync-docs-workspace/iteration-1/benchmark.md +13 -0
- package/skills/sync-docs-workspace/iteration-1/eval-doc-audit/eval_metadata.json +27 -0
- package/skills/sync-docs-workspace/iteration-1/eval-doc-audit/with_skill/outputs/result.md +210 -0
- package/skills/sync-docs-workspace/iteration-1/eval-doc-audit/with_skill/run-1/grading.json +28 -0
- package/skills/sync-docs-workspace/iteration-1/eval-doc-audit/with_skill/run-1/timing.json +1 -0
- package/skills/sync-docs-workspace/iteration-1/eval-doc-audit/without_skill/outputs/result.md +101 -0
- package/skills/sync-docs-workspace/iteration-1/eval-doc-audit/without_skill/run-1/grading.json +28 -0
- package/skills/sync-docs-workspace/iteration-1/eval-doc-audit/without_skill/run-1/timing.json +5 -0
- package/skills/sync-docs-workspace/iteration-1/eval-doc-audit/without_skill/timing.json +5 -0
- package/skills/sync-docs-workspace/iteration-1/eval-fix-mode/eval_metadata.json +27 -0
- package/skills/sync-docs-workspace/iteration-1/eval-fix-mode/with_skill/outputs/result.md +198 -0
- package/skills/sync-docs-workspace/iteration-1/eval-fix-mode/with_skill/run-1/grading.json +28 -0
- package/skills/sync-docs-workspace/iteration-1/eval-fix-mode/with_skill/run-1/timing.json +1 -0
- package/skills/sync-docs-workspace/iteration-1/eval-fix-mode/without_skill/outputs/result.md +94 -0
- package/skills/sync-docs-workspace/iteration-1/eval-fix-mode/without_skill/run-1/grading.json +28 -0
- package/skills/sync-docs-workspace/iteration-1/eval-fix-mode/without_skill/run-1/timing.json +1 -0
- package/skills/sync-docs-workspace/iteration-1/eval-sprint-closeout/eval_metadata.json +27 -0
- package/skills/sync-docs-workspace/iteration-1/eval-sprint-closeout/with_skill/outputs/result.md +237 -0
- package/skills/sync-docs-workspace/iteration-1/eval-sprint-closeout/with_skill/run-1/grading.json +28 -0
- package/skills/sync-docs-workspace/iteration-1/eval-sprint-closeout/with_skill/run-1/timing.json +1 -0
- package/skills/sync-docs-workspace/iteration-1/eval-sprint-closeout/without_skill/outputs/result.md +134 -0
- package/skills/sync-docs-workspace/iteration-1/eval-sprint-closeout/without_skill/run-1/grading.json +28 -0
- package/skills/sync-docs-workspace/iteration-1/eval-sprint-closeout/without_skill/run-1/timing.json +1 -0
- package/skills/sync-docs-workspace/iteration-2/benchmark.json +297 -0
- package/skills/sync-docs-workspace/iteration-2/benchmark.md +13 -0
- package/skills/sync-docs-workspace/iteration-2/eval-doc-audit/eval_metadata.json +27 -0
- package/skills/sync-docs-workspace/iteration-2/eval-doc-audit/with_skill/outputs/result.md +137 -0
- package/skills/sync-docs-workspace/iteration-2/eval-doc-audit/with_skill/run-1/grading.json +92 -0
- package/skills/sync-docs-workspace/iteration-2/eval-doc-audit/with_skill/run-1/timing.json +1 -0
- package/skills/sync-docs-workspace/iteration-2/eval-doc-audit/without_skill/outputs/result.md +134 -0
- package/skills/sync-docs-workspace/iteration-2/eval-doc-audit/without_skill/run-1/grading.json +86 -0
- package/skills/sync-docs-workspace/iteration-2/eval-doc-audit/without_skill/run-1/timing.json +1 -0
- package/skills/sync-docs-workspace/iteration-2/eval-fix-mode/eval_metadata.json +27 -0
- package/skills/sync-docs-workspace/iteration-2/eval-fix-mode/with_skill/outputs/result.md +193 -0
- package/skills/sync-docs-workspace/iteration-2/eval-fix-mode/with_skill/run-1/grading.json +72 -0
- package/skills/sync-docs-workspace/iteration-2/eval-fix-mode/with_skill/run-1/timing.json +1 -0
- package/skills/sync-docs-workspace/iteration-2/eval-fix-mode/without_skill/outputs/result.md +211 -0
- package/skills/sync-docs-workspace/iteration-2/eval-fix-mode/without_skill/run-1/grading.json +91 -0
- package/skills/sync-docs-workspace/iteration-2/eval-fix-mode/without_skill/run-1/timing.json +5 -0
- package/skills/sync-docs-workspace/iteration-2/eval-sprint-closeout/eval_metadata.json +27 -0
- package/skills/sync-docs-workspace/iteration-2/eval-sprint-closeout/with_skill/outputs/result.md +182 -0
- package/skills/sync-docs-workspace/iteration-2/eval-sprint-closeout/with_skill/run-1/grading.json +95 -0
- package/skills/sync-docs-workspace/iteration-2/eval-sprint-closeout/with_skill/run-1/timing.json +1 -0
- package/skills/sync-docs-workspace/iteration-2/eval-sprint-closeout/without_skill/outputs/result.md +222 -0
- package/skills/sync-docs-workspace/iteration-2/eval-sprint-closeout/without_skill/run-1/grading.json +88 -0
- package/skills/sync-docs-workspace/iteration-2/eval-sprint-closeout/without_skill/run-1/timing.json +5 -0
- package/skills/sync-docs-workspace/iteration-3/benchmark.json +298 -0
- package/skills/sync-docs-workspace/iteration-3/benchmark.md +13 -0
- package/skills/sync-docs-workspace/iteration-3/eval-doc-audit/eval_metadata.json +27 -0
- package/skills/sync-docs-workspace/iteration-3/eval-doc-audit/with_skill/outputs/result.md +125 -0
- package/skills/sync-docs-workspace/iteration-3/eval-doc-audit/with_skill/run-1/grading.json +97 -0
- package/skills/sync-docs-workspace/iteration-3/eval-doc-audit/with_skill/run-1/timing.json +5 -0
- package/skills/sync-docs-workspace/iteration-3/eval-doc-audit/without_skill/outputs/result.md +144 -0
- package/skills/sync-docs-workspace/iteration-3/eval-doc-audit/without_skill/run-1/grading.json +78 -0
- package/skills/sync-docs-workspace/iteration-3/eval-doc-audit/without_skill/run-1/timing.json +5 -0
- package/skills/sync-docs-workspace/iteration-3/eval-fix-mode/eval_metadata.json +27 -0
- package/skills/sync-docs-workspace/iteration-3/eval-fix-mode/with_skill/outputs/result.md +104 -0
- package/skills/sync-docs-workspace/iteration-3/eval-fix-mode/with_skill/run-1/grading.json +91 -0
- package/skills/sync-docs-workspace/iteration-3/eval-fix-mode/with_skill/run-1/timing.json +5 -0
- package/skills/sync-docs-workspace/iteration-3/eval-fix-mode/without_skill/outputs/result.md +79 -0
- package/skills/sync-docs-workspace/iteration-3/eval-fix-mode/without_skill/run-1/grading.json +82 -0
- package/skills/sync-docs-workspace/iteration-3/eval-fix-mode/without_skill/run-1/timing.json +5 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/eval_metadata.json +27 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/with_skill/outputs/phase1_context.json +302 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/with_skill/outputs/phase2_drift.txt +33 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/with_skill/outputs/phase3_analysis.json +114 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/with_skill/outputs/phase4_fix.txt +118 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/with_skill/outputs/phase5_validate.txt +38 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/with_skill/outputs/result.md +158 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/with_skill/run-1/grading.json +95 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/with_skill/run-1/timing.json +5 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/without_skill/outputs/result.md +71 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/without_skill/run-1/grading.json +90 -0
- package/skills/sync-docs-workspace/iteration-3/eval-sprint-closeout/without_skill/run-1/timing.json +5 -0
- package/skills/updating-service-skills/SKILL.md +136 -0
- package/skills/updating-service-skills/scripts/drift_detector.py +222 -0
- package/skills/using-quality-gates/SKILL.md +254 -0
- package/skills/using-service-skills/SKILL.md +108 -0
- package/skills/using-service-skills/scripts/cataloger.py +74 -0
- package/skills/using-service-skills/scripts/skill_activator.py +152 -0
- package/skills/using-service-skills/scripts/test_skill_activator.py +58 -0
- package/skills/using-xtrm/SKILL.md +34 -38
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Gather project context for documentation sync.
|
|
4
|
+
|
|
5
|
+
Collects:
|
|
6
|
+
- Recently closed bd issues (if .beads/ exists)
|
|
7
|
+
- Recently merged PRs (via git log)
|
|
8
|
+
- bd memories persisted this cycle (bd kv list)
|
|
9
|
+
- Stale Serena memories (via drift_detector.py)
|
|
10
|
+
|
|
11
|
+
Outputs JSON to stdout. Safe to run in any project — degrades gracefully
|
|
12
|
+
when bd or Serena are unavailable.
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
context_gatherer.py [--since=30]
|
|
16
|
+
|
|
17
|
+
--since=N Look back N commits for git context (default: 30)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import sys
|
|
21
|
+
import json
|
|
22
|
+
import subprocess
|
|
23
|
+
import time
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from datetime import datetime, timezone
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def run(cmd: list, cwd: str | None = None, timeout: int = 8) -> str | None:
|
|
29
|
+
"""Run a command, return stdout or None on failure."""
|
|
30
|
+
try:
|
|
31
|
+
result = subprocess.run(
|
|
32
|
+
cmd, cwd=cwd, capture_output=True, text=True, timeout=timeout
|
|
33
|
+
)
|
|
34
|
+
if result.returncode == 0:
|
|
35
|
+
return result.stdout.strip()
|
|
36
|
+
return None
|
|
37
|
+
except Exception:
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def find_project_root() -> Path:
|
|
42
|
+
"""Walk up from cwd looking for .git."""
|
|
43
|
+
p = Path.cwd()
|
|
44
|
+
for parent in [p, *p.parents]:
|
|
45
|
+
if (parent / ".git").exists():
|
|
46
|
+
return parent
|
|
47
|
+
return p
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def find_main_repo_root(root: Path) -> Path:
|
|
51
|
+
"""For git worktrees, resolve the main repo root from the .git file.
|
|
52
|
+
|
|
53
|
+
In a worktree, .git is a file: "gitdir: /path/to/main/.git/worktrees/<name>"
|
|
54
|
+
Navigate up two levels to reach the main .git, then one more for the repo root.
|
|
55
|
+
"""
|
|
56
|
+
git_path = root / ".git"
|
|
57
|
+
if git_path.is_file():
|
|
58
|
+
content = git_path.read_text(encoding="utf-8").strip()
|
|
59
|
+
if content.startswith("gitdir:"):
|
|
60
|
+
worktree_git = Path(content[len("gitdir:"):].strip())
|
|
61
|
+
# worktree_git = /path/to/main/.git/worktrees/<name>
|
|
62
|
+
# main .git = /path/to/main/.git (two levels up)
|
|
63
|
+
# main repo = /path/to/main (one more level)
|
|
64
|
+
main_git = worktree_git.parent.parent
|
|
65
|
+
return main_git.parent
|
|
66
|
+
return root
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def ensure_dolt_server(cwd: str) -> bool:
|
|
70
|
+
"""Ensure the Dolt server is running. Start it if not. Returns True if ready."""
|
|
71
|
+
# Quick connection test
|
|
72
|
+
test = run(["bd", "dolt", "test"], cwd=cwd, timeout=5)
|
|
73
|
+
if test is not None:
|
|
74
|
+
return True
|
|
75
|
+
|
|
76
|
+
# Server not responding — try to start it
|
|
77
|
+
try:
|
|
78
|
+
subprocess.run(
|
|
79
|
+
["bd", "dolt", "start"],
|
|
80
|
+
cwd=cwd, capture_output=True, text=True, timeout=15
|
|
81
|
+
)
|
|
82
|
+
except Exception:
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
# Wait up to 6 seconds for it to become ready
|
|
86
|
+
for _ in range(6):
|
|
87
|
+
time.sleep(1)
|
|
88
|
+
if run(["bd", "dolt", "test"], cwd=cwd, timeout=3) is not None:
|
|
89
|
+
return True
|
|
90
|
+
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def has_beads(root: Path) -> bool:
|
|
95
|
+
return (root / ".beads").exists()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def gather_bd_closed(cwd: str) -> list[dict]:
|
|
99
|
+
"""Get recently closed bd issues."""
|
|
100
|
+
out = run(["bd", "list", "--status=closed"], cwd=cwd)
|
|
101
|
+
if not out:
|
|
102
|
+
return []
|
|
103
|
+
|
|
104
|
+
issues = []
|
|
105
|
+
for line in out.splitlines():
|
|
106
|
+
line = line.strip()
|
|
107
|
+
# bd list output: "✓ <id> ● <priority> <title>"
|
|
108
|
+
if line.startswith("✓") or "closed" in line.lower():
|
|
109
|
+
parts = line.lstrip("✓ ").split()
|
|
110
|
+
if len(parts) >= 2:
|
|
111
|
+
issue_id = parts[0]
|
|
112
|
+
title_start = 2 if len(parts) > 2 and parts[1].startswith("P") else 1
|
|
113
|
+
title = " ".join(parts[title_start:])
|
|
114
|
+
issues.append({"id": issue_id, "title": title})
|
|
115
|
+
|
|
116
|
+
return issues[:20]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def gather_bd_memories(cwd: str) -> list[dict]:
|
|
120
|
+
"""Read bd memories via bd kv list, filtering memory.* keys."""
|
|
121
|
+
out = run(["bd", "kv", "list"], cwd=cwd)
|
|
122
|
+
if not out:
|
|
123
|
+
return []
|
|
124
|
+
|
|
125
|
+
memories = []
|
|
126
|
+
for line in out.splitlines():
|
|
127
|
+
stripped = line.strip()
|
|
128
|
+
if not stripped.startswith("memory."):
|
|
129
|
+
continue
|
|
130
|
+
if " = " in stripped:
|
|
131
|
+
key, _, value = stripped.partition(" = ")
|
|
132
|
+
memories.append({"key": key.strip(), "value": value.strip()})
|
|
133
|
+
else:
|
|
134
|
+
memories.append({"key": stripped, "value": ""})
|
|
135
|
+
|
|
136
|
+
return memories[:20]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def gather_merged_prs(root: Path, since_n: int) -> list[dict]:
|
|
140
|
+
"""Get merged PRs from git log."""
|
|
141
|
+
out = run(
|
|
142
|
+
["git", "log", f"-{since_n}", "--merges", "--oneline", "--format=%H|%s|%ci"],
|
|
143
|
+
cwd=str(root),
|
|
144
|
+
)
|
|
145
|
+
if not out:
|
|
146
|
+
return []
|
|
147
|
+
|
|
148
|
+
prs = []
|
|
149
|
+
for line in out.splitlines():
|
|
150
|
+
parts = line.split("|", 2)
|
|
151
|
+
if len(parts) == 3:
|
|
152
|
+
sha, subject, date = parts
|
|
153
|
+
prs.append({"sha": sha[:8], "subject": subject.strip(), "date": date.strip()})
|
|
154
|
+
return prs[:10]
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def gather_recent_commits(root: Path, since_n: int) -> list[dict]:
|
|
158
|
+
"""Get recent non-merge commits for context."""
|
|
159
|
+
out = run(
|
|
160
|
+
["git", "log", f"-{since_n}", "--no-merges", "--oneline", "--format=%H|%s|%ci"],
|
|
161
|
+
cwd=str(root),
|
|
162
|
+
)
|
|
163
|
+
if not out:
|
|
164
|
+
return []
|
|
165
|
+
|
|
166
|
+
commits = []
|
|
167
|
+
for line in out.splitlines():
|
|
168
|
+
parts = line.split("|", 2)
|
|
169
|
+
if len(parts) == 3:
|
|
170
|
+
sha, subject, date = parts
|
|
171
|
+
commits.append({"sha": sha[:8], "subject": subject.strip(), "date": date.strip()})
|
|
172
|
+
return commits[:15]
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def gather_serena_drift(root: Path) -> dict:
|
|
176
|
+
"""Run drift_detector.py and capture its output."""
|
|
177
|
+
candidates = [
|
|
178
|
+
Path.home() / ".claude/skills/documenting/scripts/drift_detector.py",
|
|
179
|
+
root / "skills/documenting/scripts/drift_detector.py",
|
|
180
|
+
]
|
|
181
|
+
detector = next((p for p in candidates if p.exists()), None)
|
|
182
|
+
if not detector:
|
|
183
|
+
return {"available": False, "stale": []}
|
|
184
|
+
|
|
185
|
+
out = run([sys.executable, str(detector), "scan"], cwd=str(root))
|
|
186
|
+
if out is None:
|
|
187
|
+
return {"available": False, "stale": []}
|
|
188
|
+
|
|
189
|
+
stale = []
|
|
190
|
+
for line in out.splitlines():
|
|
191
|
+
if line.strip().startswith(tuple("abcdefghijklmnopqrstuvwxyz_")):
|
|
192
|
+
name = line.strip()
|
|
193
|
+
if name and not name.startswith("Modified:") and not name.startswith("Last"):
|
|
194
|
+
stale.append(name)
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
"available": True,
|
|
198
|
+
"stale": stale,
|
|
199
|
+
"raw": out,
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def main() -> None:
|
|
204
|
+
since_n = 30
|
|
205
|
+
for arg in sys.argv[1:]:
|
|
206
|
+
if arg.startswith("--since="):
|
|
207
|
+
try:
|
|
208
|
+
since_n = int(arg.split("=", 1)[1])
|
|
209
|
+
except ValueError:
|
|
210
|
+
pass
|
|
211
|
+
|
|
212
|
+
root = find_project_root()
|
|
213
|
+
# In a git worktree, bd needs the main repo's .beads/ — resolve it
|
|
214
|
+
main_root = find_main_repo_root(root)
|
|
215
|
+
bd_cwd = str(main_root)
|
|
216
|
+
bd_available = has_beads(main_root)
|
|
217
|
+
|
|
218
|
+
dolt_ready = False
|
|
219
|
+
dolt_warning: str | None = None
|
|
220
|
+
if bd_available:
|
|
221
|
+
dolt_ready = ensure_dolt_server(bd_cwd)
|
|
222
|
+
if not dolt_ready:
|
|
223
|
+
dolt_warning = (
|
|
224
|
+
"Dolt server could not be started — bd data unavailable. "
|
|
225
|
+
"Run 'bd dolt start' manually from the project root and retry."
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
report = {
|
|
229
|
+
"generated_at": datetime.now(timezone.utc).isoformat(),
|
|
230
|
+
"project_root": str(root),
|
|
231
|
+
"bd_available": bd_available,
|
|
232
|
+
"bd_closed_issues": gather_bd_closed(bd_cwd) if dolt_ready else [],
|
|
233
|
+
"bd_memories": gather_bd_memories(bd_cwd) if dolt_ready else [],
|
|
234
|
+
"merged_prs": gather_merged_prs(root, since_n),
|
|
235
|
+
"recent_commits": gather_recent_commits(root, since_n),
|
|
236
|
+
"serena_drift": gather_serena_drift(root),
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if dolt_warning:
|
|
240
|
+
report["warnings"] = [dolt_warning]
|
|
241
|
+
|
|
242
|
+
print(json.dumps(report, indent=2))
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
if __name__ == "__main__":
|
|
246
|
+
main()
|