Skip to content

Commit 3ef0b1e

Browse files
committed
Refactor beginner, intermediate, advanced labels
Signed-off-by: DeathGun44 <krishnamewara841@gmail.com>
1 parent 93a8f44 commit 3ef0b1e

14 files changed

Lines changed: 316 additions & 34 deletions

.github/ISSUE_TEMPLATE/02-beginner-issue.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: Beginner Issue Template
22
description: Create a beginner issue for contributors ready to learn the codebase and own a small implementation.
3-
labels: ["beginner"]
3+
labels: ["skill: beginner"]
44
assignees: []
55
body:
66
- type: markdown
@@ -227,7 +227,7 @@ body:
227227
- [Windows Setup Guide](https://github.com/hiero-ledger/hiero-sdk-python/blob/main/docs/sdk_developers/setup_windows.md) — platform-specific installation steps
228228
- [Pylance guide](https://github.com/hiero-ledger/hiero-sdk-python/blob/main/docs/sdk_developers/pylance.md) — inline type checking during development
229229
- [Running examples](https://github.com/hiero-ledger/hiero-sdk-python/blob/main/docs/sdk_developers/examples.md)
230-
- [Browse closed beginner PRs](https://github.com/hiero-ledger/hiero-sdk-python/pulls?q=is%3Apr+is%3Amerged+label%3Abeginner) — see how others did it
230+
- [Browse closed beginner PRs](https://github.com/hiero-ledger/hiero-sdk-python/pulls?q=is%3Apr+is%3Amerged+label%3A%22skill%3A+beginner%22) — see how others did it
231231
232232
**References:**
233233
- [Hedera Protobufs](https://github.com/hashgraph/hedera-protobufs)

.github/ISSUE_TEMPLATE/03-intermediate-issue.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: Intermediate Issue Template
22
description: Create a well-documented issue for contributors ready to own larger tasks independently
3-
labels: ["intermediate"]
3+
labels: ["skill: intermediate"]
44
assignees: []
55
body:
66
- type: markdown
@@ -248,7 +248,7 @@ body:
248248
**Project references:**
249249
- [Project structure](https://github.com/hiero-ledger/hiero-sdk-python/blob/main/docs/sdk_developers/training/setup/project_structure.md)
250250
- [CONTRIBUTING.md](https://github.com/hiero-ledger/hiero-sdk-python/blob/main/CONTRIBUTING.md)
251-
- [Browse closed intermediate PRs](https://github.com/hiero-ledger/hiero-sdk-python/pulls?q=is%3Apr+is%3Amerged+label%3Aintermediate) — see how others did it
251+
- [Browse closed intermediate PRs](https://github.com/hiero-ledger/hiero-sdk-python/pulls?q=is%3Apr+is%3Amerged+label%3A%22skill%3A+intermediate%22) — see how others did it
252252
- [Pylance guide](https://github.com/hiero-ledger/hiero-sdk-python/blob/main/docs/sdk_developers/pylance.md)
253253
254254
**Protobuf references:**

.github/ISSUE_TEMPLATE/04-advanced-issue.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: Advanced Issue Template
22
description: For expert contributors tackling architectural, multi-module, or core-logic changes.
3-
labels: ["advanced"]
3+
labels: ["skill: advanced"]
44
assignees: []
55

66
body:
@@ -145,7 +145,7 @@ body:
145145
- [Transaction Lifecycle](https://github.com/hiero-ledger/hiero-sdk-python/blob/main/docs/sdk_developers/training/transaction_lifecycle.md)
146146
- [Executable Architecture](https://github.com/hiero-ledger/hiero-sdk-python/blob/main/docs/sdk_developers/training/executable.md)
147147
- [Hedera Protobufs (services)](https://github.com/hashgraph/hedera-protobufs/tree/main/services)
148-
- [Browse closed advanced PRs](https://github.com/hiero-ledger/hiero-sdk-python/pulls?q=is%3Apr+is%3Amerged+label%3Aadvanced) — see how others did it
148+
- [Browse closed advanced PRs](https://github.com/hiero-ledger/hiero-sdk-python/pulls?q=is%3Apr+is%3Amerged+label%3A%22skill%3A+advanced%22) — see how others did it
149149
150150
**🆘 Stuck?**
151151
- [Community Calls](https://zoom-lfx.platform.linuxfoundation.org/meetings/hiero?view=week)

.github/scripts/bot-advanced-check.sh

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ log() {
1212
# Configuration
1313
#######################################
1414
REQUIRED_INTERMEDIATE_COUNT=1
15+
INTERMEDIATE_LABEL="${INTERMEDIATE_LABEL:-skill: intermediate}"
1516

1617
#######################################
1718
# Validate required environment variables
@@ -67,20 +68,21 @@ get_intermediate_count() {
6768

6869
gh api graphql \
6970
-f query='
70-
query($owner: String!, $name: String!, $user: String!) {
71+
query($owner: String!, $name: String!, $user: String!, $labels: [String!]!) {
7172
repository(owner: $owner, name: $name) {
7273
intermediate: issues(
7374
first: 1
7475
states: CLOSED
75-
filterBy: { assignee: $user, labels: ["intermediate"] }
76+
filterBy: { assignee: $user, labels: $labels }
7677
) {
7778
totalCount
7879
}
7980
}
8081
}' \
8182
-f owner="$OWNER" \
8283
-f name="$NAME" \
83-
-f user="$user"
84+
-f user="$user" \
85+
-f "labels[]=$INTERMEDIATE_LABEL"
8486
}
8587

8688
#######################################
@@ -187,15 +189,17 @@ check_user() {
187189
###################################
188190
log "User @$user NOT qualified."
189191

190-
SUGGESTION="[intermediate issues](https://github.com/$REPO/labels/intermediate)"
192+
# URL-encode: jq @uri handles colons, spaces, and all special chars
193+
ENCODED_LABEL=$(printf '%s' "$INTERMEDIATE_LABEL" | jq -Rr @uri)
194+
SUGGESTION="[$INTERMEDIATE_LABEL issues](https://github.com/$REPO/labels/$ENCODED_LABEL)"
191195

192196
MSG="Hi @$user, I cannot assign you to this issue yet.
193197
194198
**Why?**
195199
Advanced issues involve high-risk changes to the core codebase and require prior experience in this repository.
196200
197201
**Requirement:**
198-
- Complete at least **$REQUIRED_INTERMEDIATE_COUNT** 'intermediate' issue (You have: **$INT_COUNT**)
202+
- Complete at least **$REQUIRED_INTERMEDIATE_COUNT** '$INTERMEDIATE_LABEL' issue (You have: **$INT_COUNT**)
199203
200204
Please check out our **$SUGGESTION** to build your experience first!
201205

.github/scripts/bot-beginner-assign-on-comment.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ Parameters:
5454
*/
5555

5656
const fs = require("fs");
57+
const { BEGINNER_LABEL } = require('./shared/labels.js');
5758

5859
const SPAM_LIST_PATH = ".github/spam-list.txt";
5960
const REQUIRED_GFI_COUNT = 1;
@@ -115,9 +116,10 @@ module.exports = async ({ github, context }) => {
115116
}
116117

117118
// 2. Label Check (Fix 2: Defensive Check)
118-
const hasBeginnerLabel = Array.isArray(issue.labels) && issue.labels.some((label) => label.name === "beginner");
119+
const beginnerLabel = BEGINNER_LABEL.toLowerCase();
120+
const hasBeginnerLabel = Array.isArray(issue.labels) && issue.labels.some((label) => label.name?.toLowerCase() === beginnerLabel);
119121
if (!hasBeginnerLabel) {
120-
console.log(`[Beginner Bot] Issue #${issue.number} does not have 'beginner' label. Exiting.`);
122+
console.log(`[Beginner Bot] Issue #${issue.number} does not have '${BEGINNER_LABEL}' label. Exiting.`);
121123
return;
122124
}
123125

@@ -261,7 +263,7 @@ Please try a GFI first, then come back — we’ll be happy to assign this! 😊
261263
owner: repo.owner.login,
262264
repo: repo.name,
263265
issue_number: issue.number,
264-
body: `👋 Hi @${commenter}, thanks for your interest! This issue is already assigned to @${currentAssignee}, but we'd love your help on another one. You can find more "beginner" issues [here](https://github.com/${repo.owner.login}/${repo.name}/issues?q=is%3Aissue+is%3Aopen+label%3Abeginner+no%3Aassignee).`,
266+
body: `👋 Hi @${commenter}, thanks for your interest! This issue is already assigned to @${currentAssignee}, but we'd love your help on another one. You can find more "${BEGINNER_LABEL}" issues [here](https://github.com/${repo.owner.login}/${repo.name}/issues?q=is%3Aissue+is%3Aopen+label%3A%22${encodeURIComponent(BEGINNER_LABEL)}%22+no%3Aassignee).`,
265267
});
266268
} catch (error) {
267269
console.error("[Beginner Bot] Failed to post already-assigned message:", {

.github/scripts/bot-intermediate-assignment.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
const shared = require('./shared/labels.js');
12
const COMMENT_MARKER = process.env.INTERMEDIATE_COMMENT_MARKER || '<!-- Intermediate Issue Guard -->';
2-
const INTERMEDIATE_LABEL = process.env.INTERMEDIATE_LABEL?.trim() || 'intermediate';
3-
const BEGINNER_LABEL = process.env.BEGINNER_LABEL?.trim() || 'beginner';
3+
const INTERMEDIATE_LABEL = process.env.INTERMEDIATE_LABEL?.trim() || shared.INTERMEDIATE_LABEL;
4+
const BEGINNER_LABEL = process.env.BEGINNER_LABEL?.trim() || shared.BEGINNER_LABEL;
45
const EXEMPT_PERMISSION_LEVELS = (process.env.INTERMEDIATE_EXEMPT_PERMISSIONS || 'admin,maintain,write,triage')
56
.split(',')
67
.map((entry) => entry.trim().toLowerCase())
@@ -61,7 +62,7 @@ async function countCompletedBeginnerIssues(github, owner, repo, username) {
6162
!isSafeSearchToken(owner) ||
6263
!isSafeSearchToken(repo) ||
6364
!isSafeSearchToken(username) ||
64-
!isSafeSearchToken(BEGINNER_LABEL)
65+
!shared.isSafeLabel(BEGINNER_LABEL)
6566
) {
6667
console.log('Invalid search inputs', {
6768
owner,
@@ -75,7 +76,7 @@ async function countCompletedBeginnerIssues(github, owner, repo, username) {
7576
try {
7677
const searchQuery = [
7778
`repo:${owner}/${repo}`,
78-
`label:${BEGINNER_LABEL}`,
79+
`label:"${BEGINNER_LABEL}"`,
7980
'is:issue',
8081
'is:closed',
8182
`assignee:${username}`,

.github/scripts/bot-next-issue-recommendation.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const { BEGINNER_LABEL, INTERMEDIATE_LABEL, ADVANCED_LABEL } = require('./shared/labels.js');
2+
13
const SUPPORTED_GFI_REPOS = [
24
'hiero-sdk-cpp',
35
'hiero-sdk-swift',
@@ -58,10 +60,10 @@ module.exports = async ({ github, context, core }) => {
5860

5961
// Determine issue difficulty level
6062
const difficultyLevels = {
61-
beginner: labelSet.has('beginner'),
63+
beginner: labelSet.has(BEGINNER_LABEL.toLowerCase()),
6264
goodFirstIssue: labelSet.has('good first issue'),
63-
intermediate: labelSet.has('intermediate'),
64-
advanced: labelSet.has('advanced'),
65+
intermediate: labelSet.has(INTERMEDIATE_LABEL.toLowerCase()),
66+
advanced: labelSet.has(ADVANCED_LABEL.toLowerCase()),
6567
};
6668

6769
// Skip if intermediate or advanced
@@ -80,7 +82,7 @@ module.exports = async ({ github, context, core }) => {
8082
let recommendedLabel = null;
8183
let isFallback = false;
8284

83-
recommendedIssues = await searchIssues(github, core, repoOwner, repoName, 'beginner');
85+
recommendedIssues = await searchIssues(github, core, repoOwner, repoName, BEGINNER_LABEL);
8486
recommendedLabel = 'Beginner';
8587

8688
if (recommendedIssues.length === 0) {

.github/scripts/coderabbit_plan_trigger.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Script to trigger CodeRabbit plan for intermediate and advanced issues
22

33
const CODERABBIT_MARKER = '<!-- CodeRabbit Plan Trigger -->';
4+
const { DIFFICULTY_LABELS } = require('./shared/labels.js');
45

56
async function triggerCodeRabbitPlan(github, owner, repo, issue, marker = CODERABBIT_MARKER, isDryRun = false) {
67
const comment = `${marker} @coderabbitai plan`;
@@ -32,13 +33,13 @@ async function triggerCodeRabbitPlan(github, owner, repo, issue, marker = CODERA
3233
}
3334

3435
function hasBeginnerOrHigherLabel(issue, label) {
35-
// Check if issue has beginner, intermediate or advanced label (case-insensitive)
36-
const allowed = ['beginner', 'intermediate', 'advanced'];
36+
// Check if issue has a difficulty label (case-insensitive)
37+
const allowed = new Set(DIFFICULTY_LABELS.map(d => d.toLowerCase()));
3738

38-
const hasAllowedLabel = issue.labels?.some(l => allowed.includes(l?.name?.toLowerCase()));
39+
const hasAllowedLabel = issue.labels?.some(l => allowed.has(l?.name?.toLowerCase()));
3940

40-
// Also check if newly added label is beginner/intermediate/advanced
41-
const isNewLabelAllowed = allowed.includes(label?.name?.toLowerCase());
41+
// Also check if newly added label is a difficulty label
42+
const isNewLabelAllowed = allowed.has(label?.name?.toLowerCase());
4243

4344
return hasAllowedLabel || isNewLabelAllowed;
4445
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/usr/bin/env bash
2+
# migrate-skill-labels.sh — One-time relabeling of existing open issues
3+
#
4+
# Renames bare difficulty labels (beginner, intermediate, advanced) to their
5+
# new "skill: *" equivalents. Run with DRY_RUN=true (default) to preview.
6+
#
7+
# Usage:
8+
# DRY_RUN=true bash .github/scripts/migrate-skill-labels.sh # preview
9+
# DRY_RUN=false bash .github/scripts/migrate-skill-labels.sh # execute
10+
#
11+
# NOTE: Requires bash 4+ for associative arrays (declare -A).
12+
# GitHub Actions runners use bash 5. macOS ships bash 3.2 by default —
13+
# use 'brew install bash' or rewrite LABEL_MAP as parallel arrays if
14+
# running locally on an unmodified Mac.
15+
#
16+
# IMPORTANT: Disable label-triggered workflows (bot-coderabbit-plan-trigger,
17+
# bot-advanced-check) BEFORE running this script to avoid webhook spam.
18+
set -euo pipefail
19+
20+
if (( BASH_VERSINFO[0] < 4 )); then
21+
echo "ERROR: bash 4+ required (found $BASH_VERSION). See script header for details."
22+
exit 1
23+
fi
24+
25+
DRY_RUN="${DRY_RUN:-true}"
26+
REPO="${REPO:-$(gh repo view --json nameWithOwner -q .nameWithOwner)}"
27+
28+
echo "Migration script for skill labels"
29+
echo " Repo: $REPO"
30+
echo " Dry run: $DRY_RUN"
31+
echo ""
32+
33+
declare -A LABEL_MAP=(
34+
["beginner"]="skill: beginner"
35+
["intermediate"]="skill: intermediate"
36+
["advanced"]="skill: advanced"
37+
)
38+
39+
# Ensure target labels exist (no-op if already present).
40+
if [[ "$DRY_RUN" != "true" ]]; then
41+
echo "Ensuring target labels exist..."
42+
for new_label in "${LABEL_MAP[@]}"; do
43+
gh label create "$new_label" --repo "$REPO" --force 2>/dev/null || true
44+
done
45+
echo ""
46+
fi
47+
48+
failures=0
49+
50+
for old_label in "${!LABEL_MAP[@]}"; do
51+
new_label="${LABEL_MAP[$old_label]}"
52+
echo "=== Migrating '$old_label' → '$new_label' ==="
53+
54+
if ! issues=$(gh issue list --repo "$REPO" --label "$old_label" --state open --limit 1000 --search "is:issue" --json number -q '.[].number' 2>&1); then
55+
echo " ❌ ERROR: gh issue list failed for '$old_label': $issues"
56+
failures=$((failures + 1))
57+
continue
58+
fi
59+
60+
if [[ -z "$issues" ]]; then
61+
echo " Found 0 open issue(s) with label '$old_label'"
62+
continue
63+
fi
64+
65+
count=$(echo "$issues" | wc -l)
66+
echo " Found $count open issue(s) with label '$old_label'"
67+
68+
for num in $issues; do
69+
echo " Issue #$num"
70+
if [[ "$DRY_RUN" == "true" ]]; then
71+
echo " [dry-run] Would add '$new_label' and remove '$old_label'"
72+
else
73+
if ! gh issue edit "$num" --repo "$REPO" --add-label "$new_label" --remove-label "$old_label"; then
74+
echo " ❌ Failed to migrate issue #$num"
75+
failures=$((failures + 1))
76+
else
77+
echo " ✅ Migrated"
78+
fi
79+
fi
80+
done
81+
done
82+
83+
if (( failures > 0 )); then
84+
echo ""
85+
echo "⚠️ $failures error(s) occurred during migration. Review output above."
86+
exit 1
87+
fi
88+
echo ""
89+
echo "✅ Migration complete."

.github/scripts/shared/labels.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* Centralized label configuration — single source of truth for difficulty labels.
3+
*
4+
* All JavaScript scripts that reference difficulty labels should import
5+
* constants from this module instead of hardcoding strings.
6+
*
7+
* To rename labels in the future, update the defaults below and the
8+
* corresponding workflow YAML `if:` conditions (which cannot import JS).
9+
*
10+
* Environment variable overrides are supported for per-workflow customization:
11+
* BEGINNER_LABEL, INTERMEDIATE_LABEL, ADVANCED_LABEL
12+
*/
13+
14+
const BEGINNER_LABEL = process.env.BEGINNER_LABEL?.trim() || 'skill: beginner';
15+
const INTERMEDIATE_LABEL = process.env.INTERMEDIATE_LABEL?.trim() || 'skill: intermediate';
16+
const ADVANCED_LABEL = process.env.ADVANCED_LABEL?.trim() || 'skill: advanced';
17+
18+
/**
19+
* All difficulty labels as an array, for scripts that need to check
20+
* whether an issue has *any* difficulty label (e.g. coderabbit_plan_trigger).
21+
*/
22+
const DIFFICULTY_LABELS = [BEGINNER_LABEL, INTERMEDIATE_LABEL, ADVANCED_LABEL];
23+
24+
/**
25+
* Validates a label string for safe use in API queries.
26+
*
27+
* Allows letters, digits, and the characters: . _ / : space -
28+
* This is intentionally looser than `isSafeSearchToken` (which rejects
29+
* colons and spaces) because labels like "skill: beginner" are valid.
30+
*
31+
* The hyphen is escaped (\-) to prevent accidental ASCII range creation
32+
* if a future developer appends characters to the character class.
33+
*
34+
* @param {string} value - The label string to validate.
35+
* @returns {boolean} True if the value is safe for use in queries.
36+
*/
37+
function isSafeLabel(value) {
38+
return typeof value === 'string' && value.length > 0 && /^[a-zA-Z0-9._/: \-]+$/.test(value);
39+
}
40+
41+
module.exports = {
42+
BEGINNER_LABEL,
43+
INTERMEDIATE_LABEL,
44+
ADVANCED_LABEL,
45+
DIFFICULTY_LABELS,
46+
isSafeLabel,
47+
};

0 commit comments

Comments
 (0)