feat: refresh shared skills catalog

This commit is contained in:
vasily committed 2026-10-30 12:40:16 +08:00
1 parent 98bb35e6fb
commit 418792d1ec
49 files changed
+2273 -2390

No files matched your search

+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env python3
"""Synchronize skill symlinks without taking ownership of unrelated entries."""
import argparse
from pathlib import Path
import os
import re
import sys
def lexical(path):
return Path(os.path.abspath(path))
def link_target(path):
return lexical(path.parent / os.readlink(path))
def inside(path, root):
return path != root and root in path.parents
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--dry-run', action='store_true', help='show changes without writing')
parser.add_argument('--check', action='store_true', help='exit 1 if links differ; do not write')
parser.add_argument('--global-dir', type=Path, default=Path(__file__).resolve().parents[1] / 'skills')
parser.add_argument('--jev-dir', type=Path, default=Path.home() / 'Projects/work/JEV/.agents/skills')
parser.add_argument('--target', action='append', type=Path, help='override target directories; repeatable')
args = parser.parse_args(argv)
sources = [lexical(args.global_dir), lexical(args.jev_dir)]
targets = args.target if args.target is not None else [Path.home() / p for p in ('.agents/skills', '.codex/skills', '.claude/skills')]
targets = [lexical(p) for p in targets]
# Reject destinations inside catalogs (or containing them) before any mutation.
for target in targets:
resolved_target = target.resolve()
if any(resolved_target == source.resolve() or
inside(resolved_target, source.resolve()) or
inside(source.resolve(), resolved_target) for source in sources):
parser.error(f'target overlaps a source catalog: {target}')
if not sources[0].is_dir():
parser.error(f'global skill source unavailable: {sources[0]}')
available = [p for p in sources if p.is_dir()]
expected = {}
errors = []
for root in available:
for child in sorted(root.iterdir()):
if not child.is_dir():
continue
if not re.fullmatch(r'[a-z0-9]+(?:-[a-z0-9]+)*', child.name) or not (child / 'SKILL.md').is_file():
errors.append(f'invalid skill directory: {child}')
continue
if child.name in expected:
errors.append(f'colliding skill name: {child.name} ({expected[child.name]}, {child})')
else:
expected[child.name] = child
if errors:
print('\n'.join(errors), file=sys.stderr)
return 2
if len(available) != len(sources):
print(f'WARN: JEV source unavailable; preserving its existing links: {sources[1]}', file=sys.stderr)
changes = 0
conflicts = 0
readonly = args.check or args.dry_run
for target in targets:
if target.exists() and not target.is_dir():
print(f'SKIP: target is not a directory: {target}', file=sys.stderr)
conflicts += 1
continue
if not readonly:
target.mkdir(parents=True, exist_ok=True)
# Only prune links to sources that were successfully inventoried.
for dest in sorted(target.iterdir()) if target.is_dir() else []:
if dest.is_symlink() and any(inside(link_target(dest), root) for root in available) and dest.name not in expected:
print(f'remove stale: {dest}')
changes += 1
if not readonly:
dest.unlink()
for name, src in sorted(expected.items()):
dest = target / name
if dest.is_symlink():
current = link_target(dest)
if current == src:
continue
if not any(inside(current, root) for root in available):
print(f'SKIP: unrelated symlink: {dest}', file=sys.stderr)
conflicts += 1
continue
elif dest.exists():
print(f'SKIP: unrelated entry: {dest}', file=sys.stderr)
conflicts += 1
continue
print(f'link: {dest} -> {src}')
changes += 1
if not readonly:
if dest.is_symlink():
dest.unlink()
dest.symlink_to(src, target_is_directory=True)
print(f'{"check" if args.check else "dry-run" if args.dry_run else "sync"}: {changes} changes, {conflicts} conflicts')
return 1 if conflicts or (args.check and changes) else 0
if __name__ == '__main__':
sys.exit(main())
+15 -22
View File
@@ -1,32 +1,25 @@
#!/usr/bin/env bash
# Print a ready-to-paste scp command for pulling a file from this host
# to the user's Windows machine over Tailscale SSH.
# Print a command for the user's Windows machine to pull a path over Tailscale SSH.
set -euo pipefail
HOST="vasily@msi.tail0b014a.ts.net"
DELIVERY_HOST="vasily@msi.tail0b014a.ts.net"
if [[ $# -ne 1 ]]; then
echo "usage: t3-deliver <file-or-directory>" >&2
exit 1
fi
path="$1"
if [[ ! -e "$path" ]]; then
echo "t3-deliver: no such path: $path" >&2
if [[ ! -e "$1" ]]; then
echo "t3-deliver: no such path: $1" >&2
exit 1
fi
if [[ "$path" != /* ]]; then
path="$(realpath "$path")"
else
path="$(realpath -m "$path")"
DELIVERY_PATH="$(realpath -- "$1")"
# Reject characters that cannot be shared safely between cmd and PowerShell.
# OpenSSH SFTP mode handles the quoted remote path without a remote shell.
if [[ "$DELIVERY_PATH" == *[\"\$\`\%\!]* || "$DELIVERY_PATH" == *$'\n'* || "$DELIVERY_PATH" == *$'\r'* ]]; then
echo 't3-deliver: rename or archive this path with a simple name before delivery' >&2
exit 1
fi
flags=""
if [[ -d "$path" ]]; then
flags="-r "
DELIVERY_FLAGS=""
if [[ -d "$DELIVERY_PATH" ]]; then
DELIVERY_FLAGS="-r "
fi
echo "Paste in cmd or PowerShell on your Windows machine:"
echo
echo "${flags}scp ${HOST}:\"${path}\" ."
printf 'Paste in cmd or PowerShell on your Windows machine (OpenSSH scp with SFTP support):\n\n'
printf 'scp %s"%s:%s" .\n' "$DELIVERY_FLAGS" "$DELIVERY_HOST" "$DELIVERY_PATH"
+125
View File
@@ -0,0 +1,125 @@
#!/usr/bin/env python3
"""Check catalog metadata, local links, and duplicate names without mutating files."""
import argparse
import json
from pathlib import Path
import re
import sys
def metadata(text):
"""Read the catalog's scalar and block-string YAML frontmatter subset."""
match = re.match(r'\A---\r?\n(.*?)\r?\n---(?:\r?\n|$)', text, re.S)
if not match:
raise ValueError('missing frontmatter')
result = {}
field = None
for line in match[1].splitlines():
if not line.strip() or line.lstrip().startswith('#'):
continue
if line[0].isspace():
if field is None:
raise ValueError('unexpected indentation in frontmatter')
result[field] += ' ' + line.strip()
continue
item = re.fullmatch(r'([a-z][a-z0-9_-]*):(?:\s+(.*))?', line)
if not item:
raise ValueError('invalid frontmatter field')
key, value = item.groups()
if key in result:
raise ValueError(f'duplicate field: {key}')
value = (value or '').strip()
field = key if value in ('>', '|', '>-', '|-') else None
if field:
result[key] = ''
else:
if value.startswith('"'):
try:
result[key] = json.loads(value)
except json.JSONDecodeError as exc:
raise ValueError(f'invalid quoted field: {key}') from exc
elif value.startswith("'"):
if len(value) < 2 or not value.endswith("'"):
raise ValueError(f'invalid quoted field: {key}')
result[key] = value[1:-1].replace("''", "'")
else:
if re.search(r':\s', value):
raise ValueError(f'invalid plain scalar: {key}; use a block string')
result[key] = value.split(' #', 1)[0]
return {k: v.strip() for k, v in result.items()}
def local_links(path):
fenced = False
for number, line in enumerate(path.read_text().splitlines(), 1):
if line.lstrip().startswith(('```', '~~~')):
fenced = not fenced
continue
if fenced:
continue
for raw in re.findall(r'\]\(([^)]+)\)', line):
target = raw.strip().split('#', 1)[0]
if not target or re.match(r'[a-zA-Z][a-zA-Z0-9+.-]*:', target):
continue
target = target.strip('<>')
resolved = path.parent / target
if not resolved.exists():
yield f'{path}:{number}: missing link target {target}'
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--global-dir', type=Path, default=Path(__file__).resolve().parents[1] / 'skills')
parser.add_argument('--jev-dir', type=Path, default=Path.home() / 'Projects/work/JEV/.agents/skills')
args = parser.parse_args(argv)
errors = []
names = {}
count = 0
if not args.global_dir.is_dir():
parser.error(f'global catalog unavailable: {args.global_dir}')
roots = [args.global_dir]
if args.jev_dir.is_dir():
roots.append(args.jev_dir)
else:
print(f'WARN: JEV catalog unavailable: {args.jev_dir}', file=sys.stderr)
for root in roots:
for directory in sorted(root.iterdir()):
if not directory.is_dir():
continue
path = directory / 'SKILL.md'
if not path.is_file():
errors.append(f'{directory}: missing SKILL.md')
continue
count += 1
try:
fields = metadata(path.read_text())
name = fields.get('name', '')
if name != directory.name or not re.fullmatch(r'[a-z0-9]+(?:-[a-z0-9]+)*', name):
errors.append(f'{path}: invalid or mismatched name')
if not fields.get('description'):
errors.append(f'{path}: empty description')
if fields.get('scope') not in ('global', 'project', 'alias'):
errors.append(f'{path}: scope must be global, project, or alias')
if name in names:
errors.append(f'{path}: name collides with {names[name]}')
names[name] = path
except ValueError as exc:
errors.append(f'{path}: {exc}')
for path in sorted(root.rglob('*.md')):
errors.extend(local_links(path))
# Global protocols are part of this catalog; JEV protocols belong to its source.
for folder in [args.global_dir.parent / 'protocols', args.jev_dir.parent / 'protocols']:
if folder.is_dir():
for path in sorted(folder.glob('*.md')):
errors.extend(local_links(path))
readme = args.global_dir.parent / 'README.md'
if readme.is_file():
errors.extend(local_links(readme))
for error in errors:
print(error, file=sys.stderr)
print(f'catalog: {count} skills, {len(errors)} errors')
return bool(errors)
if __name__ == '__main__':
sys.exit(main())