4
workflow_dispatch:
5
inputs:
6
week:
7
- description: 'Week slug, e.g. 2026-W25.'
7
+ description: 'Week slug, e.g. 2026-W25'
8
required: true
9
type: string
10
- article_url:
11
- description: 'Published SquadScope article URL.'
12
- required: true
13
- type: string
14
- article_path:
15
- description: 'Article content path, e.g. content/weekly/2026/W25.md.'
16
- required: true
10
+ publish_run_id:
11
+ description: 'Optional: crawl-and-publish workflow run ID that produced the manifest. If omitted, uses the most recent.'
12
+ required: false
13
type: string
14
15
permissions:
32
with:
33
python-version: '3.12'
34
39
- - name: Build manifest and trigger podcast generation
35
+ - name: Derive paths from week slug
36
+ id: derive
37
+ env:
38
+ WEEK: ${{ inputs.week }}
39
+ run: |
40
+ set -euo pipefail
41
+ if [[ ! "$WEEK" =~ ^[0-9]{4}-W[0-9]{2}$ ]]; then
42
+ echo "::error::week must match YYYY-WNN (two-digit week), e.g. 2026-W25 (got: $WEEK)"
43
+ exit 1
44
+ fi
45
+
46
+ # 2026-W25 → year=2026, short=W25
47
+ YEAR="${WEEK%%-*}"
48
+ SHORT="${WEEK##*-}"
49
+ echo "article_path=content/weekly/${YEAR}/${SHORT}.md" >> "$GITHUB_OUTPUT"
50
+ echo "article_url=https://claracle.com/weekly/${YEAR}/${SHORT}/" >> "$GITHUB_OUTPUT"
51
+
52
+ - name: Locate publish manifest from crawl-and-publish
53
+ id: manifest-locate
54
+ env:
55
+ WEEK: ${{ inputs.week }}
56
+ PUBLISH_RUN_ID_INPUT: ${{ inputs.publish_run_id }}
57
+ run: |
58
+ set -euo pipefail
59
+ if ! git fetch origin publish 2>/dev/null; then
60
+ echo "::error::Could not fetch publish branch. Has crawl-and-publish run yet?"
61
+ exit 1
62
+ fi
63
+ git checkout origin/publish -- data/candidates/ 2>/dev/null || true
64
+
65
+ if [ -n "$PUBLISH_RUN_ID_INPUT" ]; then
66
+ # Use specified run ID
67
+ if ! [[ "$PUBLISH_RUN_ID_INPUT" =~ ^[0-9]+$ ]]; then
68
+ echo "::error::publish_run_id must be a numeric workflow run ID (got: $PUBLISH_RUN_ID_INPUT)"
69
+ exit 1
70
+ fi
71
+ MANIFEST="data/candidates/${WEEK}/${PUBLISH_RUN_ID_INPUT}/publish-manifest.json"
72
+ if [ ! -f "$MANIFEST" ]; then
73
+ echo "::error::Manifest not found at $MANIFEST for specified run $PUBLISH_RUN_ID_INPUT"
74
+ exit 1
75
+ fi
76
+ echo "manifest_path=$MANIFEST" >> "$GITHUB_OUTPUT"
77
+ echo "publish_run_id=$PUBLISH_RUN_ID_INPUT" >> "$GITHUB_OUTPUT"
78
+ echo "::notice::Using manifest from specified run: $PUBLISH_RUN_ID_INPUT"
79
+ else
80
+ # Find the most recent manifest for this week
81
+ MANIFEST=$(find "data/candidates/${WEEK}" -name 'publish-manifest.json' -type f 2>/dev/null | sort -V | tail -1)
82
+ if [ -z "$MANIFEST" ]; then
83
+ echo "::error::No manifest found for week $WEEK in data/candidates/. Has crawl-and-publish completed for this week?"
84
+ exit 1
85
+ fi
86
+ # Extract run ID from path: data/candidates/WEEK/RUN_ID/publish-manifest.json
87
+ RUN_ID="$(basename "$(dirname "$MANIFEST")")"
88
+ echo "manifest_path=$MANIFEST" >> "$GITHUB_OUTPUT"
89
+ echo "publish_run_id=$RUN_ID" >> "$GITHUB_OUTPUT"
90
+ echo "::notice::Using manifest from most recent run: $RUN_ID"
91
+ fi
92
+
93
+ - name: Trigger podcast generation with existing manifest
94
env:
95
PODCASTER_ENDPOINT: ${{ vars.PODCASTER_ENDPOINT }}
96
PODCASTER_API_KEY: ${{ secrets.PODCASTER_API_KEY }}
97
WEEK: ${{ inputs.week }}
44
- ARTICLE_URL: ${{ inputs.article_url }}
45
- ARTICLE_PATH: ${{ inputs.article_path }}
46
- PUBLISH_RUN_ID: ${{ github.run_id }}
98
+ ARTICLE_URL: ${{ steps.derive.outputs.article_url }}
99
+ ARTICLE_PATH: ${{ steps.derive.outputs.article_path }}
100
+ MANIFEST_FILE: ${{ steps.manifest-locate.outputs.manifest_path }}
101
+ PUBLISH_RUN_ID: ${{ steps.manifest-locate.outputs.publish_run_id }}
102
+ TRIGGER_RUN_ID: ${{ github.run_id }}
103
run: |
104
set -euo pipefail
105
if [ -z "$PODCASTER_ENDPOINT" ] || [ -z "$PODCASTER_API_KEY" ]; then
107
exit 1
108
fi
109
if [ ! -f "$ARTICLE_PATH" ]; then
54
- echo "::error::article_path '$ARTICLE_PATH' does not exist in the repository."
110
+ echo "::error::Article '$ARTICLE_PATH' does not exist in the repository."
111
exit 1
112
fi
113
+ if [ ! -f "$MANIFEST_FILE" ]; then
114
+ echo "::error::Manifest file not found: $MANIFEST_FILE"
115
+ exit 1
116
+ fi
117
+ echo "::notice::Using manifest from crawl-and-publish run $PUBLISH_RUN_ID ($(date -r "$MANIFEST_FILE" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo 'date unknown'))"
118
58
- # Build a minimal publish manifest for the handoff script
59
- mkdir -p .podcast-trigger
60
- python3 - <<'PY' "$WEEK" "$ARTICLE_PATH" .podcast-trigger/publish-manifest.json
61
- import hashlib
62
- import json
63
- import sys
64
- from pathlib import Path
65
-
66
- week, article_path, manifest_path = sys.argv[1:]
67
- article = Path(article_path)
68
- article_bytes = article.read_bytes()
69
- article_sha = hashlib.sha256(article_bytes).hexdigest()
70
- raw_payload = {"week": week, "source": "github", "article_path": article_path}
71
- raw_bytes = (json.dumps(raw_payload, sort_keys=True) + "\n").encode("utf-8")
72
- raw_sha = hashlib.sha256(raw_bytes).hexdigest()
73
-
74
- manifest = {
75
- "week": week,
76
- "run_mode": "normal",
77
- "candidate": {"summary_sha256": article_sha},
78
- "analysis": {"ai_status": "ai"},
79
- "promotion": {"eligible": True, "decision": "promote"},
80
- "source_artifacts": [
81
- {
82
- "role": "raw_github",
83
- "path": f"data/raw/{week}.json",
84
- "name": f"{week}-raw-github",
85
- "sha256": raw_sha,
86
- "generated_at": "2026-01-01T00:00:00Z",
87
- "source_status": "fresh",
88
- "exists": True,
89
- "size_bytes": len(raw_bytes),
90
- "freshness": {"status": "fresh", "reasons": []},
91
- "provenance": {"path": f"data/raw/{week}.json", "sha256": raw_sha},
92
- },
93
- {
94
- "role": "published_summary",
95
- "path": article_path,
96
- "sha256": article_sha,
97
- "generated_at": "2026-01-01T00:00:00Z",
98
- "exists": True,
99
- "size_bytes": len(article_bytes),
100
- "provenance": {"path": article_path, "sha256": article_sha},
101
- },
102
- ],
103
- }
104
- Path(manifest_path).write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
105
- PY
106
-
107
- # Trigger real podcast generation (no --podcaster-dry-run)
119
+ # Trigger real podcast generation using the existing validated manifest
120
python3 scripts/podcaster_handoff.py \
121
--week "$WEEK" \
122
--article-url "$ARTICLE_URL" \
123
--article-path "$ARTICLE_PATH" \
124
--publish-run-id "$PUBLISH_RUN_ID" \
125
--publish-mode normal \
114
- --manifest .podcast-trigger/publish-manifest.json \
126
+ --manifest "$MANIFEST_FILE" \
127
--podcast-config config/podcast.json