From 602ad63816e36da3a11e05dbd754c48c7db5ce27 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Tue, 14 Apr 2026 01:07:17 +0200 Subject: [PATCH 1/4] ci: add daily workflow to track .archgate adoption via PostHog Queries the GitHub code-search API once a day for public repos containing a .archgate/ folder and posts the count, file-match total, and deduped repo list to PostHog. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/track-adoption.yml | 80 ++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 .github/workflows/track-adoption.yml diff --git a/.github/workflows/track-adoption.yml b/.github/workflows/track-adoption.yml new file mode 100644 index 00000000..913e2bfc --- /dev/null +++ b/.github/workflows/track-adoption.yml @@ -0,0 +1,80 @@ +name: Track adoption + +on: + schedule: + - cron: "0 8 * * *" # daily at 08:00 UTC + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + +jobs: + track: + name: Count .archgate adopters on GitHub + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Search GitHub for .archgate/ folders + id: search + env: + # Classic PAT with `public_repo` scope. The default GITHUB_TOKEN is an + # app-installation token and is not guaranteed to work against the + # code-search API, which requires a user account. + GH_TOKEN: ${{ secrets.GH_SEARCH_TOKEN }} + run: | + set -euo pipefail + + # Code search is capped at 1000 results per query. If we ever approach + # that, re-shard (e.g. by language or owner) before the count saturates. + repos_json=$(gh search code 'path:.archgate/' \ + --limit 1000 \ + --json repository \ + --jq '[.[] | select(.repository.isPrivate == false) | .repository.nameWithOwner] | unique') + + repo_count=$(jq 'length' <<< "$repos_json") + file_count=$(gh api -X GET search/code -f q='path:.archgate/' --jq '.total_count') + + echo "repo_count=$repo_count" >> "$GITHUB_OUTPUT" + echo "file_count=$file_count" >> "$GITHUB_OUTPUT" + { + echo "repos_json<> "$GITHUB_OUTPUT" + + echo "::notice::Found $repo_count public repos ($file_count file matches)" + + - name: Send event to PostHog + env: + POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} + POSTHOG_HOST: ${{ vars.POSTHOG_HOST || 'https://eu.i.posthog.com' }} + REPO_COUNT: ${{ steps.search.outputs.repo_count }} + FILE_COUNT: ${{ steps.search.outputs.file_count }} + REPOS_JSON: ${{ steps.search.outputs.repos_json }} + run: | + set -euo pipefail + + payload=$(jq -n \ + --arg api_key "$POSTHOG_API_KEY" \ + --argjson repo_count "$REPO_COUNT" \ + --argjson file_count "$FILE_COUNT" \ + --argjson repos "$REPOS_JSON" \ + '{ + api_key: $api_key, + event: "github_repos_count", + distinct_id: "archgate-github-tracker", + properties: { + repo_count: $repo_count, + file_count: $file_count, + repos: $repos, + "$lib": "github-actions" + } + }') + + curl -fsS -X POST "$POSTHOG_HOST/i/v0/e/" \ + -H "Content-Type: application/json" \ + --data-binary "$payload" From 46466eb4327870d96fe1447fb35876a606dc7655 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Tue, 14 Apr 2026 01:31:30 +0200 Subject: [PATCH 2/4] ci: emit per-repo events instead of aggregate count Switches the PostHog payload from a single github_repos_count event to one archgate_repo_seen event per repo, batched into /batch/. distinct_id = owner/name so each repo surfaces as a PostHog person with first-seen / last-seen tracked automatically; $set populates the person properties (owner, name, full_name, url, is_fork). Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/track-adoption.yml | 63 +++++++++++++++++++--------- 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/.github/workflows/track-adoption.yml b/.github/workflows/track-adoption.yml index 913e2bfc..54a4a9be 100644 --- a/.github/workflows/track-adoption.yml +++ b/.github/workflows/track-adoption.yml @@ -14,7 +14,7 @@ concurrency: jobs: track: - name: Count .archgate adopters on GitHub + name: Emit per-repo adoption events runs-on: ubuntu-latest timeout-minutes: 5 steps: @@ -33,48 +33,71 @@ jobs: repos_json=$(gh search code 'path:.archgate/' \ --limit 1000 \ --json repository \ - --jq '[.[] | select(.repository.isPrivate == false) | .repository.nameWithOwner] | unique') + --jq ' + [.[].repository] + | map(select(.isPrivate == false)) + | unique_by(.nameWithOwner) + | map({ + full_name: .nameWithOwner, + owner: (.nameWithOwner | split("/")[0]), + name: (.nameWithOwner | split("/")[1]), + url: .url, + is_fork: .isFork + }) + ') repo_count=$(jq 'length' <<< "$repos_json") - file_count=$(gh api -X GET search/code -f q='path:.archgate/' --jq '.total_count') - echo "repo_count=$repo_count" >> "$GITHUB_OUTPUT" - echo "file_count=$file_count" >> "$GITHUB_OUTPUT" { echo "repos_json<> "$GITHUB_OUTPUT" - echo "::notice::Found $repo_count public repos ($file_count file matches)" + echo "::notice::Found $repo_count public repos" - - name: Send event to PostHog + - name: Send per-repo events to PostHog env: POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} POSTHOG_HOST: ${{ vars.POSTHOG_HOST || 'https://eu.i.posthog.com' }} - REPO_COUNT: ${{ steps.search.outputs.repo_count }} - FILE_COUNT: ${{ steps.search.outputs.file_count }} REPOS_JSON: ${{ steps.search.outputs.repos_json }} run: | set -euo pipefail + timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ) + + # One event per repo, batched into a single PostHog call. + # distinct_id = "owner/name" makes each repo a "person" in PostHog, + # so first-seen / last-seen and per-repo timelines come for free. + # $set populates the person properties so the Persons view is useful. payload=$(jq -n \ --arg api_key "$POSTHOG_API_KEY" \ - --argjson repo_count "$REPO_COUNT" \ - --argjson file_count "$FILE_COUNT" \ + --arg timestamp "$timestamp" \ --argjson repos "$REPOS_JSON" \ '{ api_key: $api_key, - event: "github_repos_count", - distinct_id: "archgate-github-tracker", - properties: { - repo_count: $repo_count, - file_count: $file_count, - repos: $repos, - "$lib": "github-actions" - } + batch: ($repos | map({ + event: "archgate_repo_seen", + distinct_id: .full_name, + timestamp: $timestamp, + properties: { + owner: .owner, + name: .name, + full_name: .full_name, + url: .url, + is_fork: .is_fork, + "$lib": "github-actions", + "$set": { + owner: .owner, + name: .name, + full_name: .full_name, + url: .url, + is_fork: .is_fork + } + } + })) }') - curl -fsS -X POST "$POSTHOG_HOST/i/v0/e/" \ + curl -fsS -X POST "$POSTHOG_HOST/batch/" \ -H "Content-Type: application/json" \ --data-binary "$payload" From 75a91bc6e90a46bb7d50026de8345deb96ae5a47 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Tue, 14 Apr 2026 01:35:01 +0200 Subject: [PATCH 3/4] ci: add deterministic UUIDs to dedupe same-day re-runs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PostHog drops events whose `uuid` matches a previously-ingested one. Computing uuid5(NAMESPACE_URL, "archgate_repo_seen:{date}:{repo}") makes a second workflow run on the same UTC day (e.g. manual dispatch after the cron) a no-op at ingestion — so raw event counts stay clean. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/track-adoption.yml | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/.github/workflows/track-adoption.yml b/.github/workflows/track-adoption.yml index 54a4a9be..8ec6a451 100644 --- a/.github/workflows/track-adoption.yml +++ b/.github/workflows/track-adoption.yml @@ -64,7 +64,29 @@ jobs: run: | set -euo pipefail - timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ) + date_utc=$(date -u +%Y-%m-%d) + timestamp="${date_utc}T$(date -u +%H:%M:%SZ)" + + # Deterministic UUIDv5 per (event, UTC date, repo). PostHog dedupes + # events that share a uuid with a previously-ingested event, so a + # second workflow run on the same day (e.g. manual dispatch after + # the cron) is a no-op at ingestion. + uuids_json=$(python3 - "$date_utc" <<'PY' + import json, os, sys, uuid + date = sys.argv[1] + repos = json.loads(os.environ["REPOS_JSON"]) + out = { + r["full_name"]: str( + uuid.uuid5( + uuid.NAMESPACE_URL, + f"archgate_repo_seen:{date}:{r['full_name']}", + ) + ) + for r in repos + } + print(json.dumps(out)) + PY + ) # One event per repo, batched into a single PostHog call. # distinct_id = "owner/name" makes each repo a "person" in PostHog, @@ -74,11 +96,13 @@ jobs: --arg api_key "$POSTHOG_API_KEY" \ --arg timestamp "$timestamp" \ --argjson repos "$REPOS_JSON" \ + --argjson uuids "$uuids_json" \ '{ api_key: $api_key, batch: ($repos | map({ event: "archgate_repo_seen", distinct_id: .full_name, + uuid: $uuids[.full_name], timestamp: $timestamp, properties: { owner: .owner, From f103bdcc8991b7cc859ff325cc986497a07e4b57 Mon Sep 17 00:00:00 2001 From: Rhuan Barreto Date: Tue, 14 Apr 2026 01:38:17 +0200 Subject: [PATCH 4/4] ci: warn when code-search approaches the 1000-result cap Adds an explicit file_count probe and emits ::warning:: at 800 matches and ::error:: at 1000. GitHub's code-search API plateaus silently at 1000 results, which would cause the repo set to stop growing without any visible signal. The annotations turn that into a visible trigger to add query sharding. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/track-adoption.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/track-adoption.yml b/.github/workflows/track-adoption.yml index 8ec6a451..0e46196a 100644 --- a/.github/workflows/track-adoption.yml +++ b/.github/workflows/track-adoption.yml @@ -47,6 +47,8 @@ jobs: ') repo_count=$(jq 'length' <<< "$repos_json") + file_count=$(gh api -X GET search/code -f q='path:.archgate/' --jq '.total_count') + echo "repo_count=$repo_count" >> "$GITHUB_OUTPUT" { echo "repos_json<> "$GITHUB_OUTPUT" - echo "::notice::Found $repo_count public repos" + echo "::notice::Found $repo_count public repos ($file_count file matches)" + + # GitHub code search caps results at 1000 per query. Beyond that, the + # repo count plateaus silently. Warn early so we shard before saturating. + if [ "$file_count" -ge 1000 ]; then + echo "::error::File match count is ${file_count} — AT the 1000-result search cap. Repo discovery is now incomplete; shard the query (see comment above) before trusting this data." + elif [ "$file_count" -ge 800 ]; then + echo "::warning::File match count is ${file_count} — approaching the 1000-result search cap. Plan query sharding before we saturate." + fi - name: Send per-repo events to PostHog env: