diff --git a/.github/ISSUE_TEMPLATE/01_skill_proposal.yml b/.github/ISSUE_TEMPLATE/01_skill_proposal.yml index c2bf40c..2e45ed7 100644 --- a/.github/ISSUE_TEMPLATE/01_skill_proposal.yml +++ b/.github/ISSUE_TEMPLATE/01_skill_proposal.yml @@ -1,5 +1,5 @@ name: "New Skill Proposal" -description: Propose a new capability for the Skillware registry. +description: Propose a new capability for the Skillware registry (not an upgrade to an existing skill). title: "[New Skill]: " labels: ["skill request", "enhancement"] body: @@ -7,14 +7,35 @@ body: attributes: value: | Thanks for helping expand the Skillware ecosystem! - Please review the [Contribution Guidelines](https://github.com/ARPAHLS/skillware/blob/main/CONTRIBUTING.md) first. + Please review the [Contribution Guidelines](https://github.com/ARPAHLS/skillware/blob/main/CONTRIBUTING.md) and start from [templates/python_skill/](https://github.com/ARPAHLS/skillware/tree/main/templates/python_skill). + + To improve an **existing** skill, use the **Skill Upgrade** template instead. - type: input id: skill_name attributes: - label: Skill Name - description: e.g. `finance/stock_checker` or `os/file_manager` - placeholder: category/skill_name + label: Skill ID + description: Full registry ID in `category/skill_name` form + placeholder: finance/stock_checker + validations: + required: true + + - type: dropdown + id: category + attributes: + label: Category + description: Pick an existing category or propose a new one in the description + options: + - compliance + - data_engineering + - defi + - dev_tools + - finance + - monitoring + - office + - optimization + - wellness + - "Propose new category (describe below)" validations: required: true @@ -39,15 +60,27 @@ body: required: true - type: dropdown - id: models + id: runtime attributes: - label: Targeted Models (if applicable) - description: Is this skill specific to a model or truly agnostic? + label: Target runtime + description: Which LLM runtimes must the skill support? options: - - "Model Agnostic (All)" - - "Gemini 2.0" - - "Claude 3.5" - - "Local LLaMA" - - "Other" + - Model agnostic (all supported adapters) + - Gemini + - Claude + - OpenAI + - DeepSeek + - Ollama (prompt mode) + - Other / mixed validations: required: true + + - type: textarea + id: env_vars + attributes: + label: External APIs & env vars (if any) + description: List API keys or data sources the skill needs + placeholder: | + ETHERSCAN_API_KEY — optional chain enrichment + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/02_bug_report.yml b/.github/ISSUE_TEMPLATE/02_bug_report.yml index db89da6..e9281d0 100644 --- a/.github/ISSUE_TEMPLATE/02_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/02_bug_report.yml @@ -6,24 +6,28 @@ body: - type: markdown attributes: value: | - Thanks for reporting an issue! + Thanks for reporting an issue! Search [open bugs](https://github.com/ARPAHLS/skillware/issues?q=is%3Aopen+label%3Abug) first. - type: dropdown id: component attributes: label: Affected Component options: - - "Core Framework (Loader/Env)" - - "Specific Skill (e.g. Wallet Screening)" - - "Documentation" - - "Examples" + - Core Framework (loader, env, base_skill, adapters) + - CLI (skillware list, test, examples, menu) + - Specific Skill + - Examples / agent loops + - Documentation + - Packaging / PyPI install + - Tests / CI + - Security validations: required: true - type: input id: skill_name attributes: - label: Skill Name (if applicable) + label: Skill ID (if applicable) placeholder: finance/wallet_screening validations: required: false @@ -44,10 +48,9 @@ body: label: Steps to Reproduce description: How do we trigger this bug? placeholder: | - 1. Loaded skill 'X' - 2. Configured Gemini model - 3. Sent prompt "..." - 4. Crash + 1. Loaded skill 'finance/wallet_screening' + 2. Ran examples/gemini_wallet_check.py + 3. Crash on first generate_content call validations: required: true @@ -55,17 +58,20 @@ body: id: logs attributes: label: Error Logs - description: Paste the traceback or error message. - render: python + description: Paste the traceback or error message (optional for Documentation bugs). + render: shell validations: - required: true + required: false - type: textarea id: environment attributes: label: Environment - description: OS, Python version, SDK versions. + description: OS, Python version, install method (pip vs editable clone), SDK versions. placeholder: | Windows 11 Python 3.12 + pip install skillware[gemini]==0.4.2 google-genai 1.0.0 + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/03_doc_fix.yml b/.github/ISSUE_TEMPLATE/03_doc_fix.yml index 2c458b7..dd522eb 100644 --- a/.github/ISSUE_TEMPLATE/03_doc_fix.yml +++ b/.github/ISSUE_TEMPLATE/03_doc_fix.yml @@ -1,18 +1,38 @@ name: "Documentation Fix" description: Help us improve the docs. title: "[Docs]: " -labels: ["documentation", "good first issue"] +labels: ["documentation"] body: + - type: markdown + attributes: + value: | + For runnable script or agent-loop fixes under `examples/`, prefer the **Examples** template. + + - type: dropdown + id: doc_type + attributes: + label: Type of fix + options: + - Typo or clarity + - Broken link + - Outdated API or CLI behavior + - Doc drift (catalog, examples index, agent loops) + - Other + validations: + required: true + - type: textarea id: description attributes: label: What needs to be fixed? - description: Typos, unclear instructions, or broken links? + description: Describe the problem and the expected correction. validations: required: true - type: input id: page attributes: - label: Affected Page - placeholder: docs/usage/gemini.md + label: Affected Page(s) + placeholder: docs/usage/cli.md + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/04_framework_feature.yml b/.github/ISSUE_TEMPLATE/04_framework_feature.yml index 369134b..fe0e9b4 100644 --- a/.github/ISSUE_TEMPLATE/04_framework_feature.yml +++ b/.github/ISSUE_TEMPLATE/04_framework_feature.yml @@ -1,13 +1,17 @@ name: "Framework Feature" -description: Propose a change to the core Skillware engine (Loader, Envs, adapters). +description: Propose a change to the core Skillware engine (loader, env, adapters). title: "[Feat]: " labels: ["enhancement", "core-framework"] body: - type: markdown attributes: value: | - Use this for changes to `skillware/core/` (e.g., new model adapters, loader logic, security). - For new **Skills**, please use the "New Skill Proposal" template. + Use this for changes to `skillware/core/` (loader, env, base_skill, model adapters). + + - **CLI** (`skillware/cli.py`, menu, `list` / `test` / `examples`) — use the **CLI** template + - **Packaging** (PyPI wheel, `pyproject.toml`, `MANIFEST.in`) — use **Packaging** or an RFC for large changes + - **New registry skills** — use **New Skill Proposal** + - **Upgrades to existing skills** — use **Skill Upgrade** - type: textarea id: description @@ -21,16 +25,24 @@ body: id: rationale attributes: label: Rationale - description: Why is this needed? (e.g. "To support OpenAI models") + description: Why is this needed? validations: required: true + - type: input + id: affected_paths + attributes: + label: Affected paths (optional) + placeholder: skillware/core/loader.py, tests/test_loader.py + validations: + required: false + - type: textarea id: implementation attributes: label: Implementation Idea description: Do you have a plan for how to implement this in Python? placeholder: | - I can add a `to_openai_tool` method in `loader.py` that maps... + Add a helper on SkillLoader that... validations: required: false diff --git a/.github/ISSUE_TEMPLATE/05_rfc.yml b/.github/ISSUE_TEMPLATE/05_rfc.yml index 611a219..ed18b75 100644 --- a/.github/ISSUE_TEMPLATE/05_rfc.yml +++ b/.github/ISSUE_TEMPLATE/05_rfc.yml @@ -6,10 +6,11 @@ body: - type: markdown attributes: value: | - An RFC (Request for Comments) is for proposals that are too big for a simple feature request. - Use this to discuss: - - Changing core standards (e.g. how `manifest.yaml` works) - - Major refactors + An RFC is for proposals too large for a simple feature request, for example: + + - Changing core standards (how `manifest.yaml` works) + - Major refactors or manifest contract changes + - Packaging strategy, trust model, or sandbox architecture - Adding support for entirely new platforms - type: textarea diff --git a/.github/ISSUE_TEMPLATE/06_cli.yml b/.github/ISSUE_TEMPLATE/06_cli.yml new file mode 100644 index 0000000..32b5a68 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/06_cli.yml @@ -0,0 +1,77 @@ +name: "CLI" +description: Report a bug or propose a change to the skillware command-line interface. +title: "[CLI]: " +labels: ["cli", "enhancement"] +body: + - type: markdown + attributes: + value: | + Scope: `skillware/cli.py`, `skillware/__main__.py`, and [docs/usage/cli.md](https://github.com/ARPAHLS/skillware/blob/main/docs/usage/cli.md). + + For loader/path resolution logic in `skillware/core/loader.py`, use **Framework Feature** or **Bug Report** (Core Framework). + + - type: dropdown + id: change_type + attributes: + label: Change type + options: + - Bug + - Enhancement + - Documentation only + validations: + required: true + + - type: dropdown + id: command + attributes: + label: Command or area + options: + - skillware (interactive menu) + - skillware list + - skillware test + - skillware examples + - skillware --help / --version + - python -m skillware + - Path resolution / SKILLWARE_SKILL_PATH + - Other + validations: + required: true + + - type: dropdown + id: install_method + attributes: + label: Install method + options: + - pip install skillware (PyPI) + - pip install -e ".[dev]" or ".[dev,all]" (editable clone) + - Both / unknown + validations: + required: true + + - type: textarea + id: description + attributes: + label: Description + description: Expected vs actual behavior, or the feature you want. + validations: + required: true + + - type: textarea + id: reproduction + attributes: + label: Steps to reproduce (bugs) + placeholder: | + skillware examples + Expected: ... + Actual: ... + validations: + required: false + + - type: textarea + id: environment + attributes: + label: Environment + placeholder: | + Windows 11, Python 3.12, skillware 0.4.2 + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/07_skill_upgrade.yml b/.github/ISSUE_TEMPLATE/07_skill_upgrade.yml new file mode 100644 index 0000000..3ff0a5b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/07_skill_upgrade.yml @@ -0,0 +1,59 @@ +name: "Skill Upgrade" +description: Propose an enhancement to an existing registry skill (not a new skill). +title: "[Skill Upgrade]: " +labels: ["skill upgrade", "enhancement"] +body: + - type: markdown + attributes: + value: | + Use this for **existing** skills under `skills///` — new actions, data updates, manifest version bumps, test expansion. + + For a **new** registry skill, use **New Skill Proposal** instead. + + - type: input + id: skill_id + attributes: + label: Skill ID + description: Full registry ID + placeholder: finance/wallet_screening + validations: + required: true + + - type: input + id: current_version + attributes: + label: Current manifest version + placeholder: "1.0.0" + validations: + required: false + + - type: textarea + id: description + attributes: + label: Proposed change + description: What should improve and why? + validations: + required: true + + - type: dropdown + id: breaking + attributes: + label: Breaking change? + options: + - "No — backward compatible" + - "Yes — describe migration in the issue body" + - Unsure + validations: + required: true + + - type: checkboxes + id: checklist + attributes: + label: Likely touch points + options: + - label: manifest.yaml version bump + - label: skill.py logic + - label: instructions.md + - label: test_skill.py + - label: docs/skills/.md and catalog row + - label: examples/*.py or examples/README.md diff --git a/.github/ISSUE_TEMPLATE/08_examples.yml b/.github/ISSUE_TEMPLATE/08_examples.yml new file mode 100644 index 0000000..1577dec --- /dev/null +++ b/.github/ISSUE_TEMPLATE/08_examples.yml @@ -0,0 +1,61 @@ +name: "Examples" +description: Runnable scripts, agent loops, or examples index under examples/. +title: "[Examples]: " +labels: ["examples"] +body: + - type: markdown + attributes: + value: | + Scope: `examples/*.py`, [examples/README.md](https://github.com/ARPAHLS/skillware/blob/main/examples/README.md), and provider agent-loop patterns. + + For prose-only doc fixes, use **Documentation Fix**. For loader/adapter behavior, use **Framework Feature** or **Bug Report**. + + - type: input + id: scripts + attributes: + label: Script path(s) + placeholder: examples/gemini_wallet_check.py + validations: + required: true + + - type: input + id: skill_id + attributes: + label: Skill ID (if applicable) + placeholder: finance/wallet_screening + validations: + required: false + + - type: dropdown + id: provider + attributes: + label: Provider / pattern + options: + - Gemini + - Claude + - OpenAI + - DeepSeek + - Ollama + - Local execute (no LLM loop) + - Multi-skill harness + - Other + validations: + required: true + + - type: textarea + id: description + attributes: + label: What needs to change? + description: e.g. manifest-derived tool dispatch, new runnable script, index row update + validations: + required: true + + - type: checkboxes + id: updates + attributes: + label: Related updates needed + options: + - label: examples/README.md index row + - label: docs/usage/agent_loops.md reference matrix + - label: docs/skills/.md Usage Examples + - label: CHANGELOG [Unreleased] diff --git a/.github/ISSUE_TEMPLATE/09_packaging.yml b/.github/ISSUE_TEMPLATE/09_packaging.yml new file mode 100644 index 0000000..e7da954 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/09_packaging.yml @@ -0,0 +1,37 @@ +name: "Packaging" +description: PyPI wheel, install extras, or MANIFEST.in changes. +title: "[Packaging]: " +labels: ["packaging", "core-framework"] +body: + - type: markdown + attributes: + value: | + Scope: `pyproject.toml`, `MANIFEST.in`, wheel/sdist contents, PyPI extras, install smoke tests. + + For runtime loader logic, use **Framework Feature**. For CI workflow changes only, label with `ci` when opening. + + - type: textarea + id: description + attributes: + label: Description + description: What packaging behavior should change? + validations: + required: true + + - type: textarea + id: rationale + attributes: + label: Rationale + description: Who is affected (pip users, editable installs, CI)? + validations: + required: true + + - type: textarea + id: acceptance + attributes: + label: Acceptance criteria + placeholder: | + - pip install skillware includes ... + - wheel smoke test passes + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..0223a4a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: true +contact_links: + - name: Contributing Guide + url: https://github.com/ARPAHLS/skillware/blob/main/CONTRIBUTING.md + about: Read before opening a pull request + - name: Good First Issues + url: https://github.com/ARPAHLS/skillware/issues?q=is%3Aopen+label%3A%22good+first+issue%22 + about: Low-risk entry points for new contributors + - name: Skill Library + url: https://github.com/ARPAHLS/skillware/blob/main/docs/skills/README.md + about: Browse registry skills and IDs diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index a2b2638..c07ec19 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,72 +1,61 @@ - - ## Description -### Type of Change (Matches Issue Templates) +### Type of Change -- [ ] **Skill Proposal**: New Skill (Contains `manifest.yaml`, `skill.py`, and `instructions.md`) -- [ ] **Bug Report Fix**: Non-breaking change which fixes an execution error or framework bug -- [ ] **Doc Fix**: Documentation Update -- [ ] **Framework Feature / RFC Updates**: Core Framework Update (Changes to `base_skill.py`, `loader.py`, etc.) +- [ ] **New Skill** — new registry bundle under `skills/` +- [ ] **Skill Upgrade** — changes to an existing skill under `skills/` +- [ ] **Bug Fix** — incorrect runtime or framework behavior +- [ ] **Documentation** — docs, README, CONTRIBUTING only +- [ ] **Framework Feature** — `skillware/core/` loader, env, adapters +- [ ] **CLI** — `skillware/cli.py`, `docs/usage/cli.md` +- [ ] **Examples** — `examples/*.py`, agent loops, `examples/README.md` +- [ ] **Packaging** — PyPI wheel, `pyproject.toml`, `MANIFEST.in` +- [ ] **RFC / meta** — templates, labels, CI, or large design doc ## Checklist (all PRs) -- [ ] My code follows the **Agent Code of Conduct**. -- [ ] I have run `python -m flake8 .`, `pytest skills/` (or `skillware test`), and `pytest tests/` locally (or the subset relevant to this change). -- [ ] `CHANGELOG.md` updated under `[Unreleased]` if this PR changes user-visible behavior. -- [ ] `examples/README.md` is updated if this PR adds, renames, or removes a runnable script under `examples/`. - -## New or updated skill (complete only if this PR adds or changes a skill under `skills/`) +- [ ] Linked GitHub issue (`Fixes #…` or `Refs #…`) +- [ ] Scope matches the issue — no unrelated refactors +- [ ] `python -m black --check .` and `flake8` pass locally (or CI-equivalent subset) +- [ ] `pytest skills/` and `pytest tests/` pass locally when relevant +- [ ] `CHANGELOG.md` updated under `[Unreleased]` when user-visible behavior changes +- [ ] `examples/README.md` updated if this PR adds, renames, or removes a runnable script +- [ ] Ran `pytest tests/test_registry_docs.py` when skills, examples index, or agent-loops matrix changed -Skip this section for framework-only, documentation-only, or other PRs that do not touch the skill registry. +## New or updated skill -### Bundle & metadata +Skip unless this PR adds or changes files under `skills/`. -- [ ] Skill lives at `skills///` (copied from `templates/python_skill/` or equivalent). -- [ ] `manifest.yaml` has `name`, `version`, `description`, valid `parameters`, and `constitution`. -- [ ] `manifest.yaml` includes `issuer` with real `name` and `email` (not template placeholders). -- [ ] Optional: `short_description` in manifest (~80 chars) for `skillware list`. -- [ ] Optional: `issuer.github` and `issuer.org` set when applicable. -- [ ] `requirements` and `env_vars` are documented when the skill needs them. +### Bundle and metadata -### Logic, cognition, and UI +- [ ] Skill at `skills///` (from `templates/python_skill/` or equivalent) +- [ ] `manifest.yaml`: `name` (full ID), `version`, `description`, `parameters`, `constitution`, real `issuer` +- [ ] Optional: `short_description`, `issuer.github`, `issuer.org`, `requirements`, `env_vars` -- [ ] `skill.py` is deterministic Python (no arbitrary LLM-generated code paths). -- [ ] `instructions.md` explains when and how to use the skill. -- [ ] `card.json` is present and its `issuer` matches `manifest.yaml` (`name` and `email` at minimum). +### Logic, cognition, tests -### Tests & loader +- [ ] Deterministic `skill.py` (no ad-hoc LLM-generated execution paths) +- [ ] `instructions.md` explains when and how to use the skill +- [ ] `card.json` issuer matches manifest when present +- [ ] `test_skill.py` covers execution and schema expectations +- [ ] `SkillLoader.load_skill("/")` succeeds (or deps documented) -- [ ] `test_skill.py` covers execution and schema expectations. -- [ ] `SkillLoader.load_skill("/")` succeeds (or missing deps are documented). +### Documentation and catalog -### Documentation & catalog +- [ ] `docs/skills/.md` and row in `docs/skills/README.md` +- [ ] Usage Examples for Gemini, Claude, OpenAI, DeepSeek, Ollama per [skill usage template](docs/usage/skill_usage_template.md) -- [ ] `docs/skills/.md` exists or is updated (ID, Issuer, usage). -- [ ] `docs/skills/README.md` lists the skill with ID and Issuer. - -## Constitution & Safety (if adding or modifying a skill) +## Constitution and safety (skills only) ## Related Issues - + + diff --git a/.github/labels.json b/.github/labels.json index b8dbae6..a7eabf4 100644 --- a/.github/labels.json +++ b/.github/labels.json @@ -1,52 +1,87 @@ [ { "name": "bug", - "color": "FFB7B2", + "color": "FFBCBC", "description": "Something isn't working." }, { - "name": "documentation", - "color": "97C1A9", - "description": "Improvements or additions to documentation." + "name": "security", + "color": "CCCCF0", + "description": "Security vulnerabilities, trust model, or constitution updates." + }, + { + "name": "packaging", + "color": "DBD3DC", + "description": "PyPI wheel, pyproject.toml, MANIFEST.in, or install extras." + }, + { + "name": "ci", + "color": "DBD3DC", + "description": "GitHub Actions workflows and CI configuration." + }, + { + "name": "testing", + "color": "DBD3DC", + "description": "pytest, doc-drift guards, or CI test coverage." + }, + { + "name": "skill request", + "color": "D4E4F1", + "description": "Request for a new capability to be added to the registry." + }, + { + "name": "core-framework", + "color": "79B6D8", + "description": "Changes to loader, env, base classes, or model adapters." + }, + { + "name": "cli", + "color": "79B6D8", + "description": "skillware CLI, interactive menu, or docs/usage/cli.md." }, { "name": "enhancement", - "color": "C7CEEA", + "color": "D4E4F1", "description": "New feature or request." }, + { + "name": "skill upgrade", + "color": "D4E4F1", + "description": "Enhance an existing registry skill (not a new skill)." + }, { "name": "good first issue", - "color": "E0BBE4", + "color": "D2FFAE", "description": "Good for newcomers." }, { "name": "help wanted", - "color": "957DAD", + "color": "D2FFAE", "description": "Extra attention is needed." }, { - "name": "skill request", - "color": "B5EAD7", - "description": "Request for a new capability to be added." + "name": "documentation", + "color": "F4ECC8", + "description": "Improvements or additions to documentation." }, { - "name": "core-framework", - "color": "FFDAC1", - "description": "Changes to loader, env, or base classes." + "name": "examples", + "color": "FADFD0", + "description": "Runnable scripts under examples/, agent loops, or examples index." }, { - "name": "security", - "color": "FFFFD1", - "description": "Security vulnerabilities or constitution updates." + "name": "discussion", + "color": "F4ECC8", + "description": "Open discussion for RFCs and design proposals." }, { "name": "question", - "color": "FF9AA2", + "color": "F4ECC8", "description": "Further information is requested." }, { "name": "wontfix", - "color": "D3D3D3", + "color": "F4ECC8", "description": "This will not be worked on." } -] \ No newline at end of file +] diff --git a/.github/scripts/sync_labels.py b/.github/scripts/sync_labels.py new file mode 100644 index 0000000..9f8011a --- /dev/null +++ b/.github/scripts/sync_labels.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +"""Sync GitHub repository labels from .github/labels.json.""" + +from __future__ import annotations + +import json +import os +import sys +import urllib.error +import urllib.parse +import urllib.request +from pathlib import Path +from typing import Any + +LABELS_FILE = Path(__file__).resolve().parent.parent / "labels.json" +API_ROOT = "https://api.github.com" + + +def _request( + method: str, + url: str, + token: str, + payload: dict[str, Any] | None = None, +) -> dict[str, Any] | None: + data = None + headers = { + "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {token}", + "X-GitHub-Api-Version": "2022-11-28", + } + if payload is not None: + data = json.dumps(payload).encode("utf-8") + headers["Content-Type"] = "application/json" + req = urllib.request.Request(url, data=data, headers=headers, method=method) + try: + with urllib.request.urlopen(req) as resp: + body = resp.read().decode("utf-8") + return json.loads(body) if body else None + except urllib.error.HTTPError as exc: + if exc.code == 404: + return None + detail = exc.read().decode("utf-8", errors="replace") + raise RuntimeError(f"{method} {url} failed ({exc.code}): {detail}") from exc + + +def _load_labels() -> list[dict[str, str]]: + raw = json.loads(LABELS_FILE.read_text(encoding="utf-8")) + if not isinstance(raw, list): + raise ValueError("labels.json must be a JSON array") + labels: list[dict[str, str]] = [] + seen: set[str] = set() + for entry in raw: + name = entry.get("name", "").strip() + color = entry.get("color", "").strip().lstrip("#").upper() + description = entry.get("description", "").strip() + if not name: + raise ValueError("Each label entry requires a name") + if len(color) != 6 or any(c not in "0123456789ABCDEF" for c in color): + raise ValueError(f"Label {name!r} has invalid color {color!r}") + if name in seen: + raise ValueError(f"Duplicate label name: {name!r}") + seen.add(name) + labels.append({"name": name, "color": color, "description": description}) + return labels + + +def sync_labels(dry_run: bool = False) -> int: + repo = os.environ.get("GITHUB_REPOSITORY", "").strip() + token = os.environ.get("GITHUB_TOKEN", "").strip() + + labels = _load_labels() + if dry_run: + for label in labels: + print(f"[dry-run] UPSERT {label['name']} #{label['color']}") + print(f"Label validation OK ({len(labels)} labels in labels.json).") + return 0 + + if not repo: + print("GITHUB_REPOSITORY is not set", file=sys.stderr) + return 1 + if not token: + print("GITHUB_TOKEN is not set", file=sys.stderr) + return 1 + + created = updated = unchanged = 0 + + for label in labels: + name = label["name"] + encoded = urllib.parse.quote(name, safe="") + url = f"{API_ROOT}/repos/{repo}/labels/{encoded}" + body = {"color": label["color"], "description": label["description"]} + existing = _request("GET", url, token) + + if existing is None: + create_url = f"{API_ROOT}/repos/{repo}/labels" + create_body = {"name": name, **body} + _request("POST", create_url, token, create_body) + print(f"CREATE {name} #{label['color']}") + created += 1 + continue + + needs_update = ( + existing.get("color", "").upper() != label["color"] + or existing.get("description", "") != label["description"] + ) + if needs_update: + _request("PATCH", url, token, body) + print(f"UPDATE {name} #{label['color']}") + updated += 1 + else: + unchanged += 1 + + print( + f"Label sync complete: {created} created, {updated} updated, " + f"{unchanged} unchanged ({len(labels)} defined in labels.json)." + ) + return 0 + + +if __name__ == "__main__": + dry = "--dry-run" in sys.argv + raise SystemExit(sync_labels(dry_run=dry)) diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml new file mode 100644 index 0000000..ae018cc --- /dev/null +++ b/.github/workflows/sync-labels.yml @@ -0,0 +1,29 @@ +name: Sync GitHub Labels + +on: + push: + branches: [main] + paths: + - ".github/labels.json" + - ".github/scripts/sync_labels.py" + - ".github/workflows/sync-labels.yml" + workflow_dispatch: + +permissions: + issues: write + contents: read + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Validate labels.json + run: python .github/scripts/sync_labels.py --dry-run + + - name: Sync labels to GitHub + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + run: python .github/scripts/sync_labels.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 99a56e6..3bb8d4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ Contributors add user-facing entries under `[Unreleased]` in the same PR. Mainta ## [Unreleased] +### Changed + +- **GitHub**: Overhauled issue templates (CLI, Skill Upgrade, Examples, Packaging), issue chooser `config.yml`, PR template, and label taxonomy with pastel colors; labels sync automatically from `.github/labels.json` via CI on merge to `main` (#227). + ## [0.4.2] - 2026-07-08 ### Changed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c4177f5..b6b897c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,12 +28,16 @@ Pick the path that matches your issue. Only the **skill** row requires the full | Type | What you change | Typical issue label | Before coding | Verify locally | | :--- | :--- | :--- | :--- | :--- | -| **New or updated skill** | `skills///`, `docs/skills/`, templates | Skill proposal, enhancement | Skill proposal or approved issue | Bundle test + `pytest tests/test_skill_issuer.py` (see [TESTING.md](docs/TESTING.md)) | -| **Documentation** | `docs/`, `README.md`, `CONTRIBUTING.md` | Documentation, good first issue | Doc issue or typo/fix issue | Links valid; tone consistent | -| **Core framework** | `skillware/core/`, `tests/` | Framework feature | Framework feature issue | `pytest tests/`; update usage docs if API changes | -| **Bug fix** | Paths named in issue | Bug report | Reproduction or failing test | Targeted test + full `pytest tests/` when touching shared code | -| **Good first issue** | Usually docs, tests, or small fixes | Good first issue | Read acceptance criteria literally | Checklist for underlying type above | -| **RFC / large change** | Architecture, manifest contract, loader | RFC | Maintainer discussion on issue | Per RFC scope | +| **New skill** | `skills///`, `docs/skills/`, templates | `skill request`, `enhancement` | New Skill Proposal or approved issue | Bundle test + `pytest tests/test_skill_issuer.py` (see [TESTING.md](docs/TESTING.md)) | +| **Skill upgrade** | Existing bundle under `skills/` | `skill upgrade`, `enhancement` | Skill Upgrade issue | Bundle test + catalog/docs as needed | +| **Documentation** | `docs/`, `README.md`, `CONTRIBUTING.md` | `documentation` | Documentation Fix issue | Links valid; tone consistent | +| **Core framework** | `skillware/core/`, framework `tests/` | `core-framework`, `enhancement` | Framework Feature issue | `pytest tests/`; update usage docs if API changes | +| **CLI** | `skillware/cli.py`, `docs/usage/cli.md` | `cli` | CLI issue | `pytest tests/test_cli.py` when relevant | +| **Examples** | `examples/*.py`, agent loops, examples index | `examples` | Examples issue | Script runs; `pytest tests/test_registry_docs.py` when index changes | +| **Packaging** | `pyproject.toml`, `MANIFEST.in`, wheel | `packaging` | Packaging issue | Build/install smoke per issue | +| **Bug fix** | Paths named in issue | `bug` | Bug Report | Reproduction or failing test | +| **Good first issue** | Usually docs, tests, or small fixes | `good first issue` | Read acceptance criteria literally | Checklist for underlying type above | +| **RFC / large change** | Architecture, manifest contract | `discussion`, `core-framework` | RFC issue | Per RFC scope | **Skills remain the primary contribution we expect**, but every type above should follow [Getting started](#getting-started), [Universal expectations](#universal-expectations), and [Pull request process](#pull-request-process). @@ -47,11 +51,18 @@ Check [existing issues](https://github.com/ARPAHLS/skillware/issues) before star | Intent | Issue template | | :--- | :--- | -| New capability in the registry | [Skill proposal](https://github.com/ARPAHLS/skillware/issues/new/choose) | -| Loader, adapters, `base_skill` | [Framework feature](https://github.com/ARPAHLS/skillware/issues/new/choose) | -| Incorrect behavior | [Bug report](https://github.com/ARPAHLS/skillware/issues/new/choose) | +| New capability in the registry | [New Skill Proposal](https://github.com/ARPAHLS/skillware/issues/new/choose) | +| Upgrade an existing skill | [Skill Upgrade](https://github.com/ARPAHLS/skillware/issues/new/choose) | +| Loader, adapters, `base_skill` | [Framework Feature](https://github.com/ARPAHLS/skillware/issues/new/choose) | +| CLI (`list`, `test`, `examples`, menu) | [CLI](https://github.com/ARPAHLS/skillware/issues/new/choose) | +| Runnable examples / agent loops | [Examples](https://github.com/ARPAHLS/skillware/issues/new/choose) | +| PyPI wheel / install packaging | [Packaging](https://github.com/ARPAHLS/skillware/issues/new/choose) | +| Docs only | [Documentation Fix](https://github.com/ARPAHLS/skillware/issues/new/choose) | +| Incorrect behavior | [Bug Report](https://github.com/ARPAHLS/skillware/issues/new/choose) | | Large or breaking design | [RFC](https://github.com/ARPAHLS/skillware/issues/new/choose) | +Issue chooser links: [CONTRIBUTING](CONTRIBUTING.md), [good first issues](https://github.com/ARPAHLS/skillware/issues?q=is%3Aopen+label%3A%22good+first+issue%22), [Skill Library](docs/skills/README.md). Labels are defined in [`.github/labels.json`](.github/labels.json) and synced automatically on merge to `main` (see [sync-labels workflow](.github/workflows/sync-labels.yml)). + Wait for maintainer feedback on non-trivial work before investing in a large PR. ### 2. Fork and clone @@ -374,6 +385,8 @@ Skill IDs follow `category/skill_name` and should match the path under `skills/` | [docs/skills/README.md](docs/skills/README.md) | Published skill catalog | | [templates/python_skill/](templates/python_skill/) | Boilerplate for new skills | | [Pull request template](.github/PULL_REQUEST_TEMPLATE.md) | PR checklist | +| [Issue templates](.github/ISSUE_TEMPLATE/) | Bug, docs, skills, CLI, examples, RFC chooser | +| [`.github/labels.json`](.github/labels.json) | Label names, colors, descriptions (synced via CI) | | [CHANGELOG.md](CHANGELOG.md) | Release history; contributors add under `[Unreleased]` | | [Security policy](SECURITY.md) | Reporting vulnerabilities | diff --git a/docs/TESTING.md b/docs/TESTING.md index 105775b..ecee51c 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -16,9 +16,12 @@ Tests fall into four layers: **bundle**, **framework**, **maintainer**, and **ex | `[all]` extra covers bundle-test runtime deps | Done | | CLI `skillware test` for bundle discovery | Done | | Doc-drift guards (`test_registry_docs.py`) | Done | +| GitHub label policy test (`test_github_labels.py`) | Done | Every pull request runs `black --check`, `flake8`, `pytest skills/`, and `pytest tests/`. Bundle tests gate merge the same as framework and maintainer tests. +When [`.github/labels.json`](../../.github/labels.json) changes on `main`, the [Sync GitHub Labels](../../.github/workflows/sync-labels.yml) workflow updates label colors and descriptions on the repository automatically — do not edit labels manually in the GitHub UI. + ## Quick Setup Install lint tools, pytest, and optional skill runtime deps in one go (matches GitHub Actions CI): @@ -147,6 +150,8 @@ python -m pytest tests/ That covers **skill bundle tests** under `skills/` and **framework + maintainer tests** under `tests/`. It does not run `examples/`. Do not add per-skill pip lines or hardcoded skill paths to `.github/workflows/ci.yml`. +Pushes to `main` that touch `.github/labels.json` also run [`.github/workflows/sync-labels.yml`](../../.github/workflows/sync-labels.yml) to upsert GitHub labels from the JSON file. + The `[all]` extra includes optional SDK groups plus registry skill runtime deps (`web3`, `fastembed`, `numpy`, …) so `pytest skills/` works after `pip install -e ".[dev,all]"`. When a skill adds new `manifest.yaml` `requirements`, add the same packages to the matching optional extra and to `[all]` in `pyproject.toml`. ### Local commands diff --git a/docs/contributing/ai_native_workflow.md b/docs/contributing/ai_native_workflow.md index fdddda2..d1c48e3 100644 --- a/docs/contributing/ai_native_workflow.md +++ b/docs/contributing/ai_native_workflow.md @@ -29,7 +29,7 @@ This document is written **for you**, the contributing agent. Read it before you ## How you should operate -1. **Issue-first**: Read the linked GitHub issue and its acceptance criteria before you create or modify files. If there is no issue, tell your operator to open one or confirm scope with maintainers. +1. **Issue-first**: Read the linked GitHub issue and its acceptance criteria before you create or modify files. If there is no issue, tell your operator to open one via the [issue chooser](https://github.com/ARPAHLS/skillware/issues/new/choose) (New Skill, Skill Upgrade, CLI, Examples, Framework Feature, Bug Report, Documentation Fix, RFC). Labels are defined in [`.github/labels.json`](../../.github/labels.json). 2. **Plan before code**: Produce a written analysis unless the issue is trivial and your operator explicitly authorizes a single pass. 3. **Scope discipline**: Change only what the issue requires. Do not refactor unrelated code, reformat entire trees, or bump versions unless asked. 4. **Determinism**: Skill logic is ordinary Python with predictable outputs. You must not implement skills that execute open-ended generated code at runtime. @@ -90,7 +90,10 @@ You must: | If the issue involves... | You must also inspect | | :--- | :--- | -| New or updated skill | `skills///`, `docs/skills/.md`, `docs/skills/README.md`, `templates/python_skill/`, `tests/test_skill_issuer.py`, and when documenting integration: `docs/usage/README.md`, [agent_loops.md](../usage/agent_loops.md), [skill_usage_template.md](../usage/skill_usage_template.md), matching `examples/*.py` if present, and a row in `examples/README.md` if a runnable script is added or renamed. Doc-drift guards in `tests/test_registry_docs.py` verify that `docs/skills/README.md`, `examples/README.md`, and `docs/usage/agent_loops.md` stay in sync with manifests and scripts on disk — these run automatically via `pytest tests/`. | +| New skill | `skills///`, `docs/skills/.md`, `docs/skills/README.md`, `templates/python_skill/`, `tests/test_skill_issuer.py`, and when documenting integration: `docs/usage/README.md`, [agent_loops.md](../usage/agent_loops.md), [skill_usage_template.md](../usage/skill_usage_template.md), matching `examples/*.py` if present, and a row in `examples/README.md` if a runnable script is added or renamed. Doc-drift guards in `tests/test_registry_docs.py` verify that `docs/skills/README.md`, `examples/README.md`, and `docs/usage/agent_loops.md` stay in sync with manifests and scripts on disk — these run automatically via `pytest tests/`. | +| Skill upgrade | Same paths as new skill, but only the existing skill ID from the issue; bump `manifest.yaml` version when behavior or schema changes | +| CLI | `skillware/cli.py`, `docs/usage/cli.md`, `tests/test_cli.py` | +| Examples | `examples/*.py`, `examples/README.md`, `docs/usage/agent_loops.md`; run `pytest tests/test_registry_docs.py` when the index or matrix changes | | Core framework | `skillware/core/`, `tests/test_loader.py`, `docs/usage/` | | Documentation only | `docs/`, `README.md`, `CONTRIBUTING.md`, inbound links; `examples/README.md` when the issue adds, renames, or removes runnable scripts under `examples/`; for skill catalog or provider integration work, also `docs/usage/` and `docs/skills/`. Run `pytest tests/test_registry_docs.py` to confirm catalog and examples docs still match manifests and scripts on disk. | | Release / user-visible change | Root [CHANGELOG.md](../../CHANGELOG.md) under `[Unreleased]` when behavior, CLI, skills, or user-facing docs change (maintainers cut version sections) | @@ -365,6 +368,7 @@ Run this internal dialogue before you hand off to your operator. - [API keys for skills](../usage/api_keys.md) — env setup (link from skill pages; do not duplicate) - [Agent Code of Conduct](../../CODE_OF_CONDUCT.md) - [Pull request template](../../.github/PULL_REQUEST_TEMPLATE.md) +- [Issue templates](../../.github/ISSUE_TEMPLATE/) and [labels.json](../../.github/labels.json) - [Skill library](../skills/README.md) --- diff --git a/tests/test_github_labels.py b/tests/test_github_labels.py new file mode 100644 index 0000000..e798276 --- /dev/null +++ b/tests/test_github_labels.py @@ -0,0 +1,64 @@ +"""Validate .github/labels.json matches repository label policy.""" + +import json +import re +from pathlib import Path + +LABELS_PATH = Path(__file__).resolve().parent.parent / ".github" / "labels.json" +HEX_COLOR = re.compile(r"^[0-9A-Fa-f]{6}$") + +REQUIRED_LABELS = { + "bug", + "documentation", + "enhancement", + "good first issue", + "help wanted", + "skill request", + "skill upgrade", + "core-framework", + "cli", + "examples", + "testing", + "packaging", + "ci", + "security", + "discussion", + "question", + "wontfix", +} + + +def _load_labels() -> list[dict]: + return json.loads(LABELS_PATH.read_text(encoding="utf-8")) + + +def test_labels_json_exists_and_is_array(): + labels = _load_labels() + assert isinstance(labels, list) + assert len(labels) >= len(REQUIRED_LABELS) + + +def test_required_labels_present(): + names = {entry["name"] for entry in _load_labels()} + missing = REQUIRED_LABELS - names + assert not missing, f"Missing labels in labels.json: {sorted(missing)}" + + +def test_label_entries_well_formed(): + names: set[str] = set() + for entry in _load_labels(): + name = entry.get("name") + color = entry.get("color", "") + description = entry.get("description", "") + assert name and isinstance(name, str) + assert name not in names, f"Duplicate label: {name}" + names.add(name) + assert HEX_COLOR.match(color), f"Bad color for {name}: {color!r}" + assert description and isinstance(description, str) + + +def test_rfc_template_labels_exist(): + """Issue template 05_rfc.yml references discussion and core-framework.""" + names = {entry["name"] for entry in _load_labels()} + assert "discussion" in names + assert "core-framework" in names