Communitygithub.com

moonbeautiful/autumn-recruiting-radar

Skill de collecte d'offres pour le recrutement automnal destiné aux étudiants chinois (校招), stages et validation de stages.

Qu'est-ce que autumn-recruiting-radar ?

Ce Skill automatise la veille d'offres d'emploi pour le recrutement automnal (秋招) en Chine, ciblant les étudiants, les stages et les conversions de stage en CDI. Après avoir défini les préférences de l'utilisateur (promotion, poste, ville, type de recrutement, type d'entreprise), il découvre les offres en temps réel via des sources publiques (plateformes, classements, sites d'universités) et les valide prioritairement sur les sites officiels des entreprises. Le système élimine les doublons entre sources, détecte les changements et identifie les offres expirées. Les résultats sont livrés en continu dans la conversation, sans attendre la fin de la collecte. Le Skill est open source, multiplateforme (Python 3.8+), compatible avec tous les clients IA disposant de capacités de recherche web (Trae, Claude, etc.) et ne génère aucun coût supplémentaire. Il intègre un mécanisme de battement (heartbeat) pour les notifications de nouvelles offres, activable par l'utilisateur.

Compatible avec✓Claude Code~Codex CLI~Cursor
npx skills add moonbeautiful/autumn-recruiting-radar

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Radar de recrutement automnal

Skill de collecte d'informations pour le recrutement automnal (校招), les stages et la validation de stages en Chine. Commencez par confirmer les préférences de l'utilisateur, puis découvrez les postes à partir de sources publiques, en priorisant la vérification sur les sites officiels des entreprises. Enfin, utilisez des scripts déterministes pour effectuer la correspondance, la déduplication intersources, la détection des changements et l'identification des dates limites, et renvoyez la liste des postes directement dans la conversation.

Environnement d'exécution (open source et portable)

Ce Skill est destiné à être distribué en open source. Toute personne peut le cloner et l'installer sur son ordinateur. Il doit donc satisfaire aux conditions suivantes :

  • Multiplateforme : tous les scripts utilisent uniquement la bibliothèque standard Python 3.8+, et fonctionnent sous Windows / macOS / Linux ; aucun chemin absolu n'est codé en dur, aucune dépendance à des commandes spécifiques à un système (osascript, launchd, etc.).

  • Multi-modèle / Multi-client : la découverte des postes utilise uniquement la « capacité de recherche web / lecture de pages intégrée au client hôte », sans installer d'outils supplémentaires, sans contrôler le navigateur de l'utilisateur et sans engendrer de frais supplémentaires.

  • Deux chemins uniquement :

    1. Le client dispose d'une recherche web / lecture de pages intégrée (la plupart des clients IA comme Trae, Claude en sont dotés) → la collecte s'effectue directement en arrière-plan, sans action de l'utilisateur ;
    2. Le client ne dispose d'aucune capacité de recherche web → affiche le message suivant, puis s'arrête, sans jamais inventer de postes, d'entreprises, de liens ou de dates limites de mémoire.

    Message fixe lorsque la recherche web est indisponible :

    Le client actuel ne dispose pas de fonctionnalité de recherche web. Je ne peux pas obtenir les informations sur les postes en temps réel. Veuillez exécuter à nouveau ce Skill dans un client IA prenant en charge la recherche web.

  • Quel que soit le chemin utilisé pour obtenir les postes, le résultat est écrit dans runtime/inbox/jobs-input.json, et le traitement ultérieur est strictement identique.

Limites de configuration

Avant de commencer, lire intégralement :

  • configuration.md : limites de la configuration modifiable par l'utilisateur, de la configuration du projet et de l'état d'exécution ;
  • data-contract.md : champs d'entrée et de sortie des postes ;
  • source-strategy.md : priorités des sources, processus de découverte et de vérification.

Il est interdit de laisser l'utilisateur modifier config/project.json. Les préférences utilisateur sont écrites uniquement dans runtime/user-preferences.json ; l'état du programme est écrit uniquement dans runtime/state/.

Étape 0 : Initialisation

Exécutez d'abord :

SKILL_DIR=$(cd "$(dirname "SKILL.md")" && pwd)
python3 "$SKILL_DIR/scripts/bootstrap.py"

Lisez runtime/user-preferences.json. Si onboarded n'est pas true, posez uniquement les cinq questions non encore clarifiées :

  1. Année de promotion ;
  2. Poste cible ;
  3. Ville cible ;
  4. Uniquement le recrutement sur campus, ou également les stages et la validation de stage ;
  5. Quel type d'entreprise rechercher (utilisez les options discovery.company_type_options : 互联网大厂 / AI独角兽·大模型公司 / 垂类·行业公司 / 都要), et s'il existe des préférences supplémentaires en matière d'entreprise ou de secteur.

Écrivez les réponses dans runtime/user-preferences.json :

  • Si la ville est un tableau vide, cela signifie « tout le pays » ;
  • Le type d'entreprise (question 5) est utilisé à l'étape 1 pour générer la « file d'attente des entreprises » et décider qui rechercher en priorité ; la liste spécifique est obtenue par recherche en temps réel, sans être codée en dur, l'utilisateur n'a pas besoin de connaître les entreprises ;
  • Les préférences d'entreprise et de secteur sont utilisées par défaut pour le bonus de classement, sans filtrage strict ;
  • Il n'est pas demandé à l'utilisateur de remplir une liste de mots-clés ; l'agent génère role_keywords à partir de l'objectif en langage naturel ; par exemple, « produit IA » doit couvrir les appellations courantes telles que chef de produit applicatif IA, chef de produit grand modèle IA, chef de produit agent IA, chef de produit AIGC et chef de produit (direction IA) ;
  • Une fois terminé, définissez onboarded sur true et résumez la configuration à l'utilisateur.

Lorsque vous demandez le type d'entreprise, gardez un ton neutre, identique aux autres questions : expliquez que la liste spécifique des entreprises sera obtenue par recherche en temps réel par l'assistant lors de l'exécution, l'utilisateur doit simplement choisir une grande catégorie, sans avoir à lister lui-même les entreprises.

Concernant l'activation des « notifications de mise à jour », posez la question après la première diffusion des résultats (voir étape 5), ne la posez pas pendant l'initialisation.

Étape 1 : Découverte des postes (par étapes + diffusion en continu)

Lisez config/project.json (en particulier la section discovery) et references/source-strategy.md.

Principe clé : L'utilisateur n'attend pas que « tout soit recherché », mais « le prochain poste auquel postuler ». Donc, fournissez toujours les résultats au fur et à mesure, sans tout calculer d'abord. Voir la section « Découverte progressive en continu » dans source-strategy.md.

La découverte se déroule en deux étapes :

1a. Découvrir d'abord la « file d'attente des entreprises » (en temps réel, une seule requête pour plusieurs)

  • Le type d'entreprise est choisi par l'utilisateur à l'étape 0 (互联网大厂 / AI独角兽·大模型公司 / 垂类·行业公司 / 都要) ; la liste n'est pas codée en dur.
  • Utilisez des pages d'index de plateformes agrégées / classements / sites d'emploi des universités, qui « fournissent des dizaines d'entreprises en une seule requête », en fonction de la ville, du poste et du type d'entreprise de l'utilisateur, pour obtenir en temps réel un lot de « noms d'entreprises recrutant + indices de postes », créant ainsi une file d'attente ordonnée (ex. : ByteDance → Alibaba → Tencent → …).
  • Le classement peut utiliser discovery.employer_anchors pour faire apparaître d'abord les entreprises connues ; employer_anchors n'est qu'un point d'ancrage pour le tri et un filet de sécurité occasionnel. La liste réelle est déterminée par la recherche en cours.
  • Ces indices peuvent être présentés à l'utilisateur comme une « version préliminaire (à vérifier) » sans les perdre.

1b. Puis, vérifier et compléter « entreprise par entreprise » selon la file d'attente (en continu, une entreprise terminée affichée)

  • Suivez l'ordre de la file d'attente, en traitant simultanément un maximum de discovery.stream.max_concurrent_lookups entreprises ;
  • Au sein d'une entreprise, privilégiez d'abord les postes « les plus récents / en cours de recrutement » (in_hiring_first), puis les anciens ;
  • Dès qu'une entreprise est entièrement traitée, ajoutez ses postes à la conversation en cours (deliver_per_company), sans attendre la fin de toute la file ;
  • La première vague doit inclure first_batch_max_companies entreprises (par défaut 3), afin que l'utilisateur voie des résultats en quelques secondes ;
  • Limitez à max_companies_per_run entreprises maximum par exécution, et à max_per_company postes maximum par entreprise, pour éviter toute perte de contrôle ;
  • Après avoir diffusé un lot, si ask_before_long_tail est vrai, demandez : « Voulez-vous continuer à chercher des entreprises de niche comme les licornes IA / les entreprises verticales ? ». Si l'utilisateur dit « continuer », élargissez la recherche ; s'il dit « c'est suffisant », arrêtez immédiatement et organisez les résultats déjà obtenus ;
  • Les entreprises de la file d'attente qui n'ont pas été traitées sont conservées pour la prochaine exécution, sans être supprimées.

Les postes candidats doivent respecter data-contract.md : source_tier ne peut être que l'un des trois suivants : official, official_wechat, platform. Les pages d'index agrégées / les liens médiatiques seront rejetés directement par le script et ne serviront que d'« indices ». La production continue, élément par élément, est écrite (avec possibilité d'ajouts multiples) dans :

runtime/inbox/jobs-input.json

Il est interdit de deviner les dates de publication ou les dates limites ; si la page ne les indique pas clairement, écrivez unknown. Si une source échoue ou est vide, consignez-le fidèlement, sans contourner à plusieurs reprises les limites d'accès.

Étape 2 : Vérification officielle

Pour les postes découverts via des sources non officielles, recherchez en priorité la page de détail du site officiel de l'entreprise :

  • Si une page de détail officielle est trouvée : définissez-la comme source principale, et écrivez la page agrégée d'origine dans alternate_sources ;
  • Si seul le compte officiel WeChat de l'entreprise est trouvé : définissez le niveau de source sur official_wechat ;
  • Si aucune page officielle ne peut être trouvée : conservez le niveau de source réel, sans le déguiser en officiel ;
  • Si la page officielle indique clairement qu'elle est fermée, désactivée ou que le recrutement est arrêté : définissez source_status sur closed ;
  • Si la page permet encore de postuler : définissez source_status sur open ;
  • S'il est impossible de le déterminer : définissez source_status sur unknown.

Limitez la vérification approfondie à 5 nouvelles entreprises maximum par exécution, afin d'éviter une perte de contrôle des coûts.

Étape 3 : Correspondance, déduplication et détection des changements

Exécutez :

python3 "$SKILL_DIR/scripts/radar.py" run \
  --input "$SKILL_DIR/runtime/inbox/jobs-input.json" \
  --preferences "$SKILL_DIR/runtime/user-preferences.json" \
  --project-config "$SKILL_DIR/config/project.json" \
  --state-dir "$SKILL_DIR/runtime/state"

Le programme doit gérer :

  • La correspondance par promotion, poste, ville et type de recrutement ;
  • Les préférences d'entreprise et de secteur ne font que bonifier le classement ;
  • La déduplication intersource pour une même entreprise, même poste, même ville, même promotion et même type de recrutement ;
  • La priorité est donnée aux sources officielles, les sources moins fiables sont conservées comme preuves de secours ;
  • Comparer les champs stables pour identifier new, changed, seen ;
  • Afficher les changed_fields spécifiques ;
  • Marquer comme expired les postes dont la date limite est antérieure à la date d'exécution, ou dont la page officielle indique clairement qu'ils sont fermés ;
  • Les postes expirés sont archivés et ne sont pas transmis ;
  • fetched_at ne participe pas à l'évaluation des changements.

Étape 4 : Répondre directement dans la conversation

Le script génère par défaut une liste de conversation concise, contenant uniquement les postes nouveaux ou modifiés et non encore expirés. Renvoyez la sortie complète du script dans la conversation en cours, sans seulement indiquer le chemin du fichier à l'utilisateur et sans lui demander d'ouvrir un rapport.

Fichiers d'état internes :

runtime/state/
├── jobs.json
├── jobs.csv
├── seen.json
├── expired.json
├── rejected.json
├── daily/YYYY-MM-DD.md
└── outbox/YYYY-MM-DD.md

La liste de conversation doit inclure :

  • Entreprise ;
  • Titre du poste ;
  • Ville ;
  • Type de recrutement ;
  • Salaire ;
  • Date de publication ;
  • Date limite ;
  • Mots-clés du poste (mots-clés importants extraits des exigences) ;
  • Points forts du poste ;
  • Lien de candidature ;
  • Source d'information ;
  • Statut et champs modifiés.

daily/ est un instantané d'audit interne ; outbox/ est une copie interne de la liste de conversation actuelle. L'utilisateur n'a pas besoin d'ouvrir ces fichiers. Les postes seen, expired et les doublons du même lot ne doivent pas être renvoyés plusieurs fois.

Étape 5 : Après le traitement des résultats, demander si l'utilisateur souhaite activer les notifications de mise à jour

Après la première exécution, après avoir fourni la liste des postes à l'utilisateur, demandez : « Voulez-vous que je vous avertisse en cas d'ajout ou de modification de postes ? »

  • Si l'utilisateur refuse : définissez schedule.notify_on_update sur false (valeur par défaut), terminez, ne faites rien de plus ;
  • Si l'utilisateur accepte : définissez schedule.notify_on_update sur true, et planifiez les exécutions de battement suivantes conformément à l'étape 6.

L'utilisateur peut changer d'avis à tout moment : dire « je ne souhaite plus recevoir de notifications » remet notify_on_update à false ; même en cas de nouveaux postes ou de modifications, aucune notification active ne sera envoyée. Dire « réactiver les notifications » le remet à true. Il s'agit d'un interrupteur réversible, pas d'un réglage unique.

Étape 6 : Notifications de battement (uniquement lorsque notify_on_update est true)

Le principe du battement est simple : à intervalles réguliers, l'agent réexécute ce Skill (recollecte → déduplication → comparaison avec l'état historique).

  • Si cette exécution génère des postes « nouveaux » ou « modifiés », la liste est envoyée dans la conversation en cours ;
  • S'il n'y a aucun ajout ni modification, rien n'est envoyé, l'agent reste silencieux et ne dérange pas l'utilisateur.

Il repose uniquement sur une « réexécution + comparaison avec l'état historique », sans popup système, sans e-mail, sans webhook. « Qui déclenche la réexécution » se divise en deux niveaux, du général au renforcé :

6.1 Niveau par défaut (accessible à tous, zéro configuration, multiplateforme, multi-modèle)

Toute personne qui clone ce Skill utilise ce niveau par défaut, sans dépendre d'aucun minuteur :

  1. Rafraîchissement incrémental au démarrage de la session (c'est le « collecteur quotidien » universel) : lorsque ce Skill est activé dans une session et que notify_on_update est true, et qu'au moins un jour s'est écoulé depuis la dernière exécution, l'agent utilise automatiquement la recherche web intégrée du client hôte pour recollecter les entreprises surveillées par l'utilisateur (ex. ByteDance, Alibaba) → déduplication → comparaison avec l'état historique → envoie uniquement les nouveaux postes ou modifications à l'utilisateur, ou reste silencieux en l'absence de mise à jour. L'utilisateur n'a pas besoin de dire à chaque fois « vérifier », ni de configurer des commandes en arrière-plan. Étant donné que l'utilisateur ouvre son client IA tous les jours, l'action de « découvrir de nouveaux postes », qui nécessite une connexion réseau, se produit naturellement lors de la première conversation de la journée – c'est le seul moyen de collecte qui soit zéro configuration, zéro coût et non lié à un appareil pour tous les utilisateurs de GitHub.
  2. Déclenchement manuel par l'utilisateur : l'utilisateur peut dire à tout moment « vérifier / voir s'il y a du nouveau » pour relancer immédiatement.
  3. Ajoutez --heartbeat à la commande d'entrée quotidienne ; sans mise à jour, zéro sortie ; en cas de mise à jour, renvoie la liste :
python3 "$SKILL_DIR/scripts/run_daily.py" \
  --project-config "$SKILL_DIR/config/project.json" --heartbeat

Lorsque notify_on_update est true, run_daily.py inclut automatiquement le battement, sans paramètre supplémentaire.

6.2 Niveau renforcé optionnel : exécution planifiée par l'hôte / le système (« notification automatique à heures fixes chaque jour »)

Lorsque l'utilisateur souhaite explicitement une « notification automatique à heures fixes chaque jour », mettez-la en œuvre selon la priorité suivante, et expliquez clairement les coûts :

  1. Utilisez en priorité la capacité de planification ou de rappel intégrée à l'agent hôte / au client (si elle existe), qui réexécutera ce Skill à l'heure dite ; c'est la solution la plus simple et ne nécessite pas de toucher au système.
  2. Si l'hôte ne dispose pas de capacité de planification, utilisez le script multiplateforme scripts/schedule_task.py pour enregistrer une tâche planifiée native du système d'exploitation (macOS launchd / Linux cron / Planificateur de tâches Windows). Lors de l'exécution, il génère la configuration en fonction du système de l'utilisateur et de l'heure choisie, sans coder en dur les chemins, sans être lié à l'appareil du responsable :
# Prévisualiser (imprime par défaut, ne modifie pas le système, sûr) :
python3 "$SKILL_DIR/scripts/schedule_task.py" --action print --time 08:00
# Installer après confirmation de l'utilisateur (modifie la machine) :
python3 "$SKILL_DIR/scripts/schedule_task.py" --action install --time 08:00
# Voir / Désinstaller :
python3 "$SKILL_DIR/scripts/schedule_task.py" --action status
python3 "$SKILL_DIR/scripts/schedule_task.py" --action remove

Avant l'installation, il est impératif d'afficher la prévisualisation avec print et d'obtenir le consentement de l'utilisateur ; après consentement, écrivez l'heure choisie dans schedule.time et définissez schedule.enabled sur true.

Les deux limites réelles doivent être expliquées clairement à l'utilisateur (sans les cacher, sans les exagérer en « cloud 24h/24 ») :

  • L'ordinateur doit être allumé : la tâche planifiée système ne se déclenche que si l'ordinateur est allumé à l'heure prévue ; elle ne s'exécute pas en cas d'extinction ou de mise en veille, et ne se rattrape pas non plus.
  • La tâche planifiée elle-même ne « refait pas de recherche web pour trouver des postes » : run_daily.py par défaut ne retraite que les entrées existantes (déduplication, changement, archivage des expirations). Par conséquent, collector.command dans config/project.json doit rester vide par défaut et doit le rester (voir la command_note de ce champ). Ce n'est pas un défaut : la véritable découverte de nouveaux postes est assurée par le « rafraîchissement incrémental au démarrage de la session » du niveau 6.1, qui est zéro configuration et zéro coût pour tous les utilisateurs. Il n'existe pas de commande de recherche en arrière-plan « universelle, gratuite et exécutable par tous les utilisateurs de GitHub ». Par conséquent, n'essayez pas de distribuer un collector.command codé en dur pour permettre à la tâche planifiée de rechercher de nouveaux postes.
  1. Si l'hôte ne prend pas en charge la planification et que l'utilisateur ne souhaite pas utiliser de tâche système, revenez au niveau par défaut : dites à l'utilisateur « La prochaine fois que vous utiliserez l'assistant, il se mettra automatiquement à jour, ou dites simplement « vérifier » à tout moment », en ne faisant jamais passer une tâche système locale pour une notification automatique dans le cloud.

Validation

bash "$SKILL_DIR/scripts/validate_skill.sh"

La validation doit prouver :

  1. La première exécution génère de nouveaux postes ;
  2. La même entrée exécutée à nouveau n'entraîne pas de renvoi en double ;
  3. Les postes en double provenant de sources différentes ne conservent que la source la plus forte ;
  4. La modification d'un champ entraîne la sortie de changed et des champs modifiés spécifiques ;
  5. L'entrée modifiée exécutée à nouveau devient seen ;
  6. Les postes dont la date limite est clairement expirée ou passée entrent dans expired ;
  7. Les postes expirés n'apparaissent pas dans la liste de conversation ;
  8. La ville, le type de recrutement, la date de publication, la date limite, la source et les points forts sont inclus dans la liste de conversation ;
  9. La commande par défaut produit directement la liste de conversation, le JSON étant réservé aux tests automatisés.

Sécurité et authenticité

  • Accédez uniquement aux pages publiques ; ne récupérez pas de contenus nécessitant une connexion, internes ou non publics ;
  • Ne stockez pas de cookies, mots de passe, informations privées des recruteurs ;
  • Ne contournez pas les CAPTCHA, les protections anti-scraping ou les limites d'accès ;
  • Les dates inconnues sont écrites unknown, sans devinette ;
  • L'état interne et la liste de conversation conservent le nom de la source, son niveau, son lien et la date de récupération ;
  • Tous les postes doivent toujours être vérifiés sur le site officiel de recrutement de l'entreprise avant de postuler.

Skills associés