skillfed 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,32 @@
1
+ # Node
2
+ node_modules/
3
+ npm-debug.log*
4
+ *.tgz
5
+
6
+ # Python
7
+ __pycache__/
8
+ *.pyc
9
+ .venv/
10
+ dist/
11
+ build/
12
+ *.egg-info/
13
+
14
+ # Vendored payload — copied from integrations/claude-code/ by scripts/vendor-payload.mjs.
15
+ # Canonical source lives there; these are build-time copies, never hand-edited or committed.
16
+ installer/payload/
17
+ python-installer/src/skillfed/payload/
18
+
19
+ # Local env / secrets
20
+ .env
21
+ *.local
22
+
23
+ # Editor / OS junk
24
+ .DS_Store
25
+ Thumbs.db
26
+ ~$*
27
+
28
+ # Local Claude state (never part of the release)
29
+ .claude/
30
+
31
+ # Federation runtime sinks (tenant-session artifacts)
32
+ federation_data/
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.4
2
+ Name: skillfed
3
+ Version: 0.1.0
4
+ Summary: No-clone installer for Skill Federation — drops the curl-tier finder skill + /skillfed command into ~/.claude. Run via `uvx skillfed` or `pipx run skillfed`. Stdlib only, zero deps.
5
+ Project-URL: Homepage, https://github.com/skill-federation/skill-federation
6
+ Project-URL: Repository, https://github.com/skill-federation/skill-federation
7
+ License-Expression: MIT
8
+ Keywords: agent-skills,claude-code,installer,skill-federation,skillfed
9
+ Requires-Python: >=3.9
10
+ Description-Content-Type: text/markdown
11
+
12
+ # skillfed (Python installer)
13
+
14
+ No-clone installer for [Skill Federation](https://github.com/skill-federation/skill-federation).
15
+ Drops the curl-tier finder skill + `/skillfed` command into `~/.claude` (or `./.claude`).
16
+
17
+ ```bash
18
+ uvx skillfed # curl tier, user scope (~/.claude)
19
+ uvx skillfed --with-hook # + plan-approval nudge
20
+ uvx skillfed --with-npx # + register the npx -y skillfed-mcp MCP server
21
+ uvx skillfed --scope project # install into ./.claude
22
+
23
+ # or, with pipx:
24
+ pipx run skillfed
25
+ ```
26
+
27
+ Then **restart Claude Code** and run `/skillfed <what you're trying to do>`.
28
+
29
+ Stdlib only, zero dependencies. The runtime finder itself needs just `curl`. This package is one
30
+ of three install paths — see the project README for the curl bootstrap and `npx skillfed`.
31
+
32
+ ## Build (maintainers)
33
+
34
+ ```bash
35
+ node ../scripts/vendor-payload.mjs # vendor the 3 payload files into src/skillfed/payload/
36
+ python -m build # sdist + wheel in dist/
37
+ ```
@@ -0,0 +1,26 @@
1
+ # skillfed (Python installer)
2
+
3
+ No-clone installer for [Skill Federation](https://github.com/skill-federation/skill-federation).
4
+ Drops the curl-tier finder skill + `/skillfed` command into `~/.claude` (or `./.claude`).
5
+
6
+ ```bash
7
+ uvx skillfed # curl tier, user scope (~/.claude)
8
+ uvx skillfed --with-hook # + plan-approval nudge
9
+ uvx skillfed --with-npx # + register the npx -y skillfed-mcp MCP server
10
+ uvx skillfed --scope project # install into ./.claude
11
+
12
+ # or, with pipx:
13
+ pipx run skillfed
14
+ ```
15
+
16
+ Then **restart Claude Code** and run `/skillfed <what you're trying to do>`.
17
+
18
+ Stdlib only, zero dependencies. The runtime finder itself needs just `curl`. This package is one
19
+ of three install paths — see the project README for the curl bootstrap and `npx skillfed`.
20
+
21
+ ## Build (maintainers)
22
+
23
+ ```bash
24
+ node ../scripts/vendor-payload.mjs # vendor the 3 payload files into src/skillfed/payload/
25
+ python -m build # sdist + wheel in dist/
26
+ ```
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "skillfed"
7
+ version = "0.1.0"
8
+ description = "No-clone installer for Skill Federation — drops the curl-tier finder skill + /skillfed command into ~/.claude. Run via `uvx skillfed` or `pipx run skillfed`. Stdlib only, zero deps."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ keywords = ["skill-federation", "skillfed", "claude-code", "agent-skills", "installer"]
13
+ dependencies = []
14
+
15
+ [project.urls]
16
+ Homepage = "https://github.com/skill-federation/skill-federation"
17
+ Repository = "https://github.com/skill-federation/skill-federation"
18
+
19
+ [project.scripts]
20
+ skillfed = "skillfed.cli:main"
21
+
22
+ [tool.hatch.build.targets.wheel]
23
+ packages = ["src/skillfed"]
24
+
25
+ # The payload is vendored from integrations/claude-code/ by scripts/vendor-payload.mjs and is
26
+ # git-ignored, so hatchling's VCS-aware file selection would skip it. `artifacts` force-includes
27
+ # it into both the sdist and the wheel. Run `node scripts/vendor-payload.mjs` before building.
28
+ [tool.hatch.build]
29
+ artifacts = ["src/skillfed/payload/*"]
@@ -0,0 +1,3 @@
1
+ """Skill Federation — no-clone installer (Python tier, `uvx skillfed` / `pipx run skillfed`)."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,147 @@
1
+ """Skill Federation installer — Python tier (`uvx skillfed` / `pipx run skillfed`).
2
+
3
+ Same curl-tier install as install.sh / install.ps1 / `npx skillfed`, packaged for PyPI so the
4
+ smallest-dependency audience (CI, Python shops) can install with no clone and no Node:
5
+
6
+ uvx skillfed # curl tier, user scope (~/.claude)
7
+ uvx skillfed --with-hook # + plan-approval nudge (safe settings.json merge)
8
+ uvx skillfed --with-npx # + register the npx -y skillfed-mcp MCP server
9
+ uvx skillfed --scope project # install into ./.claude instead of ~/.claude
10
+
11
+ The 3 payload files are vendored into this package (src/skillfed/payload/) by
12
+ scripts/vendor-payload.mjs and shipped inside the wheel. When run from a source checkout before
13
+ vendoring, we fall back to the canonical copy under integrations/claude-code/. Stdlib only.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import argparse
19
+ import json
20
+ import os
21
+ import shutil
22
+ import sys
23
+ from importlib.resources import files
24
+ from pathlib import Path
25
+
26
+ ENDPOINT_DEFAULT = "https://qurini-skill-federation.hf.space"
27
+
28
+ # vendored filename -> (dest path under .claude, repo-relative clone-fallback path)
29
+ PAYLOAD = [
30
+ ("SKILL.md", ("skills", "skill-federation", "SKILL.md"),
31
+ ("skills", "skill-federation", "SKILL.md")),
32
+ ("plan_nudge.json", ("skills", "skill-federation", "plan_nudge.json"),
33
+ ("hooks", "plan_nudge.json")),
34
+ ("skillfed.md", ("commands", "skillfed.md"),
35
+ ("commands", "skillfed.md")),
36
+ ]
37
+
38
+
39
+ def _read_payload(name: str, repo_rel: tuple[str, ...]) -> bytes:
40
+ """Bytes of a payload file: the bundled copy if present, else the clone fallback."""
41
+ try:
42
+ # chained single-arg joinpath: multi-arg joinpath is 3.11+, this stays 3.9-safe
43
+ res = files("skillfed").joinpath("payload").joinpath(name)
44
+ if res.is_file():
45
+ return res.read_bytes()
46
+ except (FileNotFoundError, ModuleNotFoundError, OSError):
47
+ pass
48
+ # Clone fallback: <repo>/integrations/claude-code/<repo_rel>
49
+ # cli.py -> skillfed -> src -> python-installer -> <repo root>
50
+ repo = Path(__file__).resolve().parents[3]
51
+ cand = repo.joinpath("integrations", "claude-code", *repo_rel)
52
+ if cand.is_file():
53
+ return cand.read_bytes()
54
+ sys.exit(
55
+ f"error: payload '{name}' not found (bundled or in a clone). "
56
+ "Run scripts/vendor-payload.mjs, or install from the published package."
57
+ )
58
+
59
+
60
+ def _backup(path: Path) -> None:
61
+ if path.exists():
62
+ shutil.copyfile(path, path.with_name(path.name + ".bak"))
63
+ print(f" backed up -> {path}.bak")
64
+
65
+
66
+ def _read_json(path: Path) -> dict:
67
+ if path.exists() and path.stat().st_size > 0:
68
+ return json.loads(path.read_text(encoding="utf-8"))
69
+ return {}
70
+
71
+
72
+ def _write_json(obj: dict, path: Path) -> None:
73
+ path.write_text(json.dumps(obj, indent=2) + "\n", encoding="utf-8")
74
+
75
+
76
+ def main(argv: list[str] | None = None) -> int:
77
+ ap = argparse.ArgumentParser(prog="skillfed", description="Install the Skill Federation finder.")
78
+ ap.add_argument("--scope", choices=("user", "project"), default="user")
79
+ ap.add_argument("--target")
80
+ ap.add_argument("--with-hook", action="store_true")
81
+ ap.add_argument("--with-npx", action="store_true")
82
+ ap.add_argument("--endpoint", default=ENDPOINT_DEFAULT)
83
+ args = ap.parse_args(argv)
84
+
85
+ if args.target:
86
+ target = Path(args.target).resolve()
87
+ elif args.scope == "user":
88
+ target = Path.home() / ".claude"
89
+ else:
90
+ target = Path.cwd() / ".claude"
91
+
92
+ print("Skill Federation installer (uvx skillfed)")
93
+ print(f" target : {target} (scope={args.scope})")
94
+ print()
95
+
96
+ # ALWAYS: curl tier (skill + command) — plain file writes, works immediately.
97
+ skill_dir = target / "skills" / "skill-federation"
98
+ cmd_dir = target / "commands"
99
+ skill_dir.mkdir(parents=True, exist_ok=True)
100
+ cmd_dir.mkdir(parents=True, exist_ok=True)
101
+ for name, dest_parts, repo_rel in PAYLOAD:
102
+ (target.joinpath(*dest_parts)).write_bytes(_read_payload(name, repo_rel))
103
+ print("[curl] installed finder skill + /skillfed command (zero runtime)")
104
+
105
+ # --with-hook: register the plan-approval nudge (safe merge + backup, idempotent).
106
+ if args.with_hook:
107
+ nudge_abs = str(skill_dir / "plan_nudge.json").replace("\\", "/")
108
+ cmd = f'curl -s "file://{nudge_abs}"'
109
+ settings = target / "settings.json"
110
+ s = _read_json(settings)
111
+ ptu = s.setdefault("hooks", {}).setdefault("PostToolUse", [])
112
+ already = any(
113
+ "plan_nudge.json" in str(h.get("command", ""))
114
+ for e in ptu for h in e.get("hooks", [])
115
+ )
116
+ if already:
117
+ print("[hook] already registered; skipped")
118
+ else:
119
+ _backup(settings)
120
+ ptu.append({
121
+ "matcher": "ExitPlanMode",
122
+ "hooks": [{"type": "command", "command": cmd, "timeout": 20}],
123
+ })
124
+ _write_json(s, settings)
125
+ print("[hook] registered plan-approval nudge in settings.json")
126
+
127
+ # --with-npx: register the published Node MCP server (project-scoped .mcp.json).
128
+ if args.with_npx:
129
+ mcp = Path.cwd() / ".mcp.json"
130
+ m = _read_json(mcp)
131
+ _backup(mcp)
132
+ m.setdefault("mcpServers", {})["skillfed-mcp"] = {
133
+ "command": "npx",
134
+ "args": ["-y", "skillfed-mcp"],
135
+ "env": {"SKILLFED_ENDPOINT": args.endpoint},
136
+ }
137
+ _write_json(m, mcp)
138
+ print(f"[npx] registered Node MCP server -> {mcp} (npx -y skillfed-mcp)")
139
+
140
+ print()
141
+ print("Done. Restart Claude Code, then run: /skillfed <what you're trying to do>")
142
+ print(f"Endpoint: {args.endpoint} (override with $SKILLFED_ENDPOINT)")
143
+ return 0
144
+
145
+
146
+ if __name__ == "__main__":
147
+ raise SystemExit(main())
@@ -0,0 +1,185 @@
1
+ ---
2
+ name: skill-federation
3
+ description: Find vetted agent skills for the current task from a federated catalog, the privacy-preserving way. Use right after a plan is approved, when you hit a capability gap mid-task, or when the user asks to find/discover a skill ("/skillfed …", "is there a skill that…"). You generate an abstract wish-list (never the plan) and the federation matches it.
4
+ allowed-tools: Bash, Read, Write, Glob
5
+ ---
6
+
7
+ # Skill Federation — wish-list finder
8
+
9
+ Discover vetted agent skills **without ever sending the user's plan, brief, or work
10
+ across the boundary**. You reason about the *ideal* skills for the task, emit an
11
+ abstract wish-list, and the federation matches those wishes against its catalog.
12
+
13
+ > **Privacy floor (constitution Principle IV) — non-negotiable.** What leaves the machine is only
14
+ > the abstract wish — its one-line `description`, its ~4 paraphrased `formulations`, 1–5
15
+ > `keywords`, and its structured capability `sketch` (per [demand-sketch.md](demand-sketch.md)).
16
+ > The sketch's flattened terms ride **inside the search query on every search** (and on a miss the
17
+ > same sketch is the demand pointer). (The wish `name` is display-only and stays local; the search
18
+ > payload is the concatenated description + formulations + flattened sketch, plus keywords.) Every
19
+ > field stays at the "what skill should exist" abstraction. The plan, brief, outputs, file
20
+ > contents, and your reasoning trace MUST NOT appear in any description, formulation, keyword,
21
+ > sketch, or search payload. If you can't phrase a need without quoting the user's content,
22
+ > abstract it until you can.
23
+
24
+ ## When to use
25
+
26
+ - A plan was just approved (the plan-approval hook nudges you here).
27
+ - You're about to build something a packaged skill likely already does (PDF
28
+ extraction, market sizing, PR review, SQL reporting, resume tailoring, …).
29
+ - The user says "find a skill for…", "is there a skill that…", or runs `/skillfed`.
30
+
31
+ > **Don't skip silently.** If you conclude a plan needs no skill search (e.g. bespoke work on your
32
+ > own codebase), say so **explicitly** — name the capabilities you considered and why none needs a
33
+ > federated skill — and **confirm with the user** before proceeding without a search.
34
+
35
+ ## Backend — MCP tools if present, else `curl`
36
+
37
+ **If the `skillfed-mcp` MCP tools are available this session** (`find_skills`,
38
+ `get_skill_bundle`, `report_selection`, `emit_demand_pointer` — the optional Node/npx tier),
39
+ **use them**: they hit the same federation with typed, validated I/O and no shell-out. The
40
+ wish-list, selection, trust, and reporting logic below are identical — just call the tool
41
+ instead of the matching `curl` POST (`find_skills` ≙ `/search`, `get_skill_bundle` ≙ `/fetch`,
42
+ `report_selection` ≙ `/report_selection`, `emit_demand_pointer` ≙ `/report_demand`).
43
+
44
+ **Otherwise (the default), use `curl`** — it ships with Windows 10+ (`curl.exe`) and macOS
45
+ (`/usr/bin/curl`), so the finder needs **no Python, no Node, no install**. You run `curl`
46
+ through your shell (Bash) tool.
47
+
48
+ - **Endpoint**: use `$SKILLFED_ENDPOINT` if it's set, else default
49
+ `https://qurini-skill-federation.hf.space` (the keyless demo). Point it at our own
50
+ federation core later — the request/response shapes are unchanged.
51
+ - **Defaults**: `top_n` = 3 candidates per wish; ~4 paraphrases per wish.
52
+ - **Windows note**: in PowerShell, `curl` is an alias for `Invoke-WebRequest` — call
53
+ **`curl.exe`** explicitly. On macOS/Linux plain `curl` is fine.
54
+ - **Quoting-safe pattern**: write each JSON request body to a temp file and send it with
55
+ `--data-binary "@<file>"`, so no shell has to escape braces or quotes.
56
+
57
+ The federation operations below are one `curl` POST each — `/search`, `/fetch`,
58
+ `/report_selection`, `/report_demand`. (The endpoint also exposes `/report_outcome` for
59
+ post-use signals; that's out of scope for the finder.)
60
+
61
+ ## The flow (two hops; always user-approved before install)
62
+
63
+ ### Hop 1 — search
64
+
65
+ 1. **Form an expected-response sketch, then a wish-list.** For the task, imagine the
66
+ *ideal* skill(s): what each would do, its inputs/outputs, the key operations, and the
67
+ discriminative vocabulary its SKILL.md would contain. Emit that sketch as a real
68
+ `sketch` field on each wish (it powers the search query *and* becomes the demand
69
+ pointer on a miss — author it once, per [demand-sketch.md](demand-sketch.md)). Then
70
+ write **up to 10 wishes** — fewer is fine — each:
71
+ - `name`: short hypothetical skill name (display-only, stays local),
72
+ - `description`: **one line** for display only (the wish→match table) — abstract, no
73
+ plan specifics,
74
+ - `keywords`: **1–5 required** evidence terms the description omits but the target
75
+ skill's docs would contain (the discriminative subset of `sketch.domain_vocab`),
76
+ - `formulations`: **~4 paraphrases** of the description with *deliberately varied
77
+ vocabulary* (synonyms, alternate framings). The load-bearing recall field — a single
78
+ phrasing misses ~20% of the time; 4 concatenated paraphrases erase that (BM25 is
79
+ bag-of-words, so they form a robust term-union query). Keep each abstract; never
80
+ quote the plan/brief.
81
+ - `sketch`: the structured expected-response sketch — `purpose / inputs / outputs /
82
+ operations / domain_vocab / section_sketch / tags` (demand-sketch.md schema). Its
83
+ flattened term values are appended to the search query, so the single BM25 call sees
84
+ the full discriminative vocabulary a matching SKILL.md would contain (SIRA step iii),
85
+ not just the 1–5 keywords. Keep it terse and capability-level — never task data.
86
+
87
+ 2. **Search each wish with `curl` (`/search`).** For each wish, concatenate its
88
+ `description` + `formulations` + the flattened `sketch` term values into ONE
89
+ bag-of-words query string (BM25 is bag-of-words, so the concatenation is a robust
90
+ term-union — matches a K-request ensemble at 1/K the cost; the sketch supplies the rare,
91
+ discriminative vocabulary SIRA rewards). Flatten the sketch to its *values* only
92
+ (`domain_vocab`, `operations`, `inputs`, `outputs`, `purpose`, `section_sketch`, `tags`)
93
+ — never JSON keys or punctuation. Write the request body to a temp file and POST it:
94
+
95
+ ```bash
96
+ # body.json → { "tenant":"local",
97
+ # "wish":"<description + formulations + flattened sketch, space-joined>",
98
+ # "keywords":["1-5","evidence","terms"], "top_n":3 }
99
+ curl.exe -s --max-time 20 -X POST "$SKILLFED_ENDPOINT/search" \
100
+ -H "Content-Type: application/json" --data-binary "@body.json"
101
+ ```
102
+ Response per wish:
103
+ ```json
104
+ { "query_id":"q_…",
105
+ "candidates":[ { "skill_id":"…","name":"…","description":"…","score":0.27,
106
+ "trust":{"license":"MIT","license_class":"permissive","provenance":"verified","stars":null},
107
+ "source_url":"https://…" } ],
108
+ "confidence":0.59, "recommendation":"…" }
109
+ ```
110
+ Keep each wish's `query_id` (needed for selection reporting). Empty `candidates` →
111
+ demand case. Run the wishes in turn (≤10; each is <300 ms) — or issue them in parallel.
112
+
113
+ 3. **Drop already-installed skills.** Before showing matches, `Glob`
114
+ `~/.claude/skills/*/SKILL.md` and `./.claude/skills/*/SKILL.md`, read each skill's
115
+ frontmatter `name`, and remove any candidate whose name matches (normalize: lowercase,
116
+ non-alphanumerics → `-`). Don't re-recommend something the user already has.
117
+
118
+ ### Selection (your job — precision)
119
+
120
+ 4. **Agentic selection, per wish.** Each `/search` returns ≤k recall candidates; *you*
121
+ decide. For each wish: pick the single best candidate, or reject all. A high score
122
+ is not approval — judge fit against the actual need.
123
+ 5. **Surface trust BEFORE approval.** Show the user a wish→match table with each
124
+ candidate's `license_class` (permissive / copyleft / proprietary / review),
125
+ `provenance` (verified / unverified), `stars`, `source_url`, and a ⚠ for any
126
+ `security_flags`. Prefer permissive + verified; call out review/unverified ones.
127
+ **Never install without explicit user approval.**
128
+
129
+ ### Hop 2 — local resolution (local-first)
130
+
131
+ 6. For each approved match, check whether it's already installed at
132
+ `.claude/skills/<id>/` (existence check — that *is* "local search"). If present,
133
+ use the local copy as-is (local-first rule; a drifted local copy is personalization,
134
+ not corruption). If absent, fetch the bundle with `curl` (`/fetch`):
135
+ ```bash
136
+ # body.json → { "tenant":"local", "skill_id":"<skill_id>" }
137
+ curl.exe -s --max-time 20 -X POST "$SKILLFED_ENDPOINT/fetch" \
138
+ -H "Content-Type: application/json" --data-binary "@body.json"
139
+ # → { "skill_id","name","license","source_url", "body":"<full SKILL.md content>" }
140
+ # (an in-house bundle may instead return "files":{ "SKILL.md":…, … } — handle both)
141
+ ```
142
+ Write the returned `body` (or each `files` entry) to `.claude/skills/<id>/SKILL.md`
143
+ and record a `.federation.json` manifest (`skill_id`, `installed_at`, `source_url`/
144
+ `license` for OSS). Surface attribution at install.
145
+ 7. **Use or revise.** Run the installed skill. If it needs local adaptation for this
146
+ task, stage the change as a LOCAL update on the installed copy (drift) — never push
147
+ local edits back. A general improvement that isn't tenant-specific is a FEDERATED
148
+ suggestion instead. (Full reflection/suggestion chain is a later task; keep it light.)
149
+
150
+ ### Report outcomes — two complementary reports
151
+
152
+ 8. For every wish that **had candidates**, report the selection with `curl`
153
+ (`/report_selection`, per wish, with its `query_id`). `chosen` must be a **non-empty
154
+ string** — the selected id, or the literal `"None"` if you rejected every candidate:
155
+ ```bash
156
+ # body.json → { "tenant":"local", "query_id":"<query_id>",
157
+ # "chosen":"<id-or-'None'>", "rejected":["<id>","…"] }
158
+ curl.exe -s --max-time 20 -X POST "$SKILLFED_ENDPOINT/report_selection" \
159
+ -H "Content-Type: application/json" --data-binary "@body.json"
160
+ ```
161
+ `"chosen":"None"` records that the shown candidates were all wrong — a retrieval-quality
162
+ signal (not a substitute for the demand pointer below).
163
+ 9. For every wish that ended with **no skill installed** — search came back **empty**, OR you
164
+ **rejected every candidate** — record a demand pointer with `curl` (`/report_demand`).
165
+ `wish` is REQUIRED (the wish string you searched). Build the `sketch` **string** exactly per
166
+ **[demand-sketch.md](demand-sketch.md)** — a `"<query_id>: <minified-json>"` build spec
167
+ (it is a STRING, not an object; the endpoint's `sketch` field is a string):
168
+ ```bash
169
+ # body.json → { "tenant":"local",
170
+ # "wish":"<the description + formulations + flattened sketch you searched>",
171
+ # "sketch":"<query_id>: {\"purpose\":\"…\",\"inputs\":[…],\"outputs\":[…],\"operations\":[…],\"domain_vocab\":[…],\"section_sketch\":\"…\",\"tags\":[…],\"source\":\"unmatched_wish|all_rejected\"}" }
172
+ curl.exe -s --max-time 20 -X POST "$SKILLFED_ENDPOINT/report_demand" \
173
+ -H "Content-Type: application/json" --data-binary "@body.json"
174
+ ```
175
+ The two reports are **complementary**, not conflated: `report_selection` labels retrieval
176
+ quality (these candidates were shown); `report_demand` captures the capability gap (what was
177
+ actually needed). On **all-rejected** send BOTH; on **empty** send only the demand pointer.
178
+
179
+ ## Don't
180
+
181
+ - Don't put plan/brief/output text into any wish, keyword, sketch, or payload.
182
+ - Don't install without user approval, or re-recommend an already-installed skill.
183
+ - Don't author a demand sketch for a wish whose candidate you **accepted** (only on empty or all-rejected).
184
+ - Don't send `sketch` as a JSON object or `chosen` as null/empty — both are strings (see step 8–9).
185
+ - Don't treat candidates as authoritative — they're recall; you and the user decide.
@@ -0,0 +1 @@
1
+ {"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"A plan was just approved. Before executing it, run the **Skill Federation finder** (the skill-federation skill) to see whether vetted skills already exist:\n1. From the plan in your context, write a wish-list of UP TO 10 wishes — each a {name, description, keywords, formulations} for an ideal skill this plan would use (\"if every skill existed, which ≤10 would I reach for?\"), with 1–5 evidence keywords and ~4 vocabulary-varied paraphrases per wish. Do NOT send the plan anywhere — only the wishes cross.\n2. Search each wish with curl (POST $SKILLFED_ENDPOINT/search; default https://qurini-skill-federation.hf.space) and drop anything already in ~/.claude/skills or ./.claude/skills.\n3. Review the candidates per wish; select the best match or reject all; present matches with license/provenance/trust metadata and get the user's approval BEFORE installing anything (fetch via POST /fetch).\n4. Report two complementary outcomes: for a wish that had candidates → POST /report_selection with chosen = the picked id or the literal \"None\" if you rejected all; for a wish with NO skill installed (empty OR all-rejected) → POST /report_demand with the wish string + a sketch string built per demand-sketch.md.\nDo NOT skip the finder silently. If you judge that a skill search is unnecessary for this plan (e.g. bespoke work on your own codebase), state that explicitly and verbosely — list the capabilities you considered and why none needs an external skill — and ask the user to confirm before proceeding without a search."}}
@@ -0,0 +1,34 @@
1
+ ---
2
+ description: Find vetted agent skills for a task via the Skill Federation wish-list finder
3
+ ---
4
+
5
+ Run the **Skill Federation wish-list finder** (the `skill-federation` skill) for the
6
+ task below. Follow that skill's flow exactly:
7
+
8
+ 1. Sketch the ideal skill(s), then write a wish-list of **up to 10 wishes**, each
9
+ `{name, description, keywords, formulations, sketch}`: a one-line `description` (display),
10
+ **1–5 evidence keywords**, **~4 vocabulary-varied paraphrases** in `formulations` (the
11
+ load-bearing recall field), and a structured `sketch` (`purpose / inputs / outputs /
12
+ operations / domain_vocab / section_sketch / tags`, per `demand-sketch.md`) — author the
13
+ sketch once; it powers the search and, on a miss, becomes the demand pointer. Abstract
14
+ capability only — **never put the task's raw content, data, or outputs into any field**
15
+ (constitution Principle IV).
16
+ 2. Search each wish with `curl` (POST `$SKILLFED_ENDPOINT/search`, default endpoint
17
+ `https://qurini-skill-federation.hf.space`): concatenate each wish's
18
+ description + formulations + the flattened `sketch` values into the `wish` string, send
19
+ `keywords` and `top_n`. No Python, no Node — just `curl` (use `curl.exe` on Windows).
20
+ Then drop any candidate whose name matches a skill already in `~/.claude/skills` or
21
+ `./.claude/skills`.
22
+ 3. Per wish, select the best candidate or reject all. Present matches with trust
23
+ metadata (license class, provenance, stars, source, ⚠ flags) and get approval
24
+ **before** installing anything.
25
+ 4. On approval, fetch with `curl` (POST `/fetch`) + install the returned `body`/`files` under
26
+ `.claude/skills/<id>/` (local-first: use an existing local copy if present). Then report two
27
+ complementary outcomes: for any wish that had candidates → `curl` POST `/report_selection`
28
+ with `chosen` = the picked id, or the literal `"None"` if you rejected all; for any wish with
29
+ **no skill installed** (empty OR all-rejected) → `curl` POST `/report_demand` with the `wish`
30
+ string + a `sketch` string built per `demand-sketch.md`.
31
+
32
+ If the task below is empty, ask the user what capability they're looking for.
33
+
34
+ Task to search for: $ARGUMENTS