From 1386d092501ba6db66925c54f1aeb84604f9c7b6 Mon Sep 17 00:00:00 2001 From: Vardhman Gupta Date: Fri, 16 Oct 2026 14:16:09 +0530 Subject: [PATCH 1/3] feat(setup): first-run welcome guide for the utilities family (#1663) Adds a concise first-run welcome guide for the utilities skill family (list-skills, write-skill, optimize-skill, report-framework-issue, and skill-reconciler), following the pattern established in #1653. - Adds plugins/magpie-setup/skills/setup/setup_preflight/guides/utilities.md - Highlights the typical utilities flow and notes zero configuration needed - Links the guide from docs/utilities/README.md under Install & first runs - Supports personal layer reconciled state in render-preflight-fixtures.py - Adds case-10-utilities-first-run preflight-floor fixture and updates case-9 - Expands test_welcome.py test coverage and adds cross-platform symlink robustness --- docs/utilities/README.md | 6 +++ .../setup/setup_preflight/guides/utilities.md | 48 +++++++++++++++++++ .../skills/setup/setup_preflight/welcome.py | 12 +++++ tools/dev/render-preflight-fixtures.py | 11 +++++ tools/setup-preflight/tests/test_welcome.py | 17 +++++-- tools/skill-evals/README.md | 2 +- tools/skill-evals/evals/setup/README.md | 4 +- .../case-meta.json | 1 + .../case-10-utilities-first-run/expected.json | 1 + .../case-10-utilities-first-run/report.md | 37 ++++++++++++++ .../case-10-utilities-first-run/state.json | 23 +++++++++ .../case-9-first-magpie-run/report.md | 2 +- 12 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 plugins/magpie-setup/skills/setup/setup_preflight/guides/utilities.md create mode 100644 tools/skill-evals/evals/setup/preflight-floor/fixtures/case-10-utilities-first-run/case-meta.json create mode 100644 tools/skill-evals/evals/setup/preflight-floor/fixtures/case-10-utilities-first-run/expected.json create mode 100644 tools/skill-evals/evals/setup/preflight-floor/fixtures/case-10-utilities-first-run/report.md create mode 100644 tools/skill-evals/evals/setup/preflight-floor/fixtures/case-10-utilities-first-run/state.json diff --git a/docs/utilities/README.md b/docs/utilities/README.md index ec2363b74..0da97d6b4 100644 --- a/docs/utilities/README.md +++ b/docs/utilities/README.md @@ -55,6 +55,12 @@ New to Magpie? The [quick start](../quick-start.md) walks the whole path in one place โ€” install, the first `/magpie-setup:setup` run, and a recording of it happening โ€” plus the other agents and the secure-isolation setup to run next. +The first utilities skill you run opens with a one-minute +[welcome guide](../../plugins/magpie-setup/skills/setup/setup_preflight/guides/utilities.md): +the typical flow, what needs configuring, what to ask it, and how to make it yours. +Dismiss it, or hide it for good, from its last line; +*"show the utilities welcome again"* or `/magpie-setup:welcome-guide utilities` brings it back. + ### Before the first run diff --git a/plugins/magpie-setup/skills/setup/setup_preflight/guides/utilities.md b/plugins/magpie-setup/skills/setup/setup_preflight/guides/utilities.md new file mode 100644 index 000000000..bed2b6a99 --- /dev/null +++ b/plugins/magpie-setup/skills/setup/setup_preflight/guides/utilities.md @@ -0,0 +1,48 @@ + + +# ๐Ÿฆ Magpie here โ€” your skill-authoring sidekick + +A one-minute tour, then straight back to what you asked for. + +## ๐Ÿ”„ The typical flow + +1. ๐Ÿ“‹ **List** skills โ€” what is installed here and what is in the marketplace โ€” *"list skills"* +2. โœ๏ธ **Write** a new skill against framework conventions, with evals โ€” *"write a skill"* +3. โšก **Optimize** an existing skill for brevity and token efficiency โ€” *"optimize a skill"* +4. ๐Ÿ› **Report a framework issue** from where you hit it, after checking duplicates โ€” *"report an issue"* +5. ๐Ÿ” **Reconcile** skills across your repo and snapshot โ€” *"reconcile skills"* + +Nothing is written, modified, or posted without your OK. + +## โš™๏ธ Configure it + +Nothing here has to be configured: + +- ๐Ÿ› ๏ธ **Authoring & help** โ€” meta-skills work straight away in any directory or worktree +- ๐Ÿ“– **Optional project settings** โ€” reads `project.md` when present, otherwise uses built-in framework defaults + +Two ways to run Magpie when you are ready: personal install (`/magpie-setup:setup config`) or project adoption (`/magpie-setup:setup adopt`). + +## ๐Ÿ’ฌ Just ask + +Talk to Magpie in plain language: + +- *"what skills are installed here?"* +- *"write a skill that drafts PR release notes"* +- *"optimize the write-skill instructions"* +- *"file a bug about preflight caching"* +- *"reconcile my local skills against the snapshot"* + +## ๐ŸŽจ Make it yours + +Magpie ships default workflows and conventions. +Change them by describing the change: + +- *"from now on, always add a smoke test when writing a skill"* +- *"skip the marketplace check when listing skills"* + +Each becomes an override for the utility skill it changes. +Useful to everyone? *"Upstream this change to Magpie"* opens a generic `apache/magpie` PR for you to review first. + + diff --git a/plugins/magpie-setup/skills/setup/setup_preflight/welcome.py b/plugins/magpie-setup/skills/setup/setup_preflight/welcome.py index aeba7932a..eaac98d84 100644 --- a/plugins/magpie-setup/skills/setup/setup_preflight/welcome.py +++ b/plugins/magpie-setup/skills/setup/setup_preflight/welcome.py @@ -47,6 +47,7 @@ from __future__ import annotations import argparse +import contextlib import hashlib import json import os @@ -434,6 +435,14 @@ def scan_families(base: Path) -> dict[str, str]: if mirror.is_dir(): for link in sorted(mirror.iterdir()): target = link.resolve() + if not target.is_dir() and link.is_file(): + try: + rel = link.read_text(encoding="utf-8").strip() + cand = (link.parent / rel).resolve() + if cand.is_dir(): + target = cand + except OSError: + pass if target in by_dir: found.setdefault(f"magpie-{link.name}", by_dir[target]) return dict(sorted(found.items())) @@ -448,6 +457,9 @@ def render_constant(mapping: dict[str, str]) -> str: def main(argv: list[str] | None = None) -> int: + if hasattr(sys.stdout, "reconfigure"): + with contextlib.suppress(Exception): + sys.stdout.reconfigure(encoding="utf-8") parser = argparse.ArgumentParser(prog="setup_preflight.welcome", description=__doc__.split("\n")[0]) sub = parser.add_subparsers(dest="action", required=True) sub.add_parser("list", help="print the guides that ship, one per line") diff --git a/tools/dev/render-preflight-fixtures.py b/tools/dev/render-preflight-fixtures.py index 8012239bf..3501d52b0 100644 --- a/tools/dev/render-preflight-fixtures.py +++ b/tools/dev/render-preflight-fixtures.py @@ -31,6 +31,7 @@ on `PATH`). * `requires` โ€” `requires_config:` entries passed as `--requires`. * `skill`, `hash` โ€” the invoking skill's `name:` and `surface_hash:`. +* `reconciled` โ€” dict written to personal layer `reconciled.json`, or `null`. * `welcome` โ€” `true` to include the skill family's first-run welcome guide. Left out otherwise (`--no-welcome`), so a floor case is about the floor and not about which family its skill happens to belong to. @@ -73,6 +74,16 @@ def run_checker(state: dict[str, object], repo_root: Path, scratch: Path) -> str overrides.mkdir() (overrides / "project.md").write_text("# project\n", encoding="utf-8") + reconciled = state.get("reconciled") + if reconciled is not None: + if not isinstance(reconciled, dict): + raise ValueError("`reconciled` must be a dict or null") + personal = ( + (project / ".apache-magpie-local") if lock is not None else (project / ".git" / "apache-magpie") + ) + personal.mkdir(parents=True, exist_ok=True) + (personal / "reconciled.json").write_text(json.dumps(reconciled), encoding="utf-8") + listing = scratch / "plugins.json" plugins = state.get("plugins") if plugins == "unreadable": diff --git a/tools/setup-preflight/tests/test_welcome.py b/tools/setup-preflight/tests/test_welcome.py index 3974a4db4..d09e3fbfe 100644 --- a/tools/setup-preflight/tests/test_welcome.py +++ b/tools/setup-preflight/tests/test_welcome.py @@ -79,6 +79,14 @@ def test_the_family_guide_comes_on_the_next_run_not_the_same_one(project: Path, assert due(project) == [("general", "again")] +def test_the_utilities_family_guide_comes_on_the_next_run(project: Path, version) -> None: + assert due(project, "list-skills") == [("general", "first-run")] + welcome.record(project, "general", "seen") + assert due(project, "list-skills") == [("utilities", "first-run")] + welcome.record(project, "utilities", "seen") + assert due(project, "list-skills") == [("general", "again")] + + def test_at_most_one_guide_per_run(project: Path, version) -> None: for _ in range(8): found = welcome_findings(project, SECURITY_SKILL) @@ -376,11 +384,11 @@ def test_show_without_color_prints_the_markdown(capsys) -> None: def test_the_rules_section_and_both_guides_ship() -> None: assert welcome.SECTION in sections.available() - assert {"general", "security"} <= set(welcome.guides()) + assert {"general", "security", "utilities"} <= set(welcome.guides()) assert welcome.family_guides() == [g for g in welcome.guides() if g != "general"] -@pytest.mark.parametrize("name", ["general", "security"]) +@pytest.mark.parametrize("name", ["general", "security", "utilities"]) def test_each_guide_is_short_and_ends_with_the_way_back(name: str) -> None: text = welcome.guide(name) or "" assert not text.startswith(" + +There is no `.apache-magpie.lock`, no `.apache-magpie-overrides/` and no +`.apache-magpie-local/` at the repo root. The running agent is Claude Code +with magpie-setup 0.4.0 and magpie-utilities 0.4.0 installed. + +The user has already seen the initial general welcome guide on an earlier run. +The invoked skill's own frontmatter reads `name: list-skills` and +`surface_hash: sha256:2edc90a1b7f3440d`; it belongs to the utilities family. +This is the first time a utilities skill has run here. +The user asked it to "list skills". + +The pre-flight command printed: + +```json +{ + "findings": [ + { + "code": "welcome", + "facts": { + "family": "utilities", + "guide": "# \ud83d\udc26 Magpie here \u2014 your skill-authoring sidekick\n\nA one-minute tour, then straight back to what you asked for.\n\n## \ud83d\udd04 The typical flow\n\n1. \ud83d\udccb **List** skills \u2014 what is installed here and what is in the marketplace \u2014 *\"list skills\"*\n2. \u270d\ufe0f **Write** a new skill against framework conventions, with evals \u2014 *\"write a skill\"*\n3. \u26a1 **Optimize** an existing skill for brevity and token efficiency \u2014 *\"optimize a skill\"*\n4. \ud83d\udc1b **Report a framework issue** from where you hit it, after checking duplicates \u2014 *\"report an issue\"*\n5. \ud83d\udd01 **Reconcile** skills across your repo and snapshot \u2014 *\"reconcile skills\"*\n\nNothing is written, modified, or posted without your OK.\n\n## \u2699\ufe0f Configure it\n\nNothing here has to be configured:\n\n- \ud83d\udee0\ufe0f **Authoring & help** \u2014 meta-skills work straight away in any directory or worktree\n- \ud83d\udcd6 **Optional project settings** \u2014 reads `project.md` when present, otherwise uses built-in framework defaults\n\nTwo ways to run Magpie when you are ready: personal install (`/magpie-setup:setup config`) or project adoption (`/magpie-setup:setup adopt`).\n\n## \ud83d\udcac Just ask\n\nTalk to Magpie in plain language:\n\n- *\"what skills are installed here?\"*\n- *\"write a skill that drafts PR release notes\"*\n- *\"optimize the write-skill instructions\"*\n- *\"file a bug about preflight caching\"*\n- *\"reconcile my local skills against the snapshot\"*\n\n## \ud83c\udfa8 Make it yours\n\nMagpie ships default workflows and conventions.\nChange them by describing the change:\n\n- *\"from now on, always add a smoke test when writing a skill\"*\n- *\"skip the marketplace check when listing skills\"*\n\nEach becomes an override for the utility skill it changes.\nUseful to everyone? *\"Upstream this change to Magpie\"* opens a generic `apache/magpie` PR for you to review first.\n\n## \ud83d\udc4b About this guide\n\n- **Continue** \u2014 show it again next time (three times at most)\n- **Dismiss** \u2014 hide it until the next Magpie upgrade\n- **Don't show again** \u2014 hide it for good; say *\"show the utilities welcome again\"* to bring it back\n\nShow this again any time: `/magpie-setup:welcome-guide utilities`\n(`/magpie-setup-welcome-guide utilities` on a pinned snapshot).\n\n\ud83c\udf08 Prefer it in colour? Run this yourself \u2014 the `!` prefix runs it as a shell command:\n\n```text\n! PYTHONPATH=\".apache-magpie-local:$(git rev-parse --git-common-dir)/../.apache-magpie-local:$(git rev-parse --git-common-dir)/apache-magpie\" python3 -m setup_preflight.welcome show --family utilities --color=always\n```\n", + "max_shown": 3, + "reason": "first-run", + "shown": 0 + }, + "scope": "family", + "section": "welcome" + } + ], + "rules": { + "welcome": "## welcome \u2014 a first-run welcome guide is due\n\nA welcome guide is due on this run: `facts.family` names it\n(`general`, for the first Magpie skill this user runs here, or the skill's own family),\nand `facts.reason` says why (`first-run`, `again`, or `upgraded` after a dismissal).\n`facts.guide` is the guide. The checker shows at most one guide per run and has already picked it.\n\n1. **Show `facts.guide` to the user verbatim**, as Markdown, before the work they asked for.\n Do not summarise, reword, trim, or add to it, and substitute nothing in it:\n its placeholders (``, ``, ``, \u2026) are meant to be read as written.\n2. **Offer the three choices in one line**:\n **Continue** (show again next time) \u00b7 **Dismiss** (hide until the next Magpie upgrade) \u00b7 **Don't show again**.\n3. **Record `seen` at once**, which is what no answer means, then carry on with the user's request in the same turn.\n Never block the requested work on the guide, and never wait for an answer before starting it.\n4. **When the user picks Dismiss or Don't show again** \u2014 now or later in the session \u2014 record that choice;\n it replaces `seen`. Continue needs nothing further.\n\n```bash\nPYTHONPATH=\".apache-magpie-local:$(git rev-parse --git-common-dir)/../.apache-magpie-local:$(git rev-parse --git-common-dir)/apache-magpie\" \\\n python3 -m setup_preflight.welcome record --family --choice \n```\n\n`seen` brings the guide back on a later run, `facts.max_shown` recorded showings in all (`facts.shown` so far);\n`dismiss` hides it until the framework version changes; `never` hides it until re-enabled.\nEvery write merges into the personal layer's `reconciled.json` under `welcome.`, keeping every other key;\nnever write it by hand.\nIf the command exits non-zero, say nothing more about it and carry on.\n\nAny guide can be shown again on request with `/magpie-setup:welcome-guide []`,\nwhich also turns a hidden one back on." + }, + "verdict": "action" +} +``` diff --git a/tools/skill-evals/evals/setup/preflight-floor/fixtures/case-10-utilities-first-run/state.json b/tools/skill-evals/evals/setup/preflight-floor/fixtures/case-10-utilities-first-run/state.json new file mode 100644 index 000000000..45c879d2a --- /dev/null +++ b/tools/skill-evals/evals/setup/preflight-floor/fixtures/case-10-utilities-first-run/state.json @@ -0,0 +1,23 @@ +{ + "lock": null, + "plugins": [ + [ + "magpie-setup", + "0.4.0" + ], + [ + "magpie-utilities", + "0.4.0" + ] + ], + "reconciled": { + "welcome": { + "general": { + "choice": "seen", + "shown": 1 + } + } + }, + "skill": "list-skills", + "welcome": true +} diff --git a/tools/skill-evals/evals/setup/preflight-floor/fixtures/case-9-first-magpie-run/report.md b/tools/skill-evals/evals/setup/preflight-floor/fixtures/case-9-first-magpie-run/report.md index b6a3ce2ea..32584a3eb 100644 --- a/tools/skill-evals/evals/setup/preflight-floor/fixtures/case-9-first-magpie-run/report.md +++ b/tools/skill-evals/evals/setup/preflight-floor/fixtures/case-9-first-magpie-run/report.md @@ -20,7 +20,7 @@ The pre-flight command printed: "code": "welcome", "facts": { "family": "general", - "guide": "# \ud83d\udc26 Hello from Magpie \u2014 your project's maintainer sidekick\n\nOne minute, then straight back to what you asked for.\n\n## \ud83d\ude80 Getting started\n\n- \ud83d\udce6 **Install** from your agent's marketplace (Claude Code shown):\n `/plugin marketplace add apache/magpie`,\n then `/plugin install magpie-setup@apache-magpie` and one `/plugin install magpie-@apache-magpie` per family you want\n- \ud83d\ude4b **Configure it for yourself** \u2014 personal settings, nothing committed \u2014 `/magpie-setup:setup config`\n- \ud83d\udc65 **Adopt it for your project** \u2014 the configuration is committed to your repo and shared with every contributor \u2014\n `/magpie-setup:setup adopt` (a maintainers' decision)\n\nOn a pinned-snapshot install the commands are shorter: `/magpie-setup config`, `/magpie-setup adopt`.\n\n## \ud83d\udd04 Staying current\n\n- \u270b **By hand:** `claude plugin marketplace update apache-magpie`, then `claude plugin update @apache-magpie`\n- \ud83e\udd16 **Automatically (Claude Code):** `/plugin` \u2192 **Marketplaces** \u2192 **apache-magpie** \u2192 **Enable auto-update** \u2014\n it is off by default for third-party marketplaces like this one\n- \ud83e\udde9 Gemini CLI takes `--auto-update` at install time; Codex has no auto-update, so run `codex plugin marketplace upgrade`\n\nAfter an update, the next skill you run checks your configuration against it and proposes any fixes;\n`/magpie-setup:setup reconcile` checks everything at once\n(on a pinned snapshot, `/magpie-setup upgrade` does the update).\n\n## \ud83d\udcac Just ask\n\nTalk to Magpie in plain language \u2014 no syntax to learn.\nNothing is written, posted, or sent without your OK.\nInterrupt at any time, say no, or change your mind halfway through.\n\n## \ud83c\udfa8 You are the creator\n\nDescribe what you want a skill to do for you:\n\n- *\"from now on, always ask me before closing a stale issue\"*\n- *\"when you triage PRs, leave the bot-authored ones alone\"*\n\nMagpie records it as an override for that skill (`/magpie-setup:setup override `).\nWorth sharing? Say *\"upstream this change to Magpie\"* \u2014\nit makes the change generic and opens a PR to `apache/magpie` for you to review (`/magpie-setup:override-upstream`).\n\n## \ud83d\udcda More guides\n\nEach family can bring its own short guide, shown the first time you use it:\n\n- `security` \u2014 `/magpie-setup:welcome-guide security`\n\n## \ud83d\udc4b About this guide\n\n- **Continue** \u2014 show it again next time (three times at most)\n- **Dismiss** \u2014 hide it until the next Magpie upgrade\n- **Don't show again** \u2014 hide it for good; say *\"show the Magpie welcome again\"* to bring it back\n\nShow this again any time: `/magpie-setup:welcome-guide []`\n(`/magpie-setup-welcome-guide []` on a pinned snapshot).\n\n\ud83c\udf08 Prefer it in colour? Run this yourself \u2014 the `!` prefix runs it as a shell command:\n\n```text\n! PYTHONPATH=\".apache-magpie-local:$(git rev-parse --git-common-dir)/../.apache-magpie-local:$(git rev-parse --git-common-dir)/apache-magpie\" python3 -m setup_preflight.welcome show --family general --color=always\n```\n", + "guide": "# \ud83d\udc26 Hello from Magpie \u2014 your project's maintainer sidekick\n\nOne minute, then straight back to what you asked for.\n\n## \ud83d\ude80 Getting started\n\n- \ud83d\udce6 **Install** from your agent's marketplace (Claude Code shown):\n `/plugin marketplace add apache/magpie`,\n then `/plugin install magpie-setup@apache-magpie` and one `/plugin install magpie-@apache-magpie` per family you want\n- \ud83d\ude4b **Configure it for yourself** \u2014 personal settings, nothing committed \u2014 `/magpie-setup:setup config`\n- \ud83d\udc65 **Adopt it for your project** \u2014 the configuration is committed to your repo and shared with every contributor \u2014\n `/magpie-setup:setup adopt` (a maintainers' decision)\n\nOn a pinned-snapshot install the commands are shorter: `/magpie-setup config`, `/magpie-setup adopt`.\n\n## \ud83d\udd04 Staying current\n\n- \u270b **By hand:** `claude plugin marketplace update apache-magpie`, then `claude plugin update @apache-magpie`\n- \ud83e\udd16 **Automatically (Claude Code):** `/plugin` \u2192 **Marketplaces** \u2192 **apache-magpie** \u2192 **Enable auto-update** \u2014\n it is off by default for third-party marketplaces like this one\n- \ud83e\udde9 Gemini CLI takes `--auto-update` at install time; Codex has no auto-update, so run `codex plugin marketplace upgrade`\n\nAfter an update, the next skill you run checks your configuration against it and proposes any fixes;\n`/magpie-setup:setup reconcile` checks everything at once\n(on a pinned snapshot, `/magpie-setup upgrade` does the update).\n\n## \ud83d\udcac Just ask\n\nTalk to Magpie in plain language \u2014 no syntax to learn.\nNothing is written, posted, or sent without your OK.\nInterrupt at any time, say no, or change your mind halfway through.\n\n## \ud83c\udfa8 You are the creator\n\nDescribe what you want a skill to do for you:\n\n- *\"from now on, always ask me before closing a stale issue\"*\n- *\"when you triage PRs, leave the bot-authored ones alone\"*\n\nMagpie records it as an override for that skill (`/magpie-setup:setup override `).\nWorth sharing? Say *\"upstream this change to Magpie\"* \u2014\nit makes the change generic and opens a PR to `apache/magpie` for you to review (`/magpie-setup:override-upstream`).\n\n## \ud83d\udcda More guides\n\nEach family can bring its own short guide, shown the first time you use it:\n\n- `security` \u2014 `/magpie-setup:welcome-guide security`\n- `utilities` \u2014 `/magpie-setup:welcome-guide utilities`\n\n## \ud83d\udc4b About this guide\n\n- **Continue** \u2014 show it again next time (three times at most)\n- **Dismiss** \u2014 hide it until the next Magpie upgrade\n- **Don't show again** \u2014 hide it for good; say *\"show the Magpie welcome again\"* to bring it back\n\nShow this again any time: `/magpie-setup:welcome-guide []`\n(`/magpie-setup-welcome-guide []` on a pinned snapshot).\n\n\ud83c\udf08 Prefer it in colour? Run this yourself \u2014 the `!` prefix runs it as a shell command:\n\n```text\n! PYTHONPATH=\".apache-magpie-local:$(git rev-parse --git-common-dir)/../.apache-magpie-local:$(git rev-parse --git-common-dir)/apache-magpie\" python3 -m setup_preflight.welcome show --family general --color=always\n```\n", "max_shown": 3, "reason": "first-run", "shown": 0 From a779cde4925dcd140ff685f697517c38e94b5517 Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Fri, 16 Oct 2026 10:34:00 +0100 Subject: [PATCH 2/3] Describe list-skills and skill-reconciler accurately in the utilities guide list-skills indexes installed skills only, and skill-reconciler compares two near-duplicate skills rather than a repo against its snapshot. Also replaces the empty `except OSError: pass` flagged by CodeQL with `contextlib.suppress`, and re-renders the case-10 fixture. Generated-by: Claude Opus 5 --- .../skills/setup/setup_preflight/guides/utilities.md | 8 ++++---- .../magpie-setup/skills/setup/setup_preflight/welcome.py | 8 +++----- .../fixtures/case-10-utilities-first-run/report.md | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/plugins/magpie-setup/skills/setup/setup_preflight/guides/utilities.md b/plugins/magpie-setup/skills/setup/setup_preflight/guides/utilities.md index bed2b6a99..18b8bbebc 100644 --- a/plugins/magpie-setup/skills/setup/setup_preflight/guides/utilities.md +++ b/plugins/magpie-setup/skills/setup/setup_preflight/guides/utilities.md @@ -7,11 +7,11 @@ A one-minute tour, then straight back to what you asked for. ## ๐Ÿ”„ The typical flow -1. ๐Ÿ“‹ **List** skills โ€” what is installed here and what is in the marketplace โ€” *"list skills"* +1. ๐Ÿ“‹ **List** the skills installed here, grouped by family โ€” *"list skills"* 2. โœ๏ธ **Write** a new skill against framework conventions, with evals โ€” *"write a skill"* 3. โšก **Optimize** an existing skill for brevity and token efficiency โ€” *"optimize a skill"* 4. ๐Ÿ› **Report a framework issue** from where you hit it, after checking duplicates โ€” *"report an issue"* -5. ๐Ÿ” **Reconcile** skills across your repo and snapshot โ€” *"reconcile skills"* +5. ๐Ÿ” **Reconcile** two near-duplicate skills and classify where they drifted โ€” *"reconcile and "* Nothing is written, modified, or posted without your OK. @@ -32,7 +32,7 @@ Talk to Magpie in plain language: - *"write a skill that drafts PR release notes"* - *"optimize the write-skill instructions"* - *"file a bug about preflight caching"* -- *"reconcile my local skills against the snapshot"* +- *"have these two skill copies drifted?"* ## ๐ŸŽจ Make it yours @@ -40,7 +40,7 @@ Magpie ships default workflows and conventions. Change them by describing the change: - *"from now on, always add a smoke test when writing a skill"* -- *"skip the marketplace check when listing skills"* +- *"group the skill list by capability instead of family"* Each becomes an override for the utility skill it changes. Useful to everyone? *"Upstream this change to Magpie"* opens a generic `apache/magpie` PR for you to review first. diff --git a/plugins/magpie-setup/skills/setup/setup_preflight/welcome.py b/plugins/magpie-setup/skills/setup/setup_preflight/welcome.py index eaac98d84..ecd2e41d1 100644 --- a/plugins/magpie-setup/skills/setup/setup_preflight/welcome.py +++ b/plugins/magpie-setup/skills/setup/setup_preflight/welcome.py @@ -436,13 +436,11 @@ def scan_families(base: Path) -> dict[str, str]: for link in sorted(mirror.iterdir()): target = link.resolve() if not target.is_dir() and link.is_file(): - try: - rel = link.read_text(encoding="utf-8").strip() - cand = (link.parent / rel).resolve() + # A checkout without symlink support stores a mirror entry as a text file holding its target path. + with contextlib.suppress(OSError): + cand = (link.parent / link.read_text(encoding="utf-8").strip()).resolve() if cand.is_dir(): target = cand - except OSError: - pass if target in by_dir: found.setdefault(f"magpie-{link.name}", by_dir[target]) return dict(sorted(found.items())) diff --git a/tools/skill-evals/evals/setup/preflight-floor/fixtures/case-10-utilities-first-run/report.md b/tools/skill-evals/evals/setup/preflight-floor/fixtures/case-10-utilities-first-run/report.md index af4e2e488..eb36e7909 100644 --- a/tools/skill-evals/evals/setup/preflight-floor/fixtures/case-10-utilities-first-run/report.md +++ b/tools/skill-evals/evals/setup/preflight-floor/fixtures/case-10-utilities-first-run/report.md @@ -20,7 +20,7 @@ The pre-flight command printed: "code": "welcome", "facts": { "family": "utilities", - "guide": "# \ud83d\udc26 Magpie here \u2014 your skill-authoring sidekick\n\nA one-minute tour, then straight back to what you asked for.\n\n## \ud83d\udd04 The typical flow\n\n1. \ud83d\udccb **List** skills \u2014 what is installed here and what is in the marketplace \u2014 *\"list skills\"*\n2. \u270d\ufe0f **Write** a new skill against framework conventions, with evals \u2014 *\"write a skill\"*\n3. \u26a1 **Optimize** an existing skill for brevity and token efficiency \u2014 *\"optimize a skill\"*\n4. \ud83d\udc1b **Report a framework issue** from where you hit it, after checking duplicates \u2014 *\"report an issue\"*\n5. \ud83d\udd01 **Reconcile** skills across your repo and snapshot \u2014 *\"reconcile skills\"*\n\nNothing is written, modified, or posted without your OK.\n\n## \u2699\ufe0f Configure it\n\nNothing here has to be configured:\n\n- \ud83d\udee0\ufe0f **Authoring & help** \u2014 meta-skills work straight away in any directory or worktree\n- \ud83d\udcd6 **Optional project settings** \u2014 reads `project.md` when present, otherwise uses built-in framework defaults\n\nTwo ways to run Magpie when you are ready: personal install (`/magpie-setup:setup config`) or project adoption (`/magpie-setup:setup adopt`).\n\n## \ud83d\udcac Just ask\n\nTalk to Magpie in plain language:\n\n- *\"what skills are installed here?\"*\n- *\"write a skill that drafts PR release notes\"*\n- *\"optimize the write-skill instructions\"*\n- *\"file a bug about preflight caching\"*\n- *\"reconcile my local skills against the snapshot\"*\n\n## \ud83c\udfa8 Make it yours\n\nMagpie ships default workflows and conventions.\nChange them by describing the change:\n\n- *\"from now on, always add a smoke test when writing a skill\"*\n- *\"skip the marketplace check when listing skills\"*\n\nEach becomes an override for the utility skill it changes.\nUseful to everyone? *\"Upstream this change to Magpie\"* opens a generic `apache/magpie` PR for you to review first.\n\n## \ud83d\udc4b About this guide\n\n- **Continue** \u2014 show it again next time (three times at most)\n- **Dismiss** \u2014 hide it until the next Magpie upgrade\n- **Don't show again** \u2014 hide it for good; say *\"show the utilities welcome again\"* to bring it back\n\nShow this again any time: `/magpie-setup:welcome-guide utilities`\n(`/magpie-setup-welcome-guide utilities` on a pinned snapshot).\n\n\ud83c\udf08 Prefer it in colour? Run this yourself \u2014 the `!` prefix runs it as a shell command:\n\n```text\n! PYTHONPATH=\".apache-magpie-local:$(git rev-parse --git-common-dir)/../.apache-magpie-local:$(git rev-parse --git-common-dir)/apache-magpie\" python3 -m setup_preflight.welcome show --family utilities --color=always\n```\n", + "guide": "# \ud83d\udc26 Magpie here \u2014 your skill-authoring sidekick\n\nA one-minute tour, then straight back to what you asked for.\n\n## \ud83d\udd04 The typical flow\n\n1. \ud83d\udccb **List** the skills installed here, grouped by family \u2014 *\"list skills\"*\n2. \u270d\ufe0f **Write** a new skill against framework conventions, with evals \u2014 *\"write a skill\"*\n3. \u26a1 **Optimize** an existing skill for brevity and token efficiency \u2014 *\"optimize a skill\"*\n4. \ud83d\udc1b **Report a framework issue** from where you hit it, after checking duplicates \u2014 *\"report an issue\"*\n5. \ud83d\udd01 **Reconcile** two near-duplicate skills and classify where they drifted \u2014 *\"reconcile and \"*\n\nNothing is written, modified, or posted without your OK.\n\n## \u2699\ufe0f Configure it\n\nNothing here has to be configured:\n\n- \ud83d\udee0\ufe0f **Authoring & help** \u2014 meta-skills work straight away in any directory or worktree\n- \ud83d\udcd6 **Optional project settings** \u2014 reads `project.md` when present, otherwise uses built-in framework defaults\n\nTwo ways to run Magpie when you are ready: personal install (`/magpie-setup:setup config`) or project adoption (`/magpie-setup:setup adopt`).\n\n## \ud83d\udcac Just ask\n\nTalk to Magpie in plain language:\n\n- *\"what skills are installed here?\"*\n- *\"write a skill that drafts PR release notes\"*\n- *\"optimize the write-skill instructions\"*\n- *\"file a bug about preflight caching\"*\n- *\"have these two skill copies drifted?\"*\n\n## \ud83c\udfa8 Make it yours\n\nMagpie ships default workflows and conventions.\nChange them by describing the change:\n\n- *\"from now on, always add a smoke test when writing a skill\"*\n- *\"group the skill list by capability instead of family\"*\n\nEach becomes an override for the utility skill it changes.\nUseful to everyone? *\"Upstream this change to Magpie\"* opens a generic `apache/magpie` PR for you to review first.\n\n## \ud83d\udc4b About this guide\n\n- **Continue** \u2014 show it again next time (three times at most)\n- **Dismiss** \u2014 hide it until the next Magpie upgrade\n- **Don't show again** \u2014 hide it for good; say *\"show the utilities welcome again\"* to bring it back\n\nShow this again any time: `/magpie-setup:welcome-guide utilities`\n(`/magpie-setup-welcome-guide utilities` on a pinned snapshot).\n\n\ud83c\udf08 Prefer it in colour? Run this yourself \u2014 the `!` prefix runs it as a shell command:\n\n```text\n! PYTHONPATH=\".apache-magpie-local:$(git rev-parse --git-common-dir)/../.apache-magpie-local:$(git rev-parse --git-common-dir)/apache-magpie\" python3 -m setup_preflight.welcome show --family utilities --color=always\n```\n", "max_shown": 3, "reason": "first-run", "shown": 0 From e2c9a149c6f3b2efc801bd690b7b0cdd85ab97c9 Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Fri, 16 Oct 2026 10:58:06 +0100 Subject: [PATCH 3/3] Test the text-file mirror fallback and drop the unrelated stdout change scan_families() now has a test for a mirror entry checked out as a plain file holding its target path, as git writes it without symlink support, including an entry whose target does not exist. The stdout.reconfigure() in main() is unrelated to the utilities guide and is removed from this change. Generated-by: Claude Opus 5 --- .../skills/setup/setup_preflight/welcome.py | 3 --- tools/setup-preflight/tests/test_welcome.py | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/plugins/magpie-setup/skills/setup/setup_preflight/welcome.py b/plugins/magpie-setup/skills/setup/setup_preflight/welcome.py index ecd2e41d1..89bcd330d 100644 --- a/plugins/magpie-setup/skills/setup/setup_preflight/welcome.py +++ b/plugins/magpie-setup/skills/setup/setup_preflight/welcome.py @@ -455,9 +455,6 @@ def render_constant(mapping: dict[str, str]) -> str: def main(argv: list[str] | None = None) -> int: - if hasattr(sys.stdout, "reconfigure"): - with contextlib.suppress(Exception): - sys.stdout.reconfigure(encoding="utf-8") parser = argparse.ArgumentParser(prog="setup_preflight.welcome", description=__doc__.split("\n")[0]) sub = parser.add_subparsers(dest="action", required=True) sub.add_parser("list", help="print the guides that ship, one per line") diff --git a/tools/setup-preflight/tests/test_welcome.py b/tools/setup-preflight/tests/test_welcome.py index d09e3fbfe..4e0d2a77c 100644 --- a/tools/setup-preflight/tests/test_welcome.py +++ b/tools/setup-preflight/tests/test_welcome.py @@ -426,6 +426,24 @@ def test_list_prints_every_guide(capsys) -> None: assert capsys.readouterr().out.split() == welcome.guides() +def _plugin_skill(base: Path, name: str, family: str) -> Path: + skill = base / "plugins" / f"magpie-{family}" / "skills" / name + skill.mkdir(parents=True) + (skill / "SKILL.md").write_text(f"---\nname: {name}\nfamily: {family}\n---\n", encoding="utf-8") + return skill + + +def test_a_mirror_entry_checked_out_as_a_text_file_still_resolves(tmp_path: Path) -> None: + """Without symlink support git writes a link as a file holding its target path.""" + _plugin_skill(tmp_path, "list-skills", "utilities") + mirror = tmp_path / "skills" + mirror.mkdir() + (mirror / "list-skills").write_text("../plugins/magpie-utilities/skills/list-skills\n", encoding="utf-8") + (mirror / "stray").write_text("../plugins/magpie-utilities/skills/missing\n", encoding="utf-8") + + assert welcome.scan_families(tmp_path) == {"list-skills": "utilities", "magpie-list-skills": "utilities"} + + @pytest.mark.skipif(not (REPO_ROOT / "plugins").is_dir(), reason="needs the framework tree") def test_the_shipped_family_map_matches_the_framework_tree() -> None: """The prek hook keeps this in sync; a failure means it was bypassed."""