Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 43 additions & 9 deletions .agents/skills/hugo-event-intake/scripts/hugo_event_intake.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,14 +253,25 @@ def infer_kind(
"論文読み会",
"読み会",
"輪読",
"論文",
"paper",
"arxiv.org",
"transformer-circuits.pub",
"survey on",
"a survey",
"サーベイ論文",
"提案について紹介",
)
):
return "Journal Club"
if "poster" in haystack:
return "Invited Poster"
if "presentation" in haystack:
if any(
keyword in haystack
for keyword in ("invited presentation", "doctoral dissertation presentation")
):
return "Invited Presentation"
if any(keyword in haystack for keyword in ("report", "速報", "summary", "survey")):
if any(keyword in haystack for keyword in ("report", "速報", "summary", "参加報告")):
return "Report"
return "Invited Talk"

Expand Down Expand Up @@ -529,6 +540,21 @@ def derive_slug(
return "event-entry"


def link_label_for_event_url(url: str) -> str:
host = urlsplit(url).netloc.lower()
if host.endswith("connpass.com"):
return "Connpass"
if host.endswith("confit.atlas.jp"):
return "Confit"
return "Event"


def default_links_for_event_url(url: str) -> list[dict[str, str]]:
if not url:
return []
return [{"name": link_label_for_event_url(url), "url": url}]


def build_body_markdown(
spec: EventSpec, *, description: str, speakerdeck_source: SourceInfo | None
) -> str:
Expand All @@ -551,19 +577,27 @@ def build_body_markdown(
return "\n".join(part.rstrip() for part in parts if part.strip()) + "\n"

links = []
seen_urls: set[str] = set()

def add_link(name: str, url: str) -> None:
if not url or url in seen_urls:
return
links.append((name, url))
seen_urls.add(url)

if spec.event_url:
links.append(("Event", spec.event_url))
add_link(link_label_for_event_url(spec.event_url), spec.event_url)
if spec.url_slides:
links.append(("Slides", spec.url_slides))
add_link("Slides", spec.url_slides)
if spec.url_pdf:
links.append(("PDF", spec.url_pdf))
add_link("PDF", spec.url_pdf)
if spec.url_video:
links.append(("Video", spec.url_video))
add_link("Video", spec.url_video)
if spec.url_code:
links.append(("Code", spec.url_code))
add_link("Code", spec.url_code)
if spec.links:
for link in spec.links:
links.append((link["name"], link["url"]))
add_link(link["name"], link["url"])
if links:
parts.append(
"## Links\n\n"
Expand Down Expand Up @@ -752,7 +786,7 @@ def probe_event(
url_pdf=final_url_pdf,
url_video=final_url_video,
url_code=final_url_code,
links=[],
links=default_links_for_event_url(final_event_url),
body_markdown="",
featured_source_url=final_featured_source,
evidence_urls=[source.url for source in sources],
Expand Down
137 changes: 137 additions & 0 deletions .agents/skills/hugo-talks-sync/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
---
name: hugo-talks-sync
description: Discover missing Recent & Upcoming Talks for this Hugo/HugoBlox repository by comparing shunk031's external talk sources with existing content/event entries. Use when asked to update, audit, backfill, or synchronize talks from Speaker Deck profiles, generic RSS/Atom feeds, slide lists, organizer pages, or other external talk indexes before creating event pages with hugo-event-intake.
---

# Hugo Talks Sync

Find candidate talks that are present on external indexes but missing from
`content/event`, then route accepted candidates through `hugo-event-intake`.

Use this skill as the discovery layer. Use `hugo-event-intake` as the write layer.
Do not create or overwrite event pages directly from this skill.

## Workflow

1. Audit external sources.

Run the helper from the repository root:

```bash
uv run python .agents/skills/hugo-talks-sync/scripts/hugo_talks_sync.py audit --repo-root . --output /tmp/hugo-talks-sync.json
```

Defaults:

- Source adapter: `speakerdeck-profile`
- Speaker Deck profile URL: `https://speakerdeck.com/shunk031`
- Existing event root: `content/event`
- Duplicate check fields: `event_url`, `url_slides`, `url_pdf`, `url_video`, `url_code`, and Markdown body URLs

Use source options when the user names different sources:

- `--profile-url <speakerdeck-profile-url>` changes the default Speaker Deck profile.
- `--feed-url <rss-or-atom-url>` adds a generic auto-detected RSS/Atom `feed` source. Repeat it for multiple feeds.
- `--source-config <json-file>` loads explicit source definitions.

Source config shape:

```json
{
"sources": [
{
"name": "speakerdeck-shunk031",
"adapter": "speakerdeck-profile",
"profile_url": "https://speakerdeck.com/shunk031",
"options": {}
},
{
"name": "personal-talk-feed",
"adapter": "atom",
"url": "https://example.com/talks.atom",
"options": {}
}
]
}
```

First-class adapters are currently `speakerdeck-profile`, auto-detected `feed`,
explicit `rss`, and explicit `atom`. For HTML index pages, APIs, or
platform-specific sources such as connpass users or YouTube channels, add a
source adapter to the registry in
`scripts/hugo_talks_sync.py` instead of scraping ad hoc in the workflow.

2. Review `/tmp/hugo-talks-sync.json`.

For each candidate, inspect:

- `source_url`: the Speaker Deck page or external source
- `source_name`, `adapter`, and `source_type` (a compatibility alias of `adapter`)
- `title`
- `published_at`
- `image_url`
- `description`
- `extracted_urls`: organizer, paper, code, video, or PDF URLs found in the feed description
- `likely_kind` and `suggested_tags`: tentative classification to carry into
`hugo-event-intake` review. Empty `likely_kind` means the source did not
provide enough evidence; resolve it before writing.
- `suggested_intake_inputs`: URLs to pass to `hugo-event-intake`

Skip candidates marked `existing_path`. They already match an event entry.

3. Cross-check uncertain candidates.

Read [references/source-policy.md](references/source-policy.md) when deciding which
external facts are sufficient. In short: feeds are enough to identify source
items, but not always enough to determine event name, exact talk time, location,
or talk kind. Prefer organizer pages discovered in `extracted_urls`.

4. Present a proposal before writing.

Show the user a compact table of missing candidates with:

- title
- source URL
- date from the feed
- likely organizer URL if discovered
- any missing fields that `hugo-event-intake` will need to resolve

Ask which candidates to create. Do not assume that every Speaker Deck upload should
be a site event; some decks are notes, paper readings, or drafts.
Do not keep `Speaker Deck` as the event name or `Report` as the kind when the
description or discovered URLs show that the deck is a paper reading or journal
club entry.

5. Create accepted events through `hugo-event-intake`.

For each accepted candidate, run the existing intake probe with the suggested URLs:

```bash
uv run python .agents/skills/hugo-event-intake/scripts/hugo_event_intake.py probe --repo-root . --input <speaker-deck-url> --input <organizer-url> --output /tmp/event-spec.json
```

Then follow the `hugo-event-intake` confirm-then-write workflow. Keep one
`/tmp/event-spec-<slug>.json` per candidate when processing several talks.

## Source Matching Rules

- Treat exact normalized URLs as duplicates.
- Also inspect Markdown body URLs because older event pages may include source links
outside top-level frontmatter.
- Keep slide-deck sources such as Speaker Deck as `url_slides`.
- Pass organizer URLs such as connpass or official conference pages as additional
`hugo-event-intake` inputs.
- Use RSS/Atom and source-specific public metadata first. Avoid scraping HTML
unless a source adapter owns that parsing.
- Keep adapter-specific settings in each source's `options` object so future
HTML/API adapters can own selectors, pagination, API parameters, or URL-role
rules without changing the audit workflow.

## When To Stop

- Stop if Speaker Deck or the named feed is unavailable and the user did not provide
another source list.
- Stop before writing if official organizer metadata conflicts with Speaker Deck
title, date, or description.
- Stop before creating pages when the candidate is not clearly a public talk,
presentation, poster, report, or journal club entry.
4 changes: 4 additions & 0 deletions .agents/skills/hugo-talks-sync/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "Hugo Talks Sync"
short_description: "Find missing talks and feed Hugo event intake."
default_prompt: "Use $hugo-talks-sync to compare shunk031's Speaker Deck profile with content/event, propose missing talks, and hand accepted URLs to $hugo-event-intake."
29 changes: 29 additions & 0 deletions .agents/skills/hugo-talks-sync/references/source-policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Source Policy

Use this reference when `hugo-talks-sync` finds candidate talks.

## Authority Order

1. Existing `content/event` entries decide whether a source is already registered.
2. Official organizer pages decide event name, exact date/time, location, and session context.
3. Source feeds and source-specific metadata decide item title, source URL, cover image, and description.
4. Secondary sources may confirm facts, but should not be the only source for event metadata.

## Source Handling

- Prefer explicit source adapters over one-off scraping.
- Use `speakerdeck-profile` for Speaker Deck profiles, `feed` for auto-detected RSS/Atom sources, and explicit `rss` or `atom` adapters when the expected feed format is known.
- Treat feed `pubDate`, `published`, or `updated` as source publication dates until an organizer page confirms the actual event time.
- Treat URLs inside descriptions or summaries as discovery hints. Inspect them before writing.
- Add a new adapter when a source needs platform-specific semantics, such as user profile pagination, API authentication, HTML selectors, video metadata, or source-specific URL roles. Put adapter-specific settings in `options`.

## Candidate Classification

- Paper-reading titles or descriptions usually become `Journal Club`.
- Speaker Deck-only items that cite a paper URL such as arXiv or describe a
survey/paper explanation usually become `Journal Club`, not `Report`.
- Invited event pages, seminars, and conference talks usually become `Invited Talk`.
- Conference recap decks usually become `Report`.
- Poster sessions or explicitly poster-like pages usually become `Invited Poster`.
- If classification is unclear, leave it for `hugo-event-intake` confirmation instead of guessing.
An empty `likely_kind` in audit output is preferable to a false `Invited Talk`.
Loading
Loading