-
Notifications
You must be signed in to change notification settings - Fork 0
496 lines (444 loc) · 21 KB
/
Copy pathdocs-sync-skills.yml
File metadata and controls
496 lines (444 loc) · 21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
name: docs-sync-skills
# ─────────────────────────────────────────────────────────────────────────────
# Phase 4: push trigger is ENABLED. The workflow runs automatically on every
# merge to main that touches a watched path. Only enable in production after
# the Phase 3 test matrix in docs/scripts/skills-sync/README.md is green.
# ─────────────────────────────────────────────────────────────────────────────
on:
push:
branches: [main]
paths:
- 'async-collaboration/**'
- 'realtime-collaboration/**'
- 'api-reference/rest-apis/**'
- 'api-reference/sdk/**'
- 'self-host-data/**'
- 'security/**'
- 'webhooks/**'
- 'get-started/**'
- 'backend-sdks/**'
- 'ui-customization/**'
- 'permission-management/**'
- 'in-app-user-feedback/**'
- 'ai/**'
workflow_dispatch:
inputs:
before_sha:
description: "Base SHA (the commit to diff FROM). Defaults to HEAD~1."
required: false
type: string
target_sha:
description: "Target SHA (the commit to diff TO). Defaults to the workflow's checkout SHA."
required: false
type: string
concurrency:
group: skills-sync-${{ github.ref }}
cancel-in-progress: false # never cancel a running sync — risk of partial PR
permissions:
contents: read
env:
AGENT_SKILLS_REPO: velt-js/agent-skills
jobs:
# ───────────────────────────────────────────────────────────────────────────
# Job 1: plan — Stage 0 (script) + Agent 1 (docs-diff-planner)
# ───────────────────────────────────────────────────────────────────────────
plan:
name: Plan
runs-on: ubuntu-latest
timeout-minutes: 15
outputs:
has_work: ${{ steps.gate.outputs.has_work }}
tickets: ${{ steps.gate.outputs.tickets }}
target_sha: ${{ steps.resolve-shas.outputs.target_sha }}
before_sha: ${{ steps.resolve-shas.outputs.before_sha }}
# The agent-skills ref that patches must be GENERATED against so they
# apply cleanly onto the same ref in validate-and-pr. If an open
# skills-sync PR exists, that's its (accumulator) branch; otherwise main.
base_ref: ${{ steps.resolve-base-ref.outputs.base_ref }}
steps:
- name: Checkout docs (full history)
uses: actions/checkout@v4
with:
fetch-depth: 0
path: docs
- name: Checkout agent-skills
uses: actions/checkout@v4
with:
repository: ${{ env.AGENT_SKILLS_REPO }}
token: ${{ secrets.CROSS_REPO_PAT }}
path: agent-skills
fetch-depth: 1
- name: Resolve agent-skills base ref (reuse open skills-sync PR branch)
id: resolve-base-ref
env:
GH_TOKEN: ${{ secrets.CROSS_REPO_PAT }}
run: |
# Patches are generated (update-skill) and applied (validate-and-pr)
# against agent-skills. They MUST share a base, or hunks like the
# metadata.json version line fail to apply. If an open skills-sync PR
# exists, its branch is the accumulator that validate-and-pr commits
# onto — so generate against that branch. Otherwise generate against main.
EXISTING=$(gh pr list \
--repo ${{ env.AGENT_SKILLS_REPO }} \
--state open \
--label skills-sync \
--json headRefName,updatedAt \
--jq 'sort_by(.updatedAt) | reverse | .[0].headRefName // empty')
if [ -n "$EXISTING" ]; then
echo "base_ref=$EXISTING" >> "$GITHUB_OUTPUT"
echo "Open skills-sync PR found — patch base is its branch: $EXISTING"
else
echo "base_ref=main" >> "$GITHUB_OUTPUT"
echo "No open skills-sync PR — patch base is main"
fi
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install claude-code CLI
# NOTE: Adjust this to match your actual CLI install method.
# The release-note pipeline may already install it differently.
run: npm install -g @anthropic-ai/claude-code
- name: Resolve before/target SHAs
id: resolve-shas
working-directory: docs
run: |
TARGET="${{ inputs.target_sha }}"
BEFORE="${{ inputs.before_sha }}"
if [ -z "$TARGET" ]; then TARGET=$(git rev-parse HEAD); fi
if [ -z "$BEFORE" ]; then BEFORE=$(git rev-parse "${TARGET}~1"); fi
echo "target_sha=$TARGET" >> "$GITHUB_OUTPUT"
echo "before_sha=$BEFORE" >> "$GITHUB_OUTPUT"
echo "Resolved: before=$BEFORE target=$TARGET"
- name: Stage 0 — compute diff
env:
BEFORE_SHA: ${{ steps.resolve-shas.outputs.before_sha }}
AFTER_SHA: ${{ steps.resolve-shas.outputs.target_sha }}
DOCS_DIR: ${{ github.workspace }}/docs
run: bash docs/scripts/skills-sync/compute-diff.sh
- name: Stage 1a — prepare Agent 1 inputs
env:
DOCS_DIR: ${{ github.workspace }}/docs
SKILLS_DIR: ${{ github.workspace }}/agent-skills/skills
run: bash docs/scripts/skills-sync/prepare-agent-1-inputs.sh
- name: Stage 1b — run Agent 1 (docs-diff-planner)
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
DOCS_DIR: ${{ github.workspace }}/docs
AGENT_MAX_TURNS: "80"
run: bash docs/scripts/skills-sync/run-claude-agent.sh docs-diff-planner
- name: Gate — does the plan have any work?
id: gate
run: |
if [ ! -f outputs/plan.json ]; then
echo "Agent 1 did not produce plan.json — aborting"
exit 1
fi
TICKET_COUNT=$(jq '.tickets | length' outputs/plan.json)
UNMAPPED_COUNT=$(jq '.unmapped | length' outputs/plan.json)
echo "tickets=$TICKET_COUNT unmapped=$UNMAPPED_COUNT"
if [ "$TICKET_COUNT" -eq 0 ] && [ "$UNMAPPED_COUNT" -eq 0 ]; then
echo "has_work=false" >> "$GITHUB_OUTPUT"
echo "tickets=[]" >> "$GITHUB_OUTPUT"
else
echo "has_work=true" >> "$GITHUB_OUTPUT"
echo "tickets=$(jq -c '[.tickets[].skill]' outputs/plan.json)" >> "$GITHUB_OUTPUT"
fi
- name: Upload plan artifact
if: steps.gate.outputs.has_work == 'true'
uses: actions/upload-artifact@v4
with:
name: plan-${{ github.run_id }}
path: |
outputs/plan.json
outputs/hunks/
retention-days: 7
# ───────────────────────────────────────────────────────────────────────────
# Job 2: update-skill — Agent 2 (one of 6 variants), fan-out per impacted skill
# ───────────────────────────────────────────────────────────────────────────
update-skill:
name: Update ${{ matrix.skill }}
needs: plan
if: needs.plan.outputs.has_work == 'true' && needs.plan.outputs.tickets != '[]'
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
max-parallel: 5
matrix:
skill: ${{ fromJson(needs.plan.outputs.tickets) }}
steps:
- name: Checkout docs
uses: actions/checkout@v4
with:
ref: ${{ needs.plan.outputs.target_sha }}
path: docs
- name: Checkout agent-skills
uses: actions/checkout@v4
with:
repository: ${{ env.AGENT_SKILLS_REPO }}
token: ${{ secrets.CROSS_REPO_PAT }}
path: agent-skills
# Generate the patch against the SAME ref validate-and-pr applies onto
# (the open skills-sync PR's accumulator branch, else main). Without
# this the patch is built against main and fails to apply onto a
# drifted accumulator branch (e.g. an already-bumped metadata.json).
ref: ${{ needs.plan.outputs.base_ref }}
fetch-depth: 1
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install claude-code CLI
run: npm install -g @anthropic-ai/claude-code
- name: Download plan artifact
uses: actions/download-artifact@v4
with:
name: plan-${{ github.run_id }}
path: outputs
- name: Resolve updater variant for ${{ matrix.skill }}
id: variant
run: |
VARIANT=$(bash docs/scripts/skills-sync/dispatch-updater-variant.sh "${{ matrix.skill }}")
echo "name=$VARIANT" >> "$GITHUB_OUTPUT"
echo "Resolved variant: $VARIANT"
- name: Prepare Agent 2 inputs for ${{ matrix.skill }}
env:
DOCS_DIR: ${{ github.workspace }}/docs
SKILLS_DIR: ${{ github.workspace }}/agent-skills/skills
run: bash docs/scripts/skills-sync/prepare-agent-2-inputs.sh "${{ matrix.skill }}"
- name: Run Agent 2 — ${{ steps.variant.outputs.name }}
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
DOCS_DIR: ${{ github.workspace }}/docs
AGENT_MAX_TURNS: "80"
run: bash docs/scripts/skills-sync/run-claude-agent.sh "${{ steps.variant.outputs.name }}"
- name: Build patch artifact
id: patch
run: |
mkdir -p agent-skills-patches
# The updater edited files in inputs/skill/ — rsync them back onto the real skill dir,
# then capture the diff as a patch (so apply-patches.sh can merge across matrix jobs).
rsync -a --delete inputs/skill/ agent-skills/skills/${{ matrix.skill }}/
cd agent-skills
if git diff --quiet -- skills/${{ matrix.skill }}/; then
echo "No changes for ${{ matrix.skill }} — agent declined"
echo "has_changes=false" >> "$GITHUB_OUTPUT"
else
git diff -- skills/${{ matrix.skill }}/ > ../agent-skills-patches/${{ matrix.skill }}.patch
echo "has_changes=true" >> "$GITHUB_OUTPUT"
fi
- name: Upload changes artifact for ${{ matrix.skill }}
if: steps.patch.outputs.has_changes == 'true'
uses: actions/upload-artifact@v4
with:
name: changes-${{ matrix.skill }}-${{ github.run_id }}
path: |
outputs/changes.json
agent-skills-patches/${{ matrix.skill }}.patch
retention-days: 7
- name: Upload decline artifact for ${{ matrix.skill }}
if: steps.patch.outputs.has_changes == 'false' && hashFiles('outputs/decline.json') != ''
uses: actions/upload-artifact@v4
with:
name: decline-${{ matrix.skill }}-${{ github.run_id }}
path: outputs/decline.json
retention-days: 7
# ───────────────────────────────────────────────────────────────────────────
# Job 3: validate-and-pr — apply patches, Agent 3 validator, Agent 4 PR composer
# ───────────────────────────────────────────────────────────────────────────
validate-and-pr:
name: Validate & PR
needs: [plan, update-skill]
if: always() && needs.plan.outputs.has_work == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout docs
uses: actions/checkout@v4
with:
ref: ${{ needs.plan.outputs.target_sha }}
path: docs
- name: Checkout agent-skills
uses: actions/checkout@v4
with:
repository: ${{ env.AGENT_SKILLS_REPO }}
token: ${{ secrets.CROSS_REPO_PAT }}
path: agent-skills
fetch-depth: 1
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install claude-code CLI
run: npm install -g @anthropic-ai/claude-code
- name: Configure git identity
working-directory: agent-skills
run: |
git config user.name "skills-sync[bot]"
git config user.email "skills-sync[bot]@users.noreply.github.com"
- name: Resolve branch (reuse existing skills-sync PR if one is open)
working-directory: agent-skills
env:
GH_TOKEN: ${{ secrets.CROSS_REPO_PAT }}
run: |
SHORT_SHA="$(echo "${{ needs.plan.outputs.target_sha }}" | cut -c1-7)"
echo "SHORT_SHA=$SHORT_SHA" >> "$GITHUB_ENV"
# 1. Prefer reusing the branch of any already-open skills-sync PR so
# a new run lands as a commit on the existing PR instead of
# opening a parallel one. We pick the most recently-updated open
# PR labeled `skills-sync` (there should be at most one in
# steady state, but we sort just in case).
EXISTING=$(gh pr list \
--repo velt-js/agent-skills \
--state open \
--label skills-sync \
--json number,headRefName,updatedAt \
--jq 'sort_by(.updatedAt) | reverse | .[0] // empty')
if [ -n "$EXISTING" ]; then
BRANCH=$(echo "$EXISTING" | jq -r '.headRefName')
EXISTING_PR_NUMBER=$(echo "$EXISTING" | jq -r '.number')
echo "Reusing open skills-sync PR #$EXISTING_PR_NUMBER on branch $BRANCH"
git fetch origin "$BRANCH"
git checkout "$BRANCH"
echo "EXISTING_PR_NUMBER=$EXISTING_PR_NUMBER" >> "$GITHUB_ENV"
echo "EXISTING_PR_BRANCH=$BRANCH" >> "$GITHUB_ENV"
echo "BRANCH=$BRANCH" >> "$GITHUB_ENV"
exit 0
fi
# 2. No open skills-sync PR — fall back to the per-SHA branch.
BRANCH="skills-sync/$SHORT_SHA"
if git ls-remote --exit-code origin "refs/heads/$BRANCH" >/dev/null 2>&1; then
# Same-SHA re-run scenario: branch exists but no PR was opened
# (or the PR was closed). Reuse the branch but treat as new-PR path
# so Agent 4 opens a fresh PR.
git fetch origin "$BRANCH"
git checkout "$BRANCH"
else
git checkout -b "$BRANCH"
fi
echo "BRANCH=$BRANCH" >> "$GITHUB_ENV"
- name: Download all change artifacts
uses: actions/download-artifact@v4
with:
pattern: changes-*-${{ github.run_id }}
path: incoming
merge-multiple: true
- name: Download plan artifact
uses: actions/download-artifact@v4
with:
name: plan-${{ github.run_id }}
path: outputs
- name: Apply matrix patches
env:
INCOMING_DIR: ${{ github.workspace }}/incoming/agent-skills-patches
DOWNLOAD_ROOT: ${{ github.workspace }}/incoming
AGENT_SKILLS_DIR: ${{ github.workspace }}/agent-skills
run: bash docs/scripts/skills-sync/apply-patches.sh
- name: Run Agent 3 (skill-format-validator)
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
DOCS_DIR: ${{ github.workspace }}/docs
# Validator consolidates output from N matrix-parallel updaters,
# runs npm run build, and may fix formatting drift across all
# touched rule files — 40 turns is too tight when 2+ skills update.
AGENT_MAX_TURNS: "120"
run: bash docs/scripts/skills-sync/run-claude-agent.sh skill-format-validator
- name: Commit validator output (if any)
working-directory: agent-skills
run: |
if ! git diff --quiet; then
git add -A
git commit -m "skills-sync: format fixups + regenerated AGENTS.md/AGENTS.full.md"
else
echo "No validator-driven changes"
fi
- name: Build source-commit.json for Agent 4
run: |
mkdir -p inputs
cd docs
DOCS_SHA="${{ needs.plan.outputs.target_sha }}"
DOCS_AUTHOR="$(git log -1 --format='%an' "$DOCS_SHA")"
DOCS_MSG="$(git log -1 --format='%s' "$DOCS_SHA")"
cd ..
jq -n \
--arg sha "$DOCS_SHA" \
--arg short_sha "$SHORT_SHA" \
--arg author "$DOCS_AUTHOR" \
--arg message "$DOCS_MSG" \
--arg url "${{ github.server_url }}/${{ github.repository }}/commit/$DOCS_SHA" \
--arg run_url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
'{sha: $sha, short_sha: $short_sha, author: $author, message: $message, url: $url, run_url: $run_url}' \
> inputs/source-commit.json
# Also stage what Agent 4 reads
cp outputs/plan.json inputs/plan.json
cp outputs/changes-merged.json inputs/changes-merged.json 2>/dev/null || echo '[]' > inputs/changes-merged.json
cp outputs/validator-status.json inputs/validator-status.json 2>/dev/null || true
cp outputs/abort.json inputs/abort.json 2>/dev/null || true
- name: Run Agent 4 (skill-pr-composer)
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GH_TOKEN: ${{ secrets.CROSS_REPO_PAT }}
DOCS_DIR: ${{ github.workspace }}/docs
run: bash docs/scripts/skills-sync/run-claude-agent.sh skill-pr-composer
- name: Annotate unmapped changes
if: always()
run: |
if [ ! -f outputs/plan.json ]; then exit 0; fi
UNMAPPED=$(jq '.unmapped | length' outputs/plan.json)
if [ "$UNMAPPED" -gt 0 ]; then
echo "::warning::$UNMAPPED docs change(s) could not be mapped to any skill. Review plan.json → unmapped[]"
jq -r '.unmapped[] | " - \(.feature) → suggested: \(.suggested_slug // "unknown")"' outputs/plan.json
fi
- name: Assert PR was created or updated
run: |
TICKETS=$(jq -r '[.tickets[].skill] | length' outputs/plan.json 2>/dev/null || echo "0")
if [ "$TICKETS" -eq 0 ]; then
echo "No tickets — skipping PR assertion"
exit 0
fi
if [ ! -f outputs/pr-composer-result.json ]; then
echo "::error::Pipeline had $TICKETS ticket(s) but Agent 4 produced no pr-composer-result.json"
echo "::error::changes-merged.json contents: $(cat outputs/changes-merged.json 2>/dev/null || echo 'missing')"
exit 1
fi
PR_URL=$(jq -r '.pr_url // empty' outputs/pr-composer-result.json)
if [ -z "$PR_URL" ]; then
echo "::error::Pipeline had $TICKETS ticket(s) but no PR was created or updated"
echo "::error::changes-merged.json contents: $(cat outputs/changes-merged.json 2>/dev/null || echo 'missing')"
exit 1
fi
echo "PR: $PR_URL"
- name: Notify on failure (never fail silently)
if: failure()
env:
GH_TOKEN: ${{ secrets.CROSS_REPO_PAT }}
run: |
DOCS_SHA="${{ needs.plan.outputs.target_sha }}"
SHORT="$(echo "$DOCS_SHA" | cut -c1-7)"
DOCS_URL="${{ github.server_url }}/${{ github.repository }}/commit/$DOCS_SHA"
RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
MSG="⚠️ skills-sync failed for docs@${SHORT} (${DOCS_URL}). Those docs changes were **not** synced to agent-skills. Pipeline run: ${RUN_URL}"
# Prefer a comment on the open skills-sync PR; otherwise open an issue so
# a failed run is always surfaced and never silently dropped.
if [ -n "${EXISTING_PR_NUMBER:-}" ]; then
gh pr comment "$EXISTING_PR_NUMBER" --repo ${{ env.AGENT_SKILLS_REPO }} --body "$MSG" || true
else
gh issue create --repo ${{ env.AGENT_SKILLS_REPO }} \
--title "skills-sync: failed to sync docs@${SHORT}" \
--body "$MSG" \
--label skills-sync || true
fi
- name: Upload pipeline run summary
if: always()
uses: actions/upload-artifact@v4
with:
name: run-summary-${{ github.run_id }}
path: |
outputs/plan.json
outputs/changes-merged.json
outputs/validator-status.json
outputs/abort.json
outputs/pr-composer-result.json
if-no-files-found: ignore
retention-days: 30