diff --git a/.agents/skills/lfx-install/SKILL.md b/.agents/skills/lfx-install/SKILL.md new file mode 100644 index 0000000..45baf78 --- /dev/null +++ b/.agents/skills/lfx-install/SKILL.md @@ -0,0 +1,183 @@ +--- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +name: lfx-install +description: > + Install or set up LFX Skills for agents.md-compatible tools via the + lfx-skills CLI, and point Claude Code-only users to the Claude plugin path. + Walks through the choices in plain language: where their LFX repos live, + scope, agents config dirs, then runs the installer and verifies. Use + whenever the user says "I just cloned this — what now?", "set up lfx skills", + "install lfx skills", "I'm new to lfx-skills", or "first-time setup". +allowed-tools: Bash, Read, Glob, Grep, AskUserQuestion +--- + + + +# LFX Skills Install + +You guide the user through their first-time install. The bash CLI (`cli/lfx-skills install`) can do this non-interactively if every flag is supplied; this skill is the conversational layer that figures out what those flags should be by asking the user, in plain language, one question at a time. + +## Step 1: Verify you're in the clone + +This skill only works inside the `lfx-skills` clone. Verify: + +```bash +[ -x ./cli/lfx-skills ] && echo OK || echo NOT_IN_CLONE +``` + +If `NOT_IN_CLONE`, tell the user: + +> "I only run inside the lfx-skills clone. `cd` to your clone of `linuxfoundation/lfx-skills` and ask again." + +Stop. + +## Step 2: Probe the system + +Run `./cli/lfx-skills` indirectly via its install command's PROBE step — but for the conversation, you also want the data yourself so you can ask informed questions. Do these one-shot probes: + +```bash +# agents.md-compatible CLIs available +for cli in codex gemini opencode; do + command -v "$cli" >/dev/null 2>&1 && echo "$cli" +done + +# Agents config dirs +ls -d "$HOME"/.agents* 2>/dev/null + +# Dev root candidates +for d in "$HOME/lf" "$HOME/lfx" "$HOME/code/lfx" "$HOME/work/lfx"; do + [ -d "$d" ] && echo "$d" +done +``` + +## Step 3: Q1 — Install route + +Use `AskUserQuestion`: + +> "Which setup do you need? (1) agents.md-compatible tool (Codex, Gemini CLI, OpenCode), (2) Claude Code only, (3) both." + +If you detected only one CLI installed, default to it but still confirm. + +If the user picks Claude Code only, explain that Claude installs this repo as a plugin and does not use the CLI symlink installer: + +```text +/plugin marketplace add linuxfoundation/lfx-skills +/plugin install lfx-skills@lfx-skills +``` + +If they are testing from a local checkout, tell them to run Claude Code with the local plugin directory or add the local marketplace per the Claude Code plugin docs. Stop after explaining the plugin path; do not run `./cli/lfx-skills install` for Claude-only installs. + +If the user picks both, use the plugin path for Claude Code and continue with the CLI flow below for agents.md-compatible tools. The CLI itself remains agents.md-only. + +## Step 4: Q2 — Scope + +> "Install scope? (1) **Global** — available in every session of your agents.md-compatible tool. (2) **Per-repo** — only in specific repos (their `.agents/skills/`). (3) **Both** — global plus pin into specific repos." + +This question goes second so subsequent questions can adapt. (Per-repo only? Skip the global config picker. Global only? Skip the repo picker.) + +## Step 5: Q3 — LFX dev root + +Show the candidates you probed with their repo counts: + +``` +Where do you keep your LFX repo clones? + 1. ~/lf (12 lf* repos) + 2. ~/code/lfx (3 lf* repos) + 3. Custom path… +``` + +Use `AskUserQuestion`. If the user already has `LFX_DEV_ROOT` set in the shell, mention it and ask whether to keep it. + +If the chosen path doesn't exist, ask whether to create it (`mkdir -p`). If it has zero `lf*` git repos, warn but proceed: the install will still work; the dev-root-empty doctor warning will trigger until they clone some. + +## Step 6: Q4 — Agents config dirs (only if scope includes Global) + +If you saw multiple `~/.agents*` config dirs, ask which to install into: + +> "I see these agents config dirs: …. Install into all of them, or just one? (defaults to `~/.agents`)" + +Most users have one; this only matters for power users with multiple profiles. + +If scope is Per-repo only, skip this step entirely. + +## Step 7: Q5 — Repos (only if scope includes Per-repo) + +List `lf*` git repos under the chosen dev root: + +> "Which repos? Pick numbers (e.g., `1, 3, 5`), `all`, or `none`." + +If scope is Global only, skip this step entirely. + +## Step 8: Show the plan + +Before running anything, summarise: + +``` +Plan: + Scope: global + per-repo + LFX_DEV_ROOT: ~/lf + Agents dir: ~/.agents + Repos (4): lfx-v2-ui, lfx-v2-meeting-service, lfx-v2-committee-service, lfx-v2-query-service + Skills: runtime suite + lfx-skills-doctor + lfx-skills-helper + +Will create approximately N symlinks. Proceed? +``` + +`AskUserQuestion`. If no, stop. + +## Step 9: Run the installer + +Compose the non-interactive flags from the user's answers: + +```bash +./cli/lfx-skills install --yes \ + --scope= \ + --lfx-dev-root= \ + --agents-config= \ + --repos= +``` + +Stream the output to the user. + +## Step 10: Verify + +Run a quick verification: + +```bash +./cli/lfx-skills doctor +``` + +If errors, walk the user through the auto-fix: + +> "One or more checks failed. Want me to run `/lfx-skills-doctor` to investigate?" + +## Step 11: Confirm the CLI is on PATH + +The installer creates a symlink at a writable PATH dir (`~/.local/bin/lfx-skills`, `~/bin/lfx-skills`, or `/usr/local/bin/lfx-skills`) so the user can type `lfx-skills` from anywhere — no shell rc edit. Read the install output to see which path was used and tell the user: + +> "`lfx-skills` is now at `` and ready to use from any terminal." + +If the installer reported it couldn't find a writable PATH dir, share the alias snippet it printed: + +> "I couldn't find a writable PATH dir to drop the CLI into. Add this alias to your shell rc to use `lfx-skills` from anywhere: +> ```bash +> alias lfx-skills='/cli/lfx-skills' +> ``` +> Or extend PATH to include `~/.local/bin`, `~/bin`, or `/usr/local/bin`." + +## Step 12: Suggest next steps + +> "All set. Next: +> +> 1. Restart your AI coding assistant (or open a new session). +> 2. `cd` to any LFX repo and type `/lfx` — that's your plain-language entry point. +> 3. Run `/lfx-skills-doctor` anytime to recheck the install. +> 4. Run `/lfx-skills-helper` to manage what's installed where." + +## What this skill does NOT do + +- **Edit your shell rc** — never. Always print the snippet for the user to paste. +- **Install only some skills** — v1 always installs everything (the full set per chosen target). Filtering is a v2 idea. +- **Run outside the clone** — bail in Step 1. +- **Re-run silently** — confirm at the plan step before doing anything stateful. diff --git a/.agents/skills/lfx-new-skill/SKILL.md b/.agents/skills/lfx-new-skill/SKILL.md new file mode 100644 index 0000000..ea6e2da --- /dev/null +++ b/.agents/skills/lfx-new-skill/SKILL.md @@ -0,0 +1,138 @@ +--- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +name: lfx-new-skill +description: > + Scaffold a new LFX Skills suite skill under skills/. Use when someone wants + to add a new lfx skill, provides a complete SKILL.md to import, has an idea + for a skill and wants help drafting it, or asks how to create a skill. +allowed-tools: Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion +--- + + + +# LFX New Skill — Scaffolder + +You help contributors add LFX Skills suite skills under `skills/lfx-/`. Read [`references/conventions-quickref.md`](references/conventions-quickref.md) before writing files. + +Use two modes: + +- **Complete skill provided:** if the user pastes or points to a complete `SKILL.md`, use it as the source of truth. Preserve the body. Only normalize frontmatter, license header, directory name, and repo-specific placeholders when needed. +- **Draft from idea:** if the user only has an idea, help write the body. Ask targeted questions about trigger phrases, inputs, steps, tools, outputs, and boundaries. Draft concrete step-by-step instructions from the answers. + +## Step 1: Verify Location + +Run: + +```bash +[ -x ./cli/lfx-skills ] && [ -d ./skills/lfx ] && echo OK || echo NOT_IN_CLONE +``` + +If `NOT_IN_CLONE`, tell the user to `cd` to the `lfx-skills` clone and stop. + +## Step 2: Gather Inputs + +Ask whether the user has a complete `SKILL.md` or wants help drafting one. + +For a complete skill: + +- Read the supplied file/content. +- Extract `name`, `description`, and `allowed-tools` when present. +- Ask only for missing required fields. + +For a draft: + +- Ask for the skill name. It must match `^lfx-[a-z0-9-]+$` and not already exist under `skills/`. +- Ask for a frontmatter description with 3-5 trigger phrases. +- Ask which tools it needs. Default: `Bash, Read, Glob, Grep, AskUserQuestion`. +- Ask what the skill should do, what inputs it needs, what it may inspect or modify, what output it should produce, and what it must not do. + +Ask whether it needs `references/`. + +## Step 3: Write Files + +Create: + +```text +skills/lfx-/SKILL.md +``` + +If requested, also create: + +```text +skills/lfx-/references/.gitkeep +``` + +Ensure: + +- frontmatter starts on line 1 +- license comments are lines 2-3 inside the frontmatter +- `name:` equals the directory basename +- `description:` uses a YAML folded scalar +- `allowed-tools:` is present +- MCP-dependent skills include a `## Prerequisites` section + +If the skill is user-facing, ask whether to add routing guidance to `skills/lfx/SKILL.md`. Internal-only skills can remain unrouted. + +## Step 4: Validate + +Run only the new skill formatting check: + +```bash +./cli/lfx-skills doctor --skill-formatting-only --skill=lfx- +``` + +Fix `frontmatter-*` and `license-missing` issues, then rerun until clean. Do not run the full doctor for this scaffolding check; full doctor includes agents.md install/setup checks. + +## Step 5: Explain Local Testing + +Ask which runtime they want to test: Claude Code plugin, agents.md, or both. + +For Claude Code plugin testing: + +- Give the user this validation command to run from their normal terminal: + + ```bash + cd "" + claude plugin validate . + ``` + +- Ask which target LFX repo they want to test in. Resolve a repo name under `~/.lfx-skills/dev-root` or use the absolute path they provide. +- Give the user a ready-to-run command: + + ```bash + LFX_SKILLS_CLONE="" + cd "" + claude --plugin-dir "$LFX_SKILLS_CLONE" + ``` + +- Tell them to run `/lfx-skills:lfx-` in that Claude Code session. + +For agents.md testing: + +- Run `./cli/lfx-skills update`. +- Tell the user to restart their agents.md-compatible coding agent and run `/lfx-`. +- If LFX Skills is not installed for agents.md, point them to `/lfx-install` or `./install.sh`. + +Keep the two paths separate: the CLI is for agents.md installs; Claude Code uses the plugin path. + +## Step 6: Offer Commit And Release Help + +After validation, ask whether the user wants help committing the scaffolded skill. + +If they say yes: + +- Review `git diff` and `git status`. +- Commit only the intended files. +- Use `git commit -s -S`. +- Do not add co-author trailers. +- Do not push unless explicitly asked. + +For plugin versioning, explain that Claude Code picks up plugin changes when `.claude-plugin/plugin.json` gets a new SemVer `version` and the change reaches `main`. Offer to help choose the next patch/minor/major version and include the plugin version bump in the signed commit when plugin-visible behavior changed. + +## Boundaries + +- Do not install or repair the user's LFX Skills setup; route that to `/lfx-install` or `/lfx-skills-doctor`. +- Do not use the CLI to install Claude Code skills. +- Do not run Claude Code plugin commands for agents.md testing. +- Do not invent unclear behavior. Ask when scope, inputs, outputs, or safety boundaries are unclear. diff --git a/.agents/skills/lfx-new-skill/references/conventions-quickref.md b/.agents/skills/lfx-new-skill/references/conventions-quickref.md new file mode 100644 index 0000000..d3f3b34 --- /dev/null +++ b/.agents/skills/lfx-new-skill/references/conventions-quickref.md @@ -0,0 +1,132 @@ + + + +# New Skill Quick Reference + +## Frontmatter + +```yaml +--- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +name: lfx- +description: > + One paragraph with 3-5 trigger phrases users might say. +allowed-tools: Bash, Read, Glob, Grep, AskUserQuestion +--- +``` + +- `---` must be line 1. +- License comments must be lines 2-3 inside frontmatter. +- `name:` must match the directory basename. +- `description:` should use `>`. + +## Input Modes + +- **Complete skill:** preserve the supplied body; normalize only frontmatter, license, path, and repo-specific placeholders. +- **Idea:** ask enough questions to draft concrete runtime instructions. Cover triggers, inputs, steps, allowed tools, output, and explicit non-goals. + +## Tool Defaults + +| Use case | Tools | +|---|---| +| Read-only research | `Bash, Read, Glob, Grep, AskUserQuestion` | +| Reads external URLs | `Bash, Read, Glob, Grep, AskUserQuestion, WebFetch` | +| Edits files | `Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion` | +| Delegates to skills | `Bash, Read, Glob, Grep, AskUserQuestion, Skill` | +| MCP-dependent | Add the specific `mcp__*` tools | + +MCP-dependent skills should include `## Prerequisites`. + +## Body Shape + +Use this as the default body structure when drafting: + +```markdown +# + +<Who this helps and what it does.> + +## Step 1: <Action> + +<Concrete instructions. Ask for user input when needed.> + +## Step 2: <Action> + +<Concrete instructions.> + +## What this skill does NOT do + +- <Boundary> + +## Reference files + +- (none yet) +``` + +## References + +Use `references/` for long tables, checklists, examples, JSON/YAML snippets, or content the model should load only when needed. + +## Routing + +User-facing skills should usually be mentioned in `skills/lfx/SKILL.md` so `/lfx` can route to them. Internal-only skills can stay unrouted. + +## Validation + +Run: + +```bash +./cli/lfx-skills doctor --skill-formatting-only --skill=lfx-<name> +``` + +This checks only the new skill's frontmatter and license header. + +## Claude Code Local Test + +Give the user: + +```bash +cd "<absolute-path-to-lfx-skills>" +claude plugin validate . +``` + +Then ask which LFX repo they want to test in, resolve it under `~/.lfx-skills/dev-root` if they gave a repo name, and give: + +```bash +LFX_SKILLS_CLONE="<absolute-path-to-lfx-skills>" +cd "<resolved-target-repo-path>" +claude --plugin-dir "$LFX_SKILLS_CLONE" +``` + +They should test: + +```text +/lfx-skills:lfx-<name> +``` + +## agents.md Local Test + +Run: + +```bash +./cli/lfx-skills update +``` + +Then tell the user to restart their agents.md-compatible coding agent and run: + +```text +/lfx-<name> +``` + +## Commit And Release + +After validation, ask if the user wants help committing. If yes: + +- Review `git diff` and `git status`. +- Commit only intended files. +- Use `git commit -s -S`. +- Do not add co-author trailers. +- Do not push unless explicitly asked. + +For Claude Code plugin updates, bump the SemVer `version` in `.claude-plugin/plugin.json` with the skill changes when plugin-visible behavior changed. Claude Code will keep using the cached plugin if the version is unchanged. diff --git a/.agents/skills/lfx-skills-doctor/SKILL.md b/.agents/skills/lfx-skills-doctor/SKILL.md new file mode 100644 index 0000000..de7327b --- /dev/null +++ b/.agents/skills/lfx-skills-doctor/SKILL.md @@ -0,0 +1,136 @@ +--- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +name: lfx-skills-doctor +description: > + Diagnose problems with the agents.md LFX Skills installation and legacy + Claude symlink installs: broken symlinks, missing dev root, frontmatter + errors, routing gaps, MCP setup. Use whenever an agents.md-installed skill + isn't loading, when /lfx commands aren't appearing in autocomplete, when + newly installed agents.md skills don't show up, or when the user asks + "what's wrong with my install", "is my setup OK", or "check my lfx skills". + For Claude Code plugin installs, explain the plugin marketplace update path + instead of running the CLI doctor. Wraps `lfx-skills doctor --json` with a + conversational fix flow. +allowed-tools: Bash, Read, Glob, Grep, AskUserQuestion +--- + +<!-- Tool names in this file use Claude Code vocabulary. See docs/tool-mapping.md for other platforms. --> + +# LFX Skills Doctor + +You diagnose problems with a user's agents.md LFX Skills install and legacy Claude symlink installs, then walk them through fixing the ones that need a human-in-the-loop. The bash CLI handles mechanical repairs; you handle everything that needs judgment (content gaps, scaffolding, file edits). + +Claude Code plugin installs are separate. Claude Code uses `/plugin marketplace add linuxfoundation/lfx-skills` and `/plugin install lfx-skills@lfx-skills`; it does not use the CLI installer or agents.md symlinks. If the user is asking about the Claude Code plugin itself, do not run `lfx-skills doctor`. Tell them to update with `/plugin marketplace update lfx-skills` and `/plugin update lfx-skills@lfx-skills`, and to run `claude plugin validate .` from the LFX Skills clone when validating local plugin metadata. Use the CLI doctor only for agents.md installs and legacy Claude symlink cleanup. + +## Step 1: Locate the CLI + +The `lfx-skills` CLI lives in the user's lfx-skills clone at `cli/lfx-skills`. Try, in order: + +1. **On PATH:** `command -v lfx-skills` — if found, use it directly. +2. **From the manifest:** `jq -r .canonical_clone ~/.lfx-skills/config.json 2>/dev/null` — if the file exists, append `/cli/lfx-skills`. +3. **Current dir:** if the user is inside the lfx-skills clone (a `cli/lfx-skills` exists relative to `pwd`), use `./cli/lfx-skills`. +4. **Last resort:** ask the user: "Where is your lfx-skills clone? (e.g., `~/lf/lfx-skills`)". + +If none of the above works, the agents.md install was never run: tell the user to clone the repo and run `./install.sh` (or use `/lfx-install` if they're inside the clone). If they only need Claude Code, point them to the plugin marketplace commands instead. Stop here. + +## Step 2: Run diagnostics + +```bash +"$LFX_SKILLS_CLI" doctor --json +``` + +This emits a JSON array of records. Parse it. Each record has: +`{severity, id, category, title, detail, fixable, payload}`. + +Group by severity (`pass` / `warn` / `fail`). + +## Step 3: Render the report + +Use this layout. Keep it scannable. + +``` +LFX Skills Health Check +═══════════════════════════════════════════ + +✓ <N> checks passed. + +✗ <K> errors: + + 1. <title> + Why this matters: <plain-language consequence> + Fix: <action> + [auto-fixable] or [needs you to: <action>] + +⚠ <M> warnings: + + 1. <title> + Why: <consequence> + Fix: <action> +``` + +Don't dump every passing check. Summarise (`✓ 36 checks passed`) and let the user ask for the full list if they want it. + +For each error and warning, give plain-language context: not just the title from the JSON, but *why it matters* and *what to do about it*. Use `references/fix-recipes.md` (in this skill directory) to look up the per-issue narrative when the JSON record alone isn't enough. + +## Step 4: Offer fixes + +If there are fixable errors or warnings, ask: + +> "I can auto-fix N issue(s). Want me to walk through them?" + +Use `AskUserQuestion`. Wait for the answer. + +If yes: + +For each `fixable: true` record, ask per-issue (`AskUserQuestion`): "Fix `<id>`? — `<title>`". On yes, invoke: + +```bash +echo y | "$LFX_SKILLS_CLI" doctor --fix +``` + +(Or, more granularly, mark which issues to fix and answer the CLI's prompts. The CLI's `--fix` flow walks every fixable issue and asks per-issue too — you can let it drive, or you can pre-filter to the issues the user picked.) + +## Step 5: Handle the not-fixable cases + +For records with `fixable: false` that the user wants addressed, apply judgment. Common cases: + +| Issue ID | What you can offer | +|------------------------------|---------------------------------------------------------------------------------| +| `frontmatter-no-name` / `frontmatter-name-mismatch` | Read the SKILL.md, identify the line, offer to fix it via Edit. | +| `frontmatter-no-description` | Read the SKILL.md body, draft a one-paragraph description from it, offer to insert. | +| `license-missing` | Insert the YAML license-header lines (see `references/fix-recipes.md` template). | +| `routing-uncovered` | Read `lfx/SKILL.md`, find the routing table, offer to add an entry for the missing skill. | +| `routing-dangling` | Either remove the dangling entry from `lfx/SKILL.md` or hand off to `/lfx-new-skill` to scaffold the missing skill. Ask the user. | +| `symlink-no-skillmd` | Hand off to `/lfx-new-skill` to scaffold the missing SKILL.md. | +| `clone-dirty` | Informational only. Mention that the user has uncommitted changes; don't act. | + +Always ask before editing files. Never edit `lfx/SKILL.md` (or anything else) silently. + +## Step 6: Re-verify + +After applying fixes, re-run: + +```bash +"$LFX_SKILLS_CLI" doctor --json +``` + +Show the new counts. Celebrate if everything's green; otherwise, note what's still outstanding and why. + +## Step 7: Close + +End with: + +> "Run `/lfx-skills-doctor` anytime to recheck. For installation changes, `/lfx-install` or `/lfx-skills-helper` is the right entry point." + +## What this skill does NOT do + +- Diagnose Claude Code plugin cache or marketplace state through the CLI. Use Claude Code plugin commands for that path. +- Install new skills or change install scope: that's `/lfx-install`. +- List, manage, or scaffold skills: `/lfx-skills-helper` and `/lfx-new-skill`. +- Modify `lfx/SKILL.md`'s routing table without asking the user first. +- Run destructive operations (force-removing real files, etc.) — only mechanical repairs the CLI considers safe. + +## Reference files + +- [`references/fix-recipes.md`](references/fix-recipes.md) — per-issue narrative, fix templates, and copy-pasteable snippets. diff --git a/.agents/skills/lfx-skills-doctor/references/fix-recipes.md b/.agents/skills/lfx-skills-doctor/references/fix-recipes.md new file mode 100644 index 0000000..33397f1 --- /dev/null +++ b/.agents/skills/lfx-skills-doctor/references/fix-recipes.md @@ -0,0 +1,203 @@ +<!-- Copyright The Linux Foundation and each contributor to LFX. --> +<!-- SPDX-License-Identifier: MIT --> + +# Fix Recipes + +Per-issue narrative for `/lfx-skills-doctor`. The CLI's `--fix` flag handles the mechanical cases; this file covers everything else, plus extra context the JSON output doesn't include. + +Each entry follows the same shape: + +- **What:** plain-language description of what's wrong +- **Why it matters:** consequence for the user +- **Fix this session:** quick one-shot +- **Fix permanently:** lasting change +- **Auto-fixable?** yes (CLI), yes (skill), or no + +--- + +## issue-id: no-config + +**What:** No `~/.lfx-skills/config.json`. +**Why it matters:** the CLI doesn't know what's installed where. Doctor can only check things relative to the manifest, so most other checks will be skipped. +**Fix:** run `lfx-skills install`. Or, inside the lfx-skills clone, run `/lfx-install` for a guided walkthrough. +**Auto-fixable?** no (requires user choices about scope / dev root). + +--- + +## issue-id: no-symlinks + +**What:** Manifest exists but is empty (no symlinks recorded). +**Why it matters:** no LFX skills are actually installed. +**Fix:** run `lfx-skills install` to add them. +**Auto-fixable?** no. + +--- + +## issue-id: symlink-missing + +**What:** A symlink the manifest expects is gone from disk. +**Why it matters:** the corresponding `/lfx-...` command won't be available in your AI tool. +**Fix this session:** `lfx-skills doctor --fix` (CLI recreates from the manifest). +**Auto-fixable?** yes (CLI). + +--- + +## issue-id: symlink-broken + +**What:** A symlink exists but points to a path that's no longer a directory. +**Why it matters:** same as above — the skill won't load. +**Common cause:** the lfx-skills clone moved or was deleted. +**Fix this session:** `lfx-skills doctor --fix`. +**Fix permanently:** if you moved the clone, run `lfx-skills install` again from the new location to re-record `canonical_clone`. +**Auto-fixable?** yes (CLI). + +--- + +## issue-id: symlink-no-skillmd + +**What:** A symlink target exists, but the directory has no `SKILL.md`. +**Why it matters:** the skill won't load (the loader requires `SKILL.md`). Usually means someone created an empty `lfx-foo/` directory by accident, or pulled an in-progress branch. +**Fix:** either delete the empty directory, or scaffold a real skill via `/lfx-new-skill`. +**Auto-fixable?** no by CLI; the `/lfx-skills-doctor` skill can hand off to `/lfx-new-skill`. + +--- + +## issue-id: clone-not-recorded / clone-mismatch + +**What:** The CLI doesn't know which clone is canonical, or the recorded path doesn't match where the script ran from. +**Why it matters:** `lfx-skills update` and other commands rely on `canonical_clone`. Symlinks may also be pointing to a different clone than the one you think you're working in. +**Fix:** re-run `lfx-skills install` from the clone you want to be canonical. +**Auto-fixable?** no. + +--- + +## issue-id: clone-dirty + +**What:** Your `lfx-skills` clone has uncommitted changes. +**Why it matters:** purely informational. If you ran `lfx-skills update --pull`, that would fail with this state. Otherwise no impact. +**Fix:** commit, stash, or discard, depending on intent. +**Auto-fixable?** no. + +--- + +## issue-id: dev-root-not-recorded + +**What:** No `lfx_dev_root` in the manifest. +**Why it matters:** skills like `/lfx-test-journey` and `/lfx-coordinator` can't auto-discover your local LFX repos. +**Fix:** `lfx-skills config set lfx_dev_root=/path/to/your/lfx-clones`. Or re-run `lfx-skills install`. +**Auto-fixable?** no (requires user input). + +--- + +## issue-id: dev-root-missing + +**What:** The recorded `lfx_dev_root` path doesn't exist on disk. +**Common cause:** moved your clones to a new location. +**Fix:** `lfx-skills config set lfx_dev_root=/new/path`. The CLI will rewrite `~/.lfx-skills/dev-root` automatically. +**Auto-fixable?** no. + +--- + +## issue-id: dev-root-empty + +**What:** `LFX_DEV_ROOT` exists but contains no `lf*` git repos. +**Why it matters:** the skills will run, but they won't find any local repos to work on (they'll fall back to GitHub API calls when possible). +**Fix:** clone the LFX repos you work on into that directory. Examples: + +```bash +cd "$LFX_DEV_ROOT" +git clone https://github.com/linuxfoundation/lfx-v2-ui.git +git clone https://github.com/linuxfoundation/lfx-v2-meeting-service.git +``` + +**Auto-fixable?** no. + +--- + +## issue-id: dev-root-file-missing + +**What:** `~/.lfx-skills/dev-root` is gone. +**Why it matters:** the 3 skills that need a local LFX path (`/lfx-coordinator`, `/lfx-research`, `/lfx-test-journey`) read this file via `cat` to resolve the dev root without depending on env vars. Without it, they fall back to `~/lf`, which may not match your setup. +**Fix:** `lfx-skills doctor --fix` regenerates it from the manifest. Or `lfx-skills config set lfx_dev_root=/your/path` if you also need to change the path. +**Auto-fixable?** yes (CLI). + +--- + +## issue-id: dev-root-file-mismatch + +**What:** The contents of `~/.lfx-skills/dev-root` differ from `lfx_dev_root` in `config.json`. +**Common cause:** the file was hand-edited, or an old `lfx-skills` version wrote one source but not the other. +**Fix:** `lfx-skills doctor --fix` rewrites the file from `config.json` (the source of truth). +**Auto-fixable?** yes (CLI). + +--- + +## issue-id: frontmatter-missing + +**What:** A `SKILL.md` doesn't start with `---` on line 1. +**Why it matters:** the skill loader will refuse to load it. The loader requires frontmatter as the very first thing in the file (no blank lines, no comments above). +**Fix:** insert a frontmatter block at the top with the skill `name`, `description`, and `allowed-tools`. Use `/lfx-new-skill` as a template, or copy the shape from a sibling skill. +**Auto-fixable?** no by CLI; the `/lfx-skills-doctor` skill can guide the rewrite. + +--- + +## issue-id: frontmatter-no-name + +**What:** Frontmatter present but the `name:` field is missing or empty. +**Fix:** add `name: <skill-directory-basename>` to the frontmatter. Loader will fail without it. +**Auto-fixable?** no by CLI; the `/lfx-skills-doctor` skill can patch it via Edit. + +--- + +## issue-id: frontmatter-name-mismatch + +**What:** `name:` in the SKILL.md doesn't match the directory basename. +**Why it matters:** loaders use the directory name to register the slash command, but read the frontmatter for description and tools. A mismatch is confusing and may cause routing issues. +**Fix:** make `name:` equal `basename "$skill_dir"`. +**Auto-fixable?** no by CLI; the `/lfx-skills-doctor` skill can patch via Edit. + +--- + +## issue-id: frontmatter-no-description + +**What:** No `description:` field, or it's empty. +**Why it matters:** the loader uses `description` to decide when to surface the skill. Missing description means the model has no context for *when* to invoke it. +**Fix:** write a one-paragraph description that includes 3–5 trigger phrases users might say. +**Auto-fixable?** no by CLI; the `/lfx-skills-doctor` skill can draft one from the SKILL.md body. + +--- + +## issue-id: license-missing + +**What:** A `SKILL.md` doesn't have the LFX copyright header in its first 4 lines. +**Why it matters:** CI's `license-header-check` job will fail. +**Fix:** add these lines as lines 2–3, immediately after the opening `---`: + +```yaml +--- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +name: ... +``` + +(The `#` comments are valid YAML comments. They satisfy the license check without breaking frontmatter parsing.) +**Auto-fixable?** no by CLI; the `/lfx-skills-doctor` skill can patch via Edit. + +--- + +## issue-id: routing-dangling + +**What:** `lfx/SKILL.md` mentions `/lfx-foo` but no `lfx-foo/` directory exists. +**Why it matters:** the user asks `/lfx` to route to `/lfx-foo` and gets a confused error. +**Fix:** either remove the dangling reference from `lfx/SKILL.md`, or create the missing skill via `/lfx-new-skill`. +**Auto-fixable?** no — needs your call on which. + +--- + +## issue-id: routing-uncovered + +**What:** A skill exists in the clone but `lfx/SKILL.md` doesn't route to it. +**Why it matters:** users typing `/lfx` won't find the skill via the plain-language router. They can still invoke it directly with `/lfx-foo`. +**Fix:** add an entry to `lfx/SKILL.md`'s routing table for the skill, including 1–2 example trigger phrases. +**Caveat:** internal-only skills (like `lfx-backend-builder` and `lfx-ui-builder`, which are only invoked by `/lfx-coordinator`) can legitimately stay out of the routing table. Use judgment. +**Auto-fixable?** no by CLI; the `/lfx-skills-doctor` skill can patch via Edit. diff --git a/.agents/skills/lfx-skills-helper/SKILL.md b/.agents/skills/lfx-skills-helper/SKILL.md new file mode 100644 index 0000000..711f5f0 --- /dev/null +++ b/.agents/skills/lfx-skills-helper/SKILL.md @@ -0,0 +1,100 @@ +--- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +name: lfx-skills-helper +description: > + Manage the agents.md LFX Skills installation via the lfx-skills CLI: list + what's installed, install or uninstall in this repo or globally, update from + upstream, view or change config, look up what a specific skill does, and + remove legacy Claude symlink installs. Use for "add lfx skills to this repo", + "what's installed", "update lfx skills", "show my lfx setup", "uninstall", + "remove old Claude symlinks", "what does /lfx-foo do". For Claude plugin + installs, explain the plugin marketplace path. For "which skill should I use + for X" or other plain-language routing questions, hand off to /lfx. For health + checks or repair, hand off to /lfx-skills-doctor. +allowed-tools: Bash, Read, Glob, Grep, AskUserQuestion +--- + +<!-- Tool names in this file use Claude Code vocabulary. See docs/tool-mapping.md for other platforms. --> + +# LFX Skills Helper + +You are the conversational front-end for the agents.md-only `lfx-skills` CLI: install, uninstall, update, list, info, config, and legacy Claude symlink cleanup. You are NOT a router. If the user asks "which skill should I use for X" or describes a task ("I need to add a feature", "review my PR"), hand off to `/lfx` — that's the plain-language router. Your job is skill *management*, not skill *discovery*. + +Claude Code is separate: it installs LFX Skills as a plugin with `/plugin marketplace add linuxfoundation/lfx-skills` and `/plugin install lfx-skills@lfx-skills`. Do not use the CLI to install Claude Code skills. The CLI can only remove old Claude symlink installs via `lfx-skills uninstall --legacy-claude-only` or as part of `lfx-skills uninstall --all`. + +## Step 1: Locate the CLI + +Same as `/lfx-skills-doctor`. Try in order: + +1. `command -v lfx-skills` (on PATH). +2. `jq -r .canonical_clone ~/.lfx-skills/config.json 2>/dev/null` then append `/cli/lfx-skills`. +3. `./cli/lfx-skills` if you're inside the lfx-skills clone. +4. Ask the user. + +If none works: the install was never run. Tell the user to clone `linuxfoundation/lfx-skills` and run `./install.sh` (or invoke `/lfx-install` if they're already in the clone). Stop. + +## Step 2: Classify the request + +Decide which surface the request belongs to: + +| User asks about… | Surface | Action | +|--------------------------------------------|------------------|-------------------------------------------| +| Installing, updating, removing, config | This skill | Map to a CLI subcommand (Step 3) | +| What's installed / available / where | This skill | Map to a CLI subcommand (Step 3) | +| What a specific skill does | This skill | `lfx-skills info <name>` | +| **Which skill** to use for a task | Hand off to `/lfx` | Stop here; let the router pick. | +| **Diagnose** a problem / "why isn't X working" | Hand off to `/lfx-skills-doctor` | Stop here. | +| **Scaffold a new skill** | Hand off to `/lfx-new-skill` | Stop here (clone-only). | + +Read `references/intents.md` once for the management-intent → CLI mapping. If the user's phrasing isn't in the table and doesn't fit your job either, say so and suggest the right entry point. + +## Step 3: Run the CLI + +Execute the chosen subcommand. Capture stdout. Use `--json` flags where available (currently `doctor --json`) when you need structured data. + +For commands that change state (`install`, `uninstall`, `update`, `config set`), **always confirm with the user first** via `AskUserQuestion`, showing exactly what you're about to run. The CLI's `--yes` flag is appropriate only after the user has confirmed in chat. + +## Step 4: Format the output + +The CLI output is structured but utilitarian. Reformat it for conversation: + +- **`lfx-skills list`** outputs `scope<TAB>skill<TAB>link`. Render as a friendly grouped list: + + ``` + Globally installed (agents.md): + /lfx + /lfx-coordinator + ... + + Per-repo (lfx-v2-meeting-service): + /lfx-coordinator + /lfx-pr-resolve + ... + ``` + +- **`lfx-skills info <skill>`** outputs frontmatter + install locations. Render the description in prose, list trigger phrases, summarise where it's installed. + +- **`lfx-skills config`** outputs raw JSON. Pretty-print it as a small table: dev root, canonical clone, total symlinks, and CLI symlink. + +- **`lfx-skills repos`** outputs one path per line. Group as a numbered list with sizes/last-modified if helpful. + +## Step 5: Hand-off rules + +If during the conversation the request shifts to something off your turf, hand off cleanly: + +- **Diagnostic questions** ("is my install OK?", "why isn't /lfx-foo working?", "fix my broken symlinks"): hand off to `/lfx-skills-doctor`. +- **Routing / discovery** ("which skill should I use for backend work?", "I want to add a feature, where do I start?"): hand off to `/lfx`. +- **Creating a new skill**: hand off to `/lfx-new-skill` (only available inside the lfx-skills clone). + +## What this skill does NOT do + +- **Pick which skill the user should use**: that's `/lfx`'s job. This skill manages the install; it doesn't recommend skills. +- **Diagnose problems**: hand off to `/lfx-skills-doctor`. +- **Scaffold new skills**: hand off to `/lfx-new-skill`. +- **Install/uninstall without confirmation**: always show the user the exact command first. +- **Invent categorisations**: skills aren't tagged backend/frontend/etc. anywhere in their metadata; don't pretend they are. + +## Reference files + +- [`references/intents.md`](references/intents.md) — management-intent → CLI command mapping. diff --git a/.agents/skills/lfx-skills-helper/references/intents.md b/.agents/skills/lfx-skills-helper/references/intents.md new file mode 100644 index 0000000..bd9bf68 --- /dev/null +++ b/.agents/skills/lfx-skills-helper/references/intents.md @@ -0,0 +1,73 @@ +<!-- Copyright The Linux Foundation and each contributor to LFX. --> +<!-- SPDX-License-Identifier: MIT --> + +# Intent → CLI mapping + +Reference for `/lfx-skills-helper`. Maps natural-language **management** intents to the corresponding `lfx-skills` CLI invocation. + +This file is for agents.md skill *management*: install, uninstall, update, list, info, config, and legacy Claude symlink cleanup. It is not a recommendation engine. Routing questions ("which skill should I use for X?") belong to `/lfx`. Diagnostic questions belong to `/lfx-skills-doctor`. Authoring belongs to `/lfx-new-skill`. + +When the user's phrasing isn't an exact match, infer the closest intent and confirm the chosen command before running anything stateful. + +## Listing + +| User says | Run | Notes | +|-------------------------------------------------|-----------------------------------------------------------|-----------------------------------------------------------------------------| +| "What lfx skills do I have here?" | `lfx-skills list --scope=repo --repo="$(pwd)"` | Per-repo install only | +| "What lfx skills are installed globally?" | `lfx-skills list --scope=global` | Across every recorded global config dir | +| "What lfx skills do I have anywhere?" | `lfx-skills list` | Both scopes combined | +| "What lfx skills are available in the clone?" | `lfx-skills list --available` | All installable skills, regardless of install state | + +## Inspecting one skill + +| User says | Run | Notes | +|-------------------------------------------------|-----------------------------------------------------------|-----------------------------------------------------------------------------| +| "What does /lfx-coordinator do?" | `lfx-skills info lfx-coordinator` | Strip the leading slash; pass the bare name | +| "Where is /lfx-coordinator installed?" | `lfx-skills info lfx-coordinator` | The output includes install locations | +| "Show me my full setup" | `lfx-skills config` | Pretty-print the JSON for the user | +| "Where is my LFX dev root?" | `lfx-skills config get lfx_dev_root` | | +| "Which clone of lfx-skills am I using?" | `lfx-skills config get canonical_clone` | | +| "What repos are in my LFX dev root?" | `lfx-skills repos` | | + +## Installing / changing scope + +Always confirm via `AskUserQuestion` before running. Show the exact command first. + +| User says | Run | +|-------------------------------------------------|--------------------------------------------------------------------------------------| +| "Add lfx skills to this repo" | Confirm, then `lfx-skills install --yes --scope=repo --repos="$(pwd)"`. Suggest `/lfx-skills-doctor` after. | +| "Remove lfx skills from this repo" | Confirm, then `lfx-skills uninstall --yes --scope=repo --repos="$(pwd)"` | +| "Install lfx skills globally for Claude" | Explain the Claude Code plugin path: `/plugin marketplace add linuxfoundation/lfx-skills`, then `/plugin install lfx-skills@lfx-skills` | +| "Add agents.md support" | Confirm, then `lfx-skills install --yes --scope=global` | +| "Install everything everywhere" | Confirm. Don't assume `--repos=`; ask the user which repos. | +| "Uninstall lfx skills" | Confirm, then `lfx-skills uninstall --yes --all` | + +## Maintenance + +| User says | Run | Notes | +|-------------------------------------------------|-----------------------------------------------------------|-----------------------------------------------------------------------------| +| "Update lfx skills" | Ask whether they mean Claude Code plugin or agents.md CLI install. For agents.md, run `lfx-skills update --pull`. | Suggest `/lfx-skills-doctor` after agents.md updates | +| "Update the Claude plugin" / "Update Claude skills" | Explain: `/plugin marketplace update lfx-skills`, then `/plugin update lfx-skills@lfx-skills` | Claude Code plugin updates are not handled by the CLI | +| "Re-apply my install" | `lfx-skills update` | No `--pull`; just refresh symlinks against the manifest | +| "Remove old Claude symlinks" | Confirm, then `lfx-skills uninstall --yes --legacy-claude-only` | Removes only lfx-skills-owned legacy Claude symlinks | +| "Remove lfx-skills completely" | Confirm, then `lfx-skills uninstall --yes --all` | Removes agents.md symlinks, legacy Claude symlinks, CLI symlink, config | +| "Update my LFX dev root" | Confirm new path, `lfx-skills config set lfx_dev_root=NEW_PATH` | Rewrites `~/.lfx-skills/dev-root` automatically | +| "Switch my Claude config dir" | Explain that Claude Code plugin scope is managed by Claude Code settings, not this CLI. | + +## Hand-offs (not your job) + +| User says | Hand off to | +|-------------------------------------------------|--------------------------------------------------------------------------------------| +| "Which skill should I use for X?" / task descriptions | `/lfx` | +| "Run a health check" / "is my install OK?" | `/lfx-skills-doctor` | +| "Why isn't /lfx-foo working?" | `/lfx-skills-doctor` | +| "Fix my broken symlinks" | `/lfx-skills-doctor` | +| "How do I create a new lfx skill?" | `/lfx-new-skill` (only inside the lfx-skills clone) | +| "Scaffold a new skill called lfx-foo" | `/lfx-new-skill` | + +## Disambiguation rules + +- The leading slash in user phrasing (e.g., `/lfx-coordinator`) is conversational; strip it when passing to the CLI. +- If the user says "skill X" without specifying scope, assume per-repo when they're inside a repo, global otherwise. Confirm before acting. +- If the user gives a custom command-line flag combo you don't recognise, defer to the CLI: `lfx-skills help <subcommand>`. +- If the user describes a *task* rather than asking about install state, that's a routing question — hand off to `/lfx` even mid-conversation. diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 0000000..4675ed8 --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,16 @@ +{ + "name": "lfx-skills", + "owner": { + "name": "The Linux Foundation" + }, + "metadata": { + "description": "Claude Code marketplace for LFX Skills." + }, + "plugins": [ + { + "name": "lfx-skills", + "source": "./", + "description": "Claude Code skills for LFX. Start with the lfx skill and describe what you want in plain language." + } + ] +} diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..625a4ac --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "lfx-skills", + "description": "Claude Code skills for LFX. Start with the lfx skill and describe what you want in plain language.", + "version": "0.1.0", + "author": { + "name": "The Linux Foundation" + }, + "homepage": "https://github.com/linuxfoundation/lfx-skills", + "repository": "https://github.com/linuxfoundation/lfx-skills", + "license": "MIT", + "skills": ["./skills/"] +} diff --git a/.claude/skills/lfx-install/SKILL.md b/.claude/skills/lfx-install/SKILL.md new file mode 100644 index 0000000..45baf78 --- /dev/null +++ b/.claude/skills/lfx-install/SKILL.md @@ -0,0 +1,183 @@ +--- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +name: lfx-install +description: > + Install or set up LFX Skills for agents.md-compatible tools via the + lfx-skills CLI, and point Claude Code-only users to the Claude plugin path. + Walks through the choices in plain language: where their LFX repos live, + scope, agents config dirs, then runs the installer and verifies. Use + whenever the user says "I just cloned this — what now?", "set up lfx skills", + "install lfx skills", "I'm new to lfx-skills", or "first-time setup". +allowed-tools: Bash, Read, Glob, Grep, AskUserQuestion +--- + +<!-- Tool names in this file use Claude Code vocabulary. See docs/tool-mapping.md for other platforms. --> + +# LFX Skills Install + +You guide the user through their first-time install. The bash CLI (`cli/lfx-skills install`) can do this non-interactively if every flag is supplied; this skill is the conversational layer that figures out what those flags should be by asking the user, in plain language, one question at a time. + +## Step 1: Verify you're in the clone + +This skill only works inside the `lfx-skills` clone. Verify: + +```bash +[ -x ./cli/lfx-skills ] && echo OK || echo NOT_IN_CLONE +``` + +If `NOT_IN_CLONE`, tell the user: + +> "I only run inside the lfx-skills clone. `cd` to your clone of `linuxfoundation/lfx-skills` and ask again." + +Stop. + +## Step 2: Probe the system + +Run `./cli/lfx-skills` indirectly via its install command's PROBE step — but for the conversation, you also want the data yourself so you can ask informed questions. Do these one-shot probes: + +```bash +# agents.md-compatible CLIs available +for cli in codex gemini opencode; do + command -v "$cli" >/dev/null 2>&1 && echo "$cli" +done + +# Agents config dirs +ls -d "$HOME"/.agents* 2>/dev/null + +# Dev root candidates +for d in "$HOME/lf" "$HOME/lfx" "$HOME/code/lfx" "$HOME/work/lfx"; do + [ -d "$d" ] && echo "$d" +done +``` + +## Step 3: Q1 — Install route + +Use `AskUserQuestion`: + +> "Which setup do you need? (1) agents.md-compatible tool (Codex, Gemini CLI, OpenCode), (2) Claude Code only, (3) both." + +If you detected only one CLI installed, default to it but still confirm. + +If the user picks Claude Code only, explain that Claude installs this repo as a plugin and does not use the CLI symlink installer: + +```text +/plugin marketplace add linuxfoundation/lfx-skills +/plugin install lfx-skills@lfx-skills +``` + +If they are testing from a local checkout, tell them to run Claude Code with the local plugin directory or add the local marketplace per the Claude Code plugin docs. Stop after explaining the plugin path; do not run `./cli/lfx-skills install` for Claude-only installs. + +If the user picks both, use the plugin path for Claude Code and continue with the CLI flow below for agents.md-compatible tools. The CLI itself remains agents.md-only. + +## Step 4: Q2 — Scope + +> "Install scope? (1) **Global** — available in every session of your agents.md-compatible tool. (2) **Per-repo** — only in specific repos (their `.agents/skills/`). (3) **Both** — global plus pin into specific repos." + +This question goes second so subsequent questions can adapt. (Per-repo only? Skip the global config picker. Global only? Skip the repo picker.) + +## Step 5: Q3 — LFX dev root + +Show the candidates you probed with their repo counts: + +``` +Where do you keep your LFX repo clones? + 1. ~/lf (12 lf* repos) + 2. ~/code/lfx (3 lf* repos) + 3. Custom path… +``` + +Use `AskUserQuestion`. If the user already has `LFX_DEV_ROOT` set in the shell, mention it and ask whether to keep it. + +If the chosen path doesn't exist, ask whether to create it (`mkdir -p`). If it has zero `lf*` git repos, warn but proceed: the install will still work; the dev-root-empty doctor warning will trigger until they clone some. + +## Step 6: Q4 — Agents config dirs (only if scope includes Global) + +If you saw multiple `~/.agents*` config dirs, ask which to install into: + +> "I see these agents config dirs: …. Install into all of them, or just one? (defaults to `~/.agents`)" + +Most users have one; this only matters for power users with multiple profiles. + +If scope is Per-repo only, skip this step entirely. + +## Step 7: Q5 — Repos (only if scope includes Per-repo) + +List `lf*` git repos under the chosen dev root: + +> "Which repos? Pick numbers (e.g., `1, 3, 5`), `all`, or `none`." + +If scope is Global only, skip this step entirely. + +## Step 8: Show the plan + +Before running anything, summarise: + +``` +Plan: + Scope: global + per-repo + LFX_DEV_ROOT: ~/lf + Agents dir: ~/.agents + Repos (4): lfx-v2-ui, lfx-v2-meeting-service, lfx-v2-committee-service, lfx-v2-query-service + Skills: runtime suite + lfx-skills-doctor + lfx-skills-helper + +Will create approximately N symlinks. Proceed? +``` + +`AskUserQuestion`. If no, stop. + +## Step 9: Run the installer + +Compose the non-interactive flags from the user's answers: + +```bash +./cli/lfx-skills install --yes \ + --scope=<scope> \ + --lfx-dev-root=<path> \ + --agents-config=<dir> \ + --repos=<repo1,repo2,...> +``` + +Stream the output to the user. + +## Step 10: Verify + +Run a quick verification: + +```bash +./cli/lfx-skills doctor +``` + +If errors, walk the user through the auto-fix: + +> "One or more checks failed. Want me to run `/lfx-skills-doctor` to investigate?" + +## Step 11: Confirm the CLI is on PATH + +The installer creates a symlink at a writable PATH dir (`~/.local/bin/lfx-skills`, `~/bin/lfx-skills`, or `/usr/local/bin/lfx-skills`) so the user can type `lfx-skills` from anywhere — no shell rc edit. Read the install output to see which path was used and tell the user: + +> "`lfx-skills` is now at `<reported path>` and ready to use from any terminal." + +If the installer reported it couldn't find a writable PATH dir, share the alias snippet it printed: + +> "I couldn't find a writable PATH dir to drop the CLI into. Add this alias to your shell rc to use `lfx-skills` from anywhere: +> ```bash +> alias lfx-skills='<clone>/cli/lfx-skills' +> ``` +> Or extend PATH to include `~/.local/bin`, `~/bin`, or `/usr/local/bin`." + +## Step 12: Suggest next steps + +> "All set. Next: +> +> 1. Restart your AI coding assistant (or open a new session). +> 2. `cd` to any LFX repo and type `/lfx` — that's your plain-language entry point. +> 3. Run `/lfx-skills-doctor` anytime to recheck the install. +> 4. Run `/lfx-skills-helper` to manage what's installed where." + +## What this skill does NOT do + +- **Edit your shell rc** — never. Always print the snippet for the user to paste. +- **Install only some skills** — v1 always installs everything (the full set per chosen target). Filtering is a v2 idea. +- **Run outside the clone** — bail in Step 1. +- **Re-run silently** — confirm at the plan step before doing anything stateful. diff --git a/.claude/skills/lfx-new-skill/SKILL.md b/.claude/skills/lfx-new-skill/SKILL.md new file mode 100644 index 0000000..ea6e2da --- /dev/null +++ b/.claude/skills/lfx-new-skill/SKILL.md @@ -0,0 +1,138 @@ +--- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +name: lfx-new-skill +description: > + Scaffold a new LFX Skills suite skill under skills/. Use when someone wants + to add a new lfx skill, provides a complete SKILL.md to import, has an idea + for a skill and wants help drafting it, or asks how to create a skill. +allowed-tools: Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion +--- + +<!-- Tool names in this file use Claude Code vocabulary. See docs/tool-mapping.md for other platforms. --> + +# LFX New Skill — Scaffolder + +You help contributors add LFX Skills suite skills under `skills/lfx-<name>/`. Read [`references/conventions-quickref.md`](references/conventions-quickref.md) before writing files. + +Use two modes: + +- **Complete skill provided:** if the user pastes or points to a complete `SKILL.md`, use it as the source of truth. Preserve the body. Only normalize frontmatter, license header, directory name, and repo-specific placeholders when needed. +- **Draft from idea:** if the user only has an idea, help write the body. Ask targeted questions about trigger phrases, inputs, steps, tools, outputs, and boundaries. Draft concrete step-by-step instructions from the answers. + +## Step 1: Verify Location + +Run: + +```bash +[ -x ./cli/lfx-skills ] && [ -d ./skills/lfx ] && echo OK || echo NOT_IN_CLONE +``` + +If `NOT_IN_CLONE`, tell the user to `cd` to the `lfx-skills` clone and stop. + +## Step 2: Gather Inputs + +Ask whether the user has a complete `SKILL.md` or wants help drafting one. + +For a complete skill: + +- Read the supplied file/content. +- Extract `name`, `description`, and `allowed-tools` when present. +- Ask only for missing required fields. + +For a draft: + +- Ask for the skill name. It must match `^lfx-[a-z0-9-]+$` and not already exist under `skills/`. +- Ask for a frontmatter description with 3-5 trigger phrases. +- Ask which tools it needs. Default: `Bash, Read, Glob, Grep, AskUserQuestion`. +- Ask what the skill should do, what inputs it needs, what it may inspect or modify, what output it should produce, and what it must not do. + +Ask whether it needs `references/`. + +## Step 3: Write Files + +Create: + +```text +skills/lfx-<name>/SKILL.md +``` + +If requested, also create: + +```text +skills/lfx-<name>/references/.gitkeep +``` + +Ensure: + +- frontmatter starts on line 1 +- license comments are lines 2-3 inside the frontmatter +- `name:` equals the directory basename +- `description:` uses a YAML folded scalar +- `allowed-tools:` is present +- MCP-dependent skills include a `## Prerequisites` section + +If the skill is user-facing, ask whether to add routing guidance to `skills/lfx/SKILL.md`. Internal-only skills can remain unrouted. + +## Step 4: Validate + +Run only the new skill formatting check: + +```bash +./cli/lfx-skills doctor --skill-formatting-only --skill=lfx-<name> +``` + +Fix `frontmatter-*` and `license-missing` issues, then rerun until clean. Do not run the full doctor for this scaffolding check; full doctor includes agents.md install/setup checks. + +## Step 5: Explain Local Testing + +Ask which runtime they want to test: Claude Code plugin, agents.md, or both. + +For Claude Code plugin testing: + +- Give the user this validation command to run from their normal terminal: + + ```bash + cd "<absolute-path-to-lfx-skills>" + claude plugin validate . + ``` + +- Ask which target LFX repo they want to test in. Resolve a repo name under `~/.lfx-skills/dev-root` or use the absolute path they provide. +- Give the user a ready-to-run command: + + ```bash + LFX_SKILLS_CLONE="<absolute-path-to-lfx-skills>" + cd "<resolved-target-repo-path>" + claude --plugin-dir "$LFX_SKILLS_CLONE" + ``` + +- Tell them to run `/lfx-skills:lfx-<name>` in that Claude Code session. + +For agents.md testing: + +- Run `./cli/lfx-skills update`. +- Tell the user to restart their agents.md-compatible coding agent and run `/lfx-<name>`. +- If LFX Skills is not installed for agents.md, point them to `/lfx-install` or `./install.sh`. + +Keep the two paths separate: the CLI is for agents.md installs; Claude Code uses the plugin path. + +## Step 6: Offer Commit And Release Help + +After validation, ask whether the user wants help committing the scaffolded skill. + +If they say yes: + +- Review `git diff` and `git status`. +- Commit only the intended files. +- Use `git commit -s -S`. +- Do not add co-author trailers. +- Do not push unless explicitly asked. + +For plugin versioning, explain that Claude Code picks up plugin changes when `.claude-plugin/plugin.json` gets a new SemVer `version` and the change reaches `main`. Offer to help choose the next patch/minor/major version and include the plugin version bump in the signed commit when plugin-visible behavior changed. + +## Boundaries + +- Do not install or repair the user's LFX Skills setup; route that to `/lfx-install` or `/lfx-skills-doctor`. +- Do not use the CLI to install Claude Code skills. +- Do not run Claude Code plugin commands for agents.md testing. +- Do not invent unclear behavior. Ask when scope, inputs, outputs, or safety boundaries are unclear. diff --git a/.claude/skills/lfx-new-skill/references/conventions-quickref.md b/.claude/skills/lfx-new-skill/references/conventions-quickref.md new file mode 100644 index 0000000..d3f3b34 --- /dev/null +++ b/.claude/skills/lfx-new-skill/references/conventions-quickref.md @@ -0,0 +1,132 @@ +<!-- Copyright The Linux Foundation and each contributor to LFX. --> +<!-- SPDX-License-Identifier: MIT --> + +# New Skill Quick Reference + +## Frontmatter + +```yaml +--- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +name: lfx-<name> +description: > + One paragraph with 3-5 trigger phrases users might say. +allowed-tools: Bash, Read, Glob, Grep, AskUserQuestion +--- +``` + +- `---` must be line 1. +- License comments must be lines 2-3 inside frontmatter. +- `name:` must match the directory basename. +- `description:` should use `>`. + +## Input Modes + +- **Complete skill:** preserve the supplied body; normalize only frontmatter, license, path, and repo-specific placeholders. +- **Idea:** ask enough questions to draft concrete runtime instructions. Cover triggers, inputs, steps, allowed tools, output, and explicit non-goals. + +## Tool Defaults + +| Use case | Tools | +|---|---| +| Read-only research | `Bash, Read, Glob, Grep, AskUserQuestion` | +| Reads external URLs | `Bash, Read, Glob, Grep, AskUserQuestion, WebFetch` | +| Edits files | `Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion` | +| Delegates to skills | `Bash, Read, Glob, Grep, AskUserQuestion, Skill` | +| MCP-dependent | Add the specific `mcp__*` tools | + +MCP-dependent skills should include `## Prerequisites`. + +## Body Shape + +Use this as the default body structure when drafting: + +```markdown +# <Title> + +<Who this helps and what it does.> + +## Step 1: <Action> + +<Concrete instructions. Ask for user input when needed.> + +## Step 2: <Action> + +<Concrete instructions.> + +## What this skill does NOT do + +- <Boundary> + +## Reference files + +- (none yet) +``` + +## References + +Use `references/` for long tables, checklists, examples, JSON/YAML snippets, or content the model should load only when needed. + +## Routing + +User-facing skills should usually be mentioned in `skills/lfx/SKILL.md` so `/lfx` can route to them. Internal-only skills can stay unrouted. + +## Validation + +Run: + +```bash +./cli/lfx-skills doctor --skill-formatting-only --skill=lfx-<name> +``` + +This checks only the new skill's frontmatter and license header. + +## Claude Code Local Test + +Give the user: + +```bash +cd "<absolute-path-to-lfx-skills>" +claude plugin validate . +``` + +Then ask which LFX repo they want to test in, resolve it under `~/.lfx-skills/dev-root` if they gave a repo name, and give: + +```bash +LFX_SKILLS_CLONE="<absolute-path-to-lfx-skills>" +cd "<resolved-target-repo-path>" +claude --plugin-dir "$LFX_SKILLS_CLONE" +``` + +They should test: + +```text +/lfx-skills:lfx-<name> +``` + +## agents.md Local Test + +Run: + +```bash +./cli/lfx-skills update +``` + +Then tell the user to restart their agents.md-compatible coding agent and run: + +```text +/lfx-<name> +``` + +## Commit And Release + +After validation, ask if the user wants help committing. If yes: + +- Review `git diff` and `git status`. +- Commit only intended files. +- Use `git commit -s -S`. +- Do not add co-author trailers. +- Do not push unless explicitly asked. + +For Claude Code plugin updates, bump the SemVer `version` in `.claude-plugin/plugin.json` with the skill changes when plugin-visible behavior changed. Claude Code will keep using the cached plugin if the version is unchanged. diff --git a/.claude/skills/lfx-skills-doctor/SKILL.md b/.claude/skills/lfx-skills-doctor/SKILL.md new file mode 100644 index 0000000..de7327b --- /dev/null +++ b/.claude/skills/lfx-skills-doctor/SKILL.md @@ -0,0 +1,136 @@ +--- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +name: lfx-skills-doctor +description: > + Diagnose problems with the agents.md LFX Skills installation and legacy + Claude symlink installs: broken symlinks, missing dev root, frontmatter + errors, routing gaps, MCP setup. Use whenever an agents.md-installed skill + isn't loading, when /lfx commands aren't appearing in autocomplete, when + newly installed agents.md skills don't show up, or when the user asks + "what's wrong with my install", "is my setup OK", or "check my lfx skills". + For Claude Code plugin installs, explain the plugin marketplace update path + instead of running the CLI doctor. Wraps `lfx-skills doctor --json` with a + conversational fix flow. +allowed-tools: Bash, Read, Glob, Grep, AskUserQuestion +--- + +<!-- Tool names in this file use Claude Code vocabulary. See docs/tool-mapping.md for other platforms. --> + +# LFX Skills Doctor + +You diagnose problems with a user's agents.md LFX Skills install and legacy Claude symlink installs, then walk them through fixing the ones that need a human-in-the-loop. The bash CLI handles mechanical repairs; you handle everything that needs judgment (content gaps, scaffolding, file edits). + +Claude Code plugin installs are separate. Claude Code uses `/plugin marketplace add linuxfoundation/lfx-skills` and `/plugin install lfx-skills@lfx-skills`; it does not use the CLI installer or agents.md symlinks. If the user is asking about the Claude Code plugin itself, do not run `lfx-skills doctor`. Tell them to update with `/plugin marketplace update lfx-skills` and `/plugin update lfx-skills@lfx-skills`, and to run `claude plugin validate .` from the LFX Skills clone when validating local plugin metadata. Use the CLI doctor only for agents.md installs and legacy Claude symlink cleanup. + +## Step 1: Locate the CLI + +The `lfx-skills` CLI lives in the user's lfx-skills clone at `cli/lfx-skills`. Try, in order: + +1. **On PATH:** `command -v lfx-skills` — if found, use it directly. +2. **From the manifest:** `jq -r .canonical_clone ~/.lfx-skills/config.json 2>/dev/null` — if the file exists, append `/cli/lfx-skills`. +3. **Current dir:** if the user is inside the lfx-skills clone (a `cli/lfx-skills` exists relative to `pwd`), use `./cli/lfx-skills`. +4. **Last resort:** ask the user: "Where is your lfx-skills clone? (e.g., `~/lf/lfx-skills`)". + +If none of the above works, the agents.md install was never run: tell the user to clone the repo and run `./install.sh` (or use `/lfx-install` if they're inside the clone). If they only need Claude Code, point them to the plugin marketplace commands instead. Stop here. + +## Step 2: Run diagnostics + +```bash +"$LFX_SKILLS_CLI" doctor --json +``` + +This emits a JSON array of records. Parse it. Each record has: +`{severity, id, category, title, detail, fixable, payload}`. + +Group by severity (`pass` / `warn` / `fail`). + +## Step 3: Render the report + +Use this layout. Keep it scannable. + +``` +LFX Skills Health Check +═══════════════════════════════════════════ + +✓ <N> checks passed. + +✗ <K> errors: + + 1. <title> + Why this matters: <plain-language consequence> + Fix: <action> + [auto-fixable] or [needs you to: <action>] + +⚠ <M> warnings: + + 1. <title> + Why: <consequence> + Fix: <action> +``` + +Don't dump every passing check. Summarise (`✓ 36 checks passed`) and let the user ask for the full list if they want it. + +For each error and warning, give plain-language context: not just the title from the JSON, but *why it matters* and *what to do about it*. Use `references/fix-recipes.md` (in this skill directory) to look up the per-issue narrative when the JSON record alone isn't enough. + +## Step 4: Offer fixes + +If there are fixable errors or warnings, ask: + +> "I can auto-fix N issue(s). Want me to walk through them?" + +Use `AskUserQuestion`. Wait for the answer. + +If yes: + +For each `fixable: true` record, ask per-issue (`AskUserQuestion`): "Fix `<id>`? — `<title>`". On yes, invoke: + +```bash +echo y | "$LFX_SKILLS_CLI" doctor --fix +``` + +(Or, more granularly, mark which issues to fix and answer the CLI's prompts. The CLI's `--fix` flow walks every fixable issue and asks per-issue too — you can let it drive, or you can pre-filter to the issues the user picked.) + +## Step 5: Handle the not-fixable cases + +For records with `fixable: false` that the user wants addressed, apply judgment. Common cases: + +| Issue ID | What you can offer | +|------------------------------|---------------------------------------------------------------------------------| +| `frontmatter-no-name` / `frontmatter-name-mismatch` | Read the SKILL.md, identify the line, offer to fix it via Edit. | +| `frontmatter-no-description` | Read the SKILL.md body, draft a one-paragraph description from it, offer to insert. | +| `license-missing` | Insert the YAML license-header lines (see `references/fix-recipes.md` template). | +| `routing-uncovered` | Read `lfx/SKILL.md`, find the routing table, offer to add an entry for the missing skill. | +| `routing-dangling` | Either remove the dangling entry from `lfx/SKILL.md` or hand off to `/lfx-new-skill` to scaffold the missing skill. Ask the user. | +| `symlink-no-skillmd` | Hand off to `/lfx-new-skill` to scaffold the missing SKILL.md. | +| `clone-dirty` | Informational only. Mention that the user has uncommitted changes; don't act. | + +Always ask before editing files. Never edit `lfx/SKILL.md` (or anything else) silently. + +## Step 6: Re-verify + +After applying fixes, re-run: + +```bash +"$LFX_SKILLS_CLI" doctor --json +``` + +Show the new counts. Celebrate if everything's green; otherwise, note what's still outstanding and why. + +## Step 7: Close + +End with: + +> "Run `/lfx-skills-doctor` anytime to recheck. For installation changes, `/lfx-install` or `/lfx-skills-helper` is the right entry point." + +## What this skill does NOT do + +- Diagnose Claude Code plugin cache or marketplace state through the CLI. Use Claude Code plugin commands for that path. +- Install new skills or change install scope: that's `/lfx-install`. +- List, manage, or scaffold skills: `/lfx-skills-helper` and `/lfx-new-skill`. +- Modify `lfx/SKILL.md`'s routing table without asking the user first. +- Run destructive operations (force-removing real files, etc.) — only mechanical repairs the CLI considers safe. + +## Reference files + +- [`references/fix-recipes.md`](references/fix-recipes.md) — per-issue narrative, fix templates, and copy-pasteable snippets. diff --git a/.claude/skills/lfx-skills-doctor/references/fix-recipes.md b/.claude/skills/lfx-skills-doctor/references/fix-recipes.md new file mode 100644 index 0000000..33397f1 --- /dev/null +++ b/.claude/skills/lfx-skills-doctor/references/fix-recipes.md @@ -0,0 +1,203 @@ +<!-- Copyright The Linux Foundation and each contributor to LFX. --> +<!-- SPDX-License-Identifier: MIT --> + +# Fix Recipes + +Per-issue narrative for `/lfx-skills-doctor`. The CLI's `--fix` flag handles the mechanical cases; this file covers everything else, plus extra context the JSON output doesn't include. + +Each entry follows the same shape: + +- **What:** plain-language description of what's wrong +- **Why it matters:** consequence for the user +- **Fix this session:** quick one-shot +- **Fix permanently:** lasting change +- **Auto-fixable?** yes (CLI), yes (skill), or no + +--- + +## issue-id: no-config + +**What:** No `~/.lfx-skills/config.json`. +**Why it matters:** the CLI doesn't know what's installed where. Doctor can only check things relative to the manifest, so most other checks will be skipped. +**Fix:** run `lfx-skills install`. Or, inside the lfx-skills clone, run `/lfx-install` for a guided walkthrough. +**Auto-fixable?** no (requires user choices about scope / dev root). + +--- + +## issue-id: no-symlinks + +**What:** Manifest exists but is empty (no symlinks recorded). +**Why it matters:** no LFX skills are actually installed. +**Fix:** run `lfx-skills install` to add them. +**Auto-fixable?** no. + +--- + +## issue-id: symlink-missing + +**What:** A symlink the manifest expects is gone from disk. +**Why it matters:** the corresponding `/lfx-...` command won't be available in your AI tool. +**Fix this session:** `lfx-skills doctor --fix` (CLI recreates from the manifest). +**Auto-fixable?** yes (CLI). + +--- + +## issue-id: symlink-broken + +**What:** A symlink exists but points to a path that's no longer a directory. +**Why it matters:** same as above — the skill won't load. +**Common cause:** the lfx-skills clone moved or was deleted. +**Fix this session:** `lfx-skills doctor --fix`. +**Fix permanently:** if you moved the clone, run `lfx-skills install` again from the new location to re-record `canonical_clone`. +**Auto-fixable?** yes (CLI). + +--- + +## issue-id: symlink-no-skillmd + +**What:** A symlink target exists, but the directory has no `SKILL.md`. +**Why it matters:** the skill won't load (the loader requires `SKILL.md`). Usually means someone created an empty `lfx-foo/` directory by accident, or pulled an in-progress branch. +**Fix:** either delete the empty directory, or scaffold a real skill via `/lfx-new-skill`. +**Auto-fixable?** no by CLI; the `/lfx-skills-doctor` skill can hand off to `/lfx-new-skill`. + +--- + +## issue-id: clone-not-recorded / clone-mismatch + +**What:** The CLI doesn't know which clone is canonical, or the recorded path doesn't match where the script ran from. +**Why it matters:** `lfx-skills update` and other commands rely on `canonical_clone`. Symlinks may also be pointing to a different clone than the one you think you're working in. +**Fix:** re-run `lfx-skills install` from the clone you want to be canonical. +**Auto-fixable?** no. + +--- + +## issue-id: clone-dirty + +**What:** Your `lfx-skills` clone has uncommitted changes. +**Why it matters:** purely informational. If you ran `lfx-skills update --pull`, that would fail with this state. Otherwise no impact. +**Fix:** commit, stash, or discard, depending on intent. +**Auto-fixable?** no. + +--- + +## issue-id: dev-root-not-recorded + +**What:** No `lfx_dev_root` in the manifest. +**Why it matters:** skills like `/lfx-test-journey` and `/lfx-coordinator` can't auto-discover your local LFX repos. +**Fix:** `lfx-skills config set lfx_dev_root=/path/to/your/lfx-clones`. Or re-run `lfx-skills install`. +**Auto-fixable?** no (requires user input). + +--- + +## issue-id: dev-root-missing + +**What:** The recorded `lfx_dev_root` path doesn't exist on disk. +**Common cause:** moved your clones to a new location. +**Fix:** `lfx-skills config set lfx_dev_root=/new/path`. The CLI will rewrite `~/.lfx-skills/dev-root` automatically. +**Auto-fixable?** no. + +--- + +## issue-id: dev-root-empty + +**What:** `LFX_DEV_ROOT` exists but contains no `lf*` git repos. +**Why it matters:** the skills will run, but they won't find any local repos to work on (they'll fall back to GitHub API calls when possible). +**Fix:** clone the LFX repos you work on into that directory. Examples: + +```bash +cd "$LFX_DEV_ROOT" +git clone https://github.com/linuxfoundation/lfx-v2-ui.git +git clone https://github.com/linuxfoundation/lfx-v2-meeting-service.git +``` + +**Auto-fixable?** no. + +--- + +## issue-id: dev-root-file-missing + +**What:** `~/.lfx-skills/dev-root` is gone. +**Why it matters:** the 3 skills that need a local LFX path (`/lfx-coordinator`, `/lfx-research`, `/lfx-test-journey`) read this file via `cat` to resolve the dev root without depending on env vars. Without it, they fall back to `~/lf`, which may not match your setup. +**Fix:** `lfx-skills doctor --fix` regenerates it from the manifest. Or `lfx-skills config set lfx_dev_root=/your/path` if you also need to change the path. +**Auto-fixable?** yes (CLI). + +--- + +## issue-id: dev-root-file-mismatch + +**What:** The contents of `~/.lfx-skills/dev-root` differ from `lfx_dev_root` in `config.json`. +**Common cause:** the file was hand-edited, or an old `lfx-skills` version wrote one source but not the other. +**Fix:** `lfx-skills doctor --fix` rewrites the file from `config.json` (the source of truth). +**Auto-fixable?** yes (CLI). + +--- + +## issue-id: frontmatter-missing + +**What:** A `SKILL.md` doesn't start with `---` on line 1. +**Why it matters:** the skill loader will refuse to load it. The loader requires frontmatter as the very first thing in the file (no blank lines, no comments above). +**Fix:** insert a frontmatter block at the top with the skill `name`, `description`, and `allowed-tools`. Use `/lfx-new-skill` as a template, or copy the shape from a sibling skill. +**Auto-fixable?** no by CLI; the `/lfx-skills-doctor` skill can guide the rewrite. + +--- + +## issue-id: frontmatter-no-name + +**What:** Frontmatter present but the `name:` field is missing or empty. +**Fix:** add `name: <skill-directory-basename>` to the frontmatter. Loader will fail without it. +**Auto-fixable?** no by CLI; the `/lfx-skills-doctor` skill can patch it via Edit. + +--- + +## issue-id: frontmatter-name-mismatch + +**What:** `name:` in the SKILL.md doesn't match the directory basename. +**Why it matters:** loaders use the directory name to register the slash command, but read the frontmatter for description and tools. A mismatch is confusing and may cause routing issues. +**Fix:** make `name:` equal `basename "$skill_dir"`. +**Auto-fixable?** no by CLI; the `/lfx-skills-doctor` skill can patch via Edit. + +--- + +## issue-id: frontmatter-no-description + +**What:** No `description:` field, or it's empty. +**Why it matters:** the loader uses `description` to decide when to surface the skill. Missing description means the model has no context for *when* to invoke it. +**Fix:** write a one-paragraph description that includes 3–5 trigger phrases users might say. +**Auto-fixable?** no by CLI; the `/lfx-skills-doctor` skill can draft one from the SKILL.md body. + +--- + +## issue-id: license-missing + +**What:** A `SKILL.md` doesn't have the LFX copyright header in its first 4 lines. +**Why it matters:** CI's `license-header-check` job will fail. +**Fix:** add these lines as lines 2–3, immediately after the opening `---`: + +```yaml +--- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +name: ... +``` + +(The `#` comments are valid YAML comments. They satisfy the license check without breaking frontmatter parsing.) +**Auto-fixable?** no by CLI; the `/lfx-skills-doctor` skill can patch via Edit. + +--- + +## issue-id: routing-dangling + +**What:** `lfx/SKILL.md` mentions `/lfx-foo` but no `lfx-foo/` directory exists. +**Why it matters:** the user asks `/lfx` to route to `/lfx-foo` and gets a confused error. +**Fix:** either remove the dangling reference from `lfx/SKILL.md`, or create the missing skill via `/lfx-new-skill`. +**Auto-fixable?** no — needs your call on which. + +--- + +## issue-id: routing-uncovered + +**What:** A skill exists in the clone but `lfx/SKILL.md` doesn't route to it. +**Why it matters:** users typing `/lfx` won't find the skill via the plain-language router. They can still invoke it directly with `/lfx-foo`. +**Fix:** add an entry to `lfx/SKILL.md`'s routing table for the skill, including 1–2 example trigger phrases. +**Caveat:** internal-only skills (like `lfx-backend-builder` and `lfx-ui-builder`, which are only invoked by `/lfx-coordinator`) can legitimately stay out of the routing table. Use judgment. +**Auto-fixable?** no by CLI; the `/lfx-skills-doctor` skill can patch via Edit. diff --git a/.claude/skills/lfx-skills-helper/SKILL.md b/.claude/skills/lfx-skills-helper/SKILL.md new file mode 100644 index 0000000..711f5f0 --- /dev/null +++ b/.claude/skills/lfx-skills-helper/SKILL.md @@ -0,0 +1,100 @@ +--- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +name: lfx-skills-helper +description: > + Manage the agents.md LFX Skills installation via the lfx-skills CLI: list + what's installed, install or uninstall in this repo or globally, update from + upstream, view or change config, look up what a specific skill does, and + remove legacy Claude symlink installs. Use for "add lfx skills to this repo", + "what's installed", "update lfx skills", "show my lfx setup", "uninstall", + "remove old Claude symlinks", "what does /lfx-foo do". For Claude plugin + installs, explain the plugin marketplace path. For "which skill should I use + for X" or other plain-language routing questions, hand off to /lfx. For health + checks or repair, hand off to /lfx-skills-doctor. +allowed-tools: Bash, Read, Glob, Grep, AskUserQuestion +--- + +<!-- Tool names in this file use Claude Code vocabulary. See docs/tool-mapping.md for other platforms. --> + +# LFX Skills Helper + +You are the conversational front-end for the agents.md-only `lfx-skills` CLI: install, uninstall, update, list, info, config, and legacy Claude symlink cleanup. You are NOT a router. If the user asks "which skill should I use for X" or describes a task ("I need to add a feature", "review my PR"), hand off to `/lfx` — that's the plain-language router. Your job is skill *management*, not skill *discovery*. + +Claude Code is separate: it installs LFX Skills as a plugin with `/plugin marketplace add linuxfoundation/lfx-skills` and `/plugin install lfx-skills@lfx-skills`. Do not use the CLI to install Claude Code skills. The CLI can only remove old Claude symlink installs via `lfx-skills uninstall --legacy-claude-only` or as part of `lfx-skills uninstall --all`. + +## Step 1: Locate the CLI + +Same as `/lfx-skills-doctor`. Try in order: + +1. `command -v lfx-skills` (on PATH). +2. `jq -r .canonical_clone ~/.lfx-skills/config.json 2>/dev/null` then append `/cli/lfx-skills`. +3. `./cli/lfx-skills` if you're inside the lfx-skills clone. +4. Ask the user. + +If none works: the install was never run. Tell the user to clone `linuxfoundation/lfx-skills` and run `./install.sh` (or invoke `/lfx-install` if they're already in the clone). Stop. + +## Step 2: Classify the request + +Decide which surface the request belongs to: + +| User asks about… | Surface | Action | +|--------------------------------------------|------------------|-------------------------------------------| +| Installing, updating, removing, config | This skill | Map to a CLI subcommand (Step 3) | +| What's installed / available / where | This skill | Map to a CLI subcommand (Step 3) | +| What a specific skill does | This skill | `lfx-skills info <name>` | +| **Which skill** to use for a task | Hand off to `/lfx` | Stop here; let the router pick. | +| **Diagnose** a problem / "why isn't X working" | Hand off to `/lfx-skills-doctor` | Stop here. | +| **Scaffold a new skill** | Hand off to `/lfx-new-skill` | Stop here (clone-only). | + +Read `references/intents.md` once for the management-intent → CLI mapping. If the user's phrasing isn't in the table and doesn't fit your job either, say so and suggest the right entry point. + +## Step 3: Run the CLI + +Execute the chosen subcommand. Capture stdout. Use `--json` flags where available (currently `doctor --json`) when you need structured data. + +For commands that change state (`install`, `uninstall`, `update`, `config set`), **always confirm with the user first** via `AskUserQuestion`, showing exactly what you're about to run. The CLI's `--yes` flag is appropriate only after the user has confirmed in chat. + +## Step 4: Format the output + +The CLI output is structured but utilitarian. Reformat it for conversation: + +- **`lfx-skills list`** outputs `scope<TAB>skill<TAB>link`. Render as a friendly grouped list: + + ``` + Globally installed (agents.md): + /lfx + /lfx-coordinator + ... + + Per-repo (lfx-v2-meeting-service): + /lfx-coordinator + /lfx-pr-resolve + ... + ``` + +- **`lfx-skills info <skill>`** outputs frontmatter + install locations. Render the description in prose, list trigger phrases, summarise where it's installed. + +- **`lfx-skills config`** outputs raw JSON. Pretty-print it as a small table: dev root, canonical clone, total symlinks, and CLI symlink. + +- **`lfx-skills repos`** outputs one path per line. Group as a numbered list with sizes/last-modified if helpful. + +## Step 5: Hand-off rules + +If during the conversation the request shifts to something off your turf, hand off cleanly: + +- **Diagnostic questions** ("is my install OK?", "why isn't /lfx-foo working?", "fix my broken symlinks"): hand off to `/lfx-skills-doctor`. +- **Routing / discovery** ("which skill should I use for backend work?", "I want to add a feature, where do I start?"): hand off to `/lfx`. +- **Creating a new skill**: hand off to `/lfx-new-skill` (only available inside the lfx-skills clone). + +## What this skill does NOT do + +- **Pick which skill the user should use**: that's `/lfx`'s job. This skill manages the install; it doesn't recommend skills. +- **Diagnose problems**: hand off to `/lfx-skills-doctor`. +- **Scaffold new skills**: hand off to `/lfx-new-skill`. +- **Install/uninstall without confirmation**: always show the user the exact command first. +- **Invent categorisations**: skills aren't tagged backend/frontend/etc. anywhere in their metadata; don't pretend they are. + +## Reference files + +- [`references/intents.md`](references/intents.md) — management-intent → CLI command mapping. diff --git a/.claude/skills/lfx-skills-helper/references/intents.md b/.claude/skills/lfx-skills-helper/references/intents.md new file mode 100644 index 0000000..bd9bf68 --- /dev/null +++ b/.claude/skills/lfx-skills-helper/references/intents.md @@ -0,0 +1,73 @@ +<!-- Copyright The Linux Foundation and each contributor to LFX. --> +<!-- SPDX-License-Identifier: MIT --> + +# Intent → CLI mapping + +Reference for `/lfx-skills-helper`. Maps natural-language **management** intents to the corresponding `lfx-skills` CLI invocation. + +This file is for agents.md skill *management*: install, uninstall, update, list, info, config, and legacy Claude symlink cleanup. It is not a recommendation engine. Routing questions ("which skill should I use for X?") belong to `/lfx`. Diagnostic questions belong to `/lfx-skills-doctor`. Authoring belongs to `/lfx-new-skill`. + +When the user's phrasing isn't an exact match, infer the closest intent and confirm the chosen command before running anything stateful. + +## Listing + +| User says | Run | Notes | +|-------------------------------------------------|-----------------------------------------------------------|-----------------------------------------------------------------------------| +| "What lfx skills do I have here?" | `lfx-skills list --scope=repo --repo="$(pwd)"` | Per-repo install only | +| "What lfx skills are installed globally?" | `lfx-skills list --scope=global` | Across every recorded global config dir | +| "What lfx skills do I have anywhere?" | `lfx-skills list` | Both scopes combined | +| "What lfx skills are available in the clone?" | `lfx-skills list --available` | All installable skills, regardless of install state | + +## Inspecting one skill + +| User says | Run | Notes | +|-------------------------------------------------|-----------------------------------------------------------|-----------------------------------------------------------------------------| +| "What does /lfx-coordinator do?" | `lfx-skills info lfx-coordinator` | Strip the leading slash; pass the bare name | +| "Where is /lfx-coordinator installed?" | `lfx-skills info lfx-coordinator` | The output includes install locations | +| "Show me my full setup" | `lfx-skills config` | Pretty-print the JSON for the user | +| "Where is my LFX dev root?" | `lfx-skills config get lfx_dev_root` | | +| "Which clone of lfx-skills am I using?" | `lfx-skills config get canonical_clone` | | +| "What repos are in my LFX dev root?" | `lfx-skills repos` | | + +## Installing / changing scope + +Always confirm via `AskUserQuestion` before running. Show the exact command first. + +| User says | Run | +|-------------------------------------------------|--------------------------------------------------------------------------------------| +| "Add lfx skills to this repo" | Confirm, then `lfx-skills install --yes --scope=repo --repos="$(pwd)"`. Suggest `/lfx-skills-doctor` after. | +| "Remove lfx skills from this repo" | Confirm, then `lfx-skills uninstall --yes --scope=repo --repos="$(pwd)"` | +| "Install lfx skills globally for Claude" | Explain the Claude Code plugin path: `/plugin marketplace add linuxfoundation/lfx-skills`, then `/plugin install lfx-skills@lfx-skills` | +| "Add agents.md support" | Confirm, then `lfx-skills install --yes --scope=global` | +| "Install everything everywhere" | Confirm. Don't assume `--repos=`; ask the user which repos. | +| "Uninstall lfx skills" | Confirm, then `lfx-skills uninstall --yes --all` | + +## Maintenance + +| User says | Run | Notes | +|-------------------------------------------------|-----------------------------------------------------------|-----------------------------------------------------------------------------| +| "Update lfx skills" | Ask whether they mean Claude Code plugin or agents.md CLI install. For agents.md, run `lfx-skills update --pull`. | Suggest `/lfx-skills-doctor` after agents.md updates | +| "Update the Claude plugin" / "Update Claude skills" | Explain: `/plugin marketplace update lfx-skills`, then `/plugin update lfx-skills@lfx-skills` | Claude Code plugin updates are not handled by the CLI | +| "Re-apply my install" | `lfx-skills update` | No `--pull`; just refresh symlinks against the manifest | +| "Remove old Claude symlinks" | Confirm, then `lfx-skills uninstall --yes --legacy-claude-only` | Removes only lfx-skills-owned legacy Claude symlinks | +| "Remove lfx-skills completely" | Confirm, then `lfx-skills uninstall --yes --all` | Removes agents.md symlinks, legacy Claude symlinks, CLI symlink, config | +| "Update my LFX dev root" | Confirm new path, `lfx-skills config set lfx_dev_root=NEW_PATH` | Rewrites `~/.lfx-skills/dev-root` automatically | +| "Switch my Claude config dir" | Explain that Claude Code plugin scope is managed by Claude Code settings, not this CLI. | + +## Hand-offs (not your job) + +| User says | Hand off to | +|-------------------------------------------------|--------------------------------------------------------------------------------------| +| "Which skill should I use for X?" / task descriptions | `/lfx` | +| "Run a health check" / "is my install OK?" | `/lfx-skills-doctor` | +| "Why isn't /lfx-foo working?" | `/lfx-skills-doctor` | +| "Fix my broken symlinks" | `/lfx-skills-doctor` | +| "How do I create a new lfx skill?" | `/lfx-new-skill` (only inside the lfx-skills clone) | +| "Scaffold a new skill called lfx-foo" | `/lfx-new-skill` | + +## Disambiguation rules + +- The leading slash in user phrasing (e.g., `/lfx-coordinator`) is conversational; strip it when passing to the CLI. +- If the user says "skill X" without specifying scope, assume per-repo when they're inside a repo, global otherwise. Confirm before acting. +- If the user gives a custom command-line flag combo you don't recognise, defer to the CLI: `lfx-skills help <subcommand>`. +- If the user describes a *task* rather than asking about install state, that's a routing question — hand off to `/lfx` even mid-conversation. diff --git a/.github/workflows/license-header-check.yml b/.github/workflows/license-header-check.yml index bbfe404..fc7798e 100644 --- a/.github/workflows/license-header-check.yml +++ b/.github/workflows/license-header-check.yml @@ -12,4 +12,7 @@ permissions: jobs: license-header-check: name: License Header Check - uses: linuxfoundation/lfx-public-workflows/.github/workflows/license-header-check.yml@874b1c3f4e5d6789abcdeffedcba1234567890ab + uses: linuxfoundation/lfx-public-workflows/.github/workflows/license-header-check.yml@main + with: + copyright_line: "Copyright The Linux Foundation and each contributor to LFX." + include_files: "*.md" diff --git a/.gitignore b/.gitignore index 55cfde5..f33ee5d 100644 --- a/.gitignore +++ b/.gitignore @@ -16,11 +16,42 @@ .Trashes Thumbs.db -# Claude AI assistant -.claude +# Claude AI assistant — local state (settings.local.json, projects/, etc.) +# stays out of the repo. The four committed bootstrap skills below make this +# source repo usable in Claude Code without installing the published plugin. +.claude/* +!.claude/ +!.claude/skills/ +.claude/skills/* +!.claude/skills/lfx-install/ +!.claude/skills/lfx-install/** +!.claude/skills/lfx-skills-doctor/ +!.claude/skills/lfx-skills-doctor/** +!.claude/skills/lfx-skills-helper/ +!.claude/skills/lfx-skills-helper/** +!.claude/skills/lfx-new-skill/ +!.claude/skills/lfx-new-skill/** -# Installed skill symlinks (created by per-repo installation) -.claude/skills/ +# agents.md-compatible tools can use the four committed bootstrap skills in +# this repo without running the installer. Other generated per-repo skill +# files or symlinks stay local. +.agents/* +!.agents/ +!.agents/skills/ +.agents/skills/* +!.agents/skills/lfx-install/ +!.agents/skills/lfx-install/** +!.agents/skills/lfx-skills-doctor/ +!.agents/skills/lfx-skills-doctor/** +!.agents/skills/lfx-skills-helper/ +!.agents/skills/lfx-skills-helper/** +!.agents/skills/lfx-new-skill/ +!.agents/skills/lfx-new-skill/** + +# Legacy install manifest. Older clones of lfx-skills wrote this; the CLI +# stores its manifest at ~/.lfx-skills/config.json, so this file is no +# longer used. Ignored in case any clone still has one lying around. +.install-manifest.json # Environment and configuration files .env diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..551192f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,79 @@ +<!-- Copyright The Linux Foundation and each contributor to LFX. --> +<!-- SPDX-License-Identifier: MIT --> + +# LFX Skills repo + +You are inside the LFX Skills source repository. agents.md-compatible tools use the `cli/lfx-skills` CLI to install skills globally or per repo. Claude Code uses the `.claude-plugin/plugin.json` plugin manifest and does not use the CLI installer. + +The four repo helper skills live under `.agents/skills/` and `.claude/skills/` so agents can work on and contribute to this repository locally. They are not part of the published LFX Skills suite. + +- **`/lfx-install`** — guides users through agents.md setup after clone. Walks them through scope, config dirs, repos, and dev root, then runs the CLI installer. +- **`/lfx-skills-doctor`** — diagnoses problems with an existing install. Use when the user reports skills not loading, missing autocomplete entries, or unexpected behavior. +- **`/lfx-skills-helper`** — manages the install: lists what's installed, installs/uninstalls in this repo, updates from upstream, shows config. Skill management only — *not* a router. +- **`/lfx-new-skill`** — scaffolds a new skill in this repo. Use when the contributor wants to add a new lfx skill or asks "how do I create a new skill". + +The CLI itself is at `cli/lfx-skills`. Run `cli/lfx-skills help` for the full command reference. + +## Claude vs agents.md + +- Claude Code install/update happens through the in-repo plugin marketplace: `/plugin marketplace add linuxfoundation/lfx-skills`, then `/plugin install lfx-skills@lfx-skills`. +- agents.md install/update happens through `cli/lfx-skills`. +- The CLI has no platform picker anymore. It installs agents.md symlinks only. +- To remove old Claude symlink installs from before the plugin pivot, run `cli/lfx-skills uninstall --legacy-claude-only`. +- To remove the complete local agents.md install, CLI symlink, config, and legacy Claude symlinks owned by this clone, run `cli/lfx-skills uninstall --all`. + +## When to use which + +- "How do I install this?" / "I just cloned, what now?" → `/lfx-install` +- "Skills aren't loading" / "is my setup OK?" → `/lfx-skills-doctor` +- "What's installed?" / "add to this repo" / "what does X do?" → `/lfx-skills-helper` +- "Create a new skill called …" → `/lfx-new-skill` +- Anything else (modifying an existing skill, writing docs, reviewing changes): proceed normally — use the existing user-facing skills (`/lfx`, `/lfx-coordinator`, `/lfx-preflight`, etc.) as you would in any LFX repo. + +## Repo layout + +- `cli/lfx-skills` — multi-subcommand bash CLI. +- `lib/*.sh` — sourced by the CLI (probe, config, symlinks, doctor, ui, targets). +- `skills/lfx*/` — runtime LFX Skills suite. Each directory is one skill, with `SKILL.md` and optional `references/`. Everything here is exposed by the Claude plugin and installed by the agents.md CLI. +- `.agents/skills/{lfx-install,lfx-skills-doctor,lfx-skills-helper,lfx-new-skill}/` — committed repo-local helpers for agents.md-compatible tools working inside this source repo. The CLI installs only `lfx-skills-doctor` and `lfx-skills-helper` from here outside the repo. +- `.claude-plugin/plugin.json` — Claude Code plugin manifest. It exposes `./skills/` as the runtime suite and intentionally does not include repo/install/meta helpers. +- `.claude/skills/{lfx-install,lfx-skills-doctor,lfx-skills-helper,lfx-new-skill}/` — committed repo-local helpers for Claude Code working inside this source repo. These are not part of the distributed plugin. +- `.claude-plugin/marketplace.json` — Claude Code marketplace manifest. It lists the `lfx-skills` plugin and points its source at `"./"`. +- `install.sh` — thin shim that execs `cli/lfx-skills install "$@"`. +- `~/.lfx-skills/config.json` — agents.md install manifest written by the CLI (not in this repo). New installs do not record a platform. +- `~/.lfx-skills/dev-root` — single-line text file the 3 dev-root-aware skills `cat` to resolve `LFX_DEV_ROOT` without depending on shell env. +- `~/.local/bin/lfx-skills` (or `~/bin/...`, or `/usr/local/bin/...`) — symlink the installer creates so `lfx-skills` is on PATH everywhere. No shell rc edit needed. + +## Tool-name compatibility + +The skill bodies in this repo use Claude Code's tool vocabulary by default (Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion, Skill, WebFetch). When running under Codex, Gemini CLI, or OpenCode, treat them as the closest equivalent on your platform. See `docs/tool-mapping.md` if present. + +## Conventions + +- Every `SKILL.md` starts with `---` on line 1, with `# Copyright …` and `# SPDX-License-Identifier:` as YAML comments on lines 2–3. +- The `name:` frontmatter field equals the directory basename. +- DCO-signed and cryptographically signed manual commits are required (`git commit -s -S`). + +## Versioning + +- One skill change or a batch of skill changes can ship in the same release. +- Claude Code plugin changes must bump `.claude-plugin/plugin.json` `version`; otherwise Claude Code will keep using the cached plugin version. +- New LFX Skills suite skills go under `skills/`; repo/install/meta helpers for this source repo go under `.agents/skills/` and `.claude/skills/`. +- The marketplace follows the LFX Skills default branch and uses `"./"` as the local plugin source. + +Version bump guidelines: + +| Change type | Version component | +|---|---| +| Typo fixes, prompt wording tweaks, docs, installer fixes, CI fixes | **patch** | +| New skills, substantial skill behavior updates, new supported platform behavior | **minor** | +| Breaking command names, plugin name changes, removing or renaming skills, install layout breaks | **major** (only when explicitly instructed) | + +Bump the plugin version with the skill changes: + +```bash +git add .claude-plugin/plugin.json skills/<changed-skill> +git commit -s -S -m "feat: update lfx skills plugin" +``` + +After the change is on `main`, Claude users update with `/plugin marketplace update lfx-skills` and `/plugin update lfx-skills@lfx-skills`. agents.md users update with `lfx-skills update --pull` and `lfx-skills doctor`. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..9bb1ae4 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,75 @@ +<!-- Copyright The Linux Foundation and each contributor to LFX. --> +<!-- SPDX-License-Identifier: MIT --> + +# LFX Skills repo + +You are inside the LFX Skills source repository. Claude Code users install the runtime skills through the `.claude-plugin/plugin.json` plugin manifest; agents.md-compatible tools use the `cli/lfx-skills` CLI. Claude Code does not use the CLI installer. + +The four repo helper skills live under `.agents/skills/` and `.claude/skills/` so agents can work on and contribute to this repository locally. They are not part of the published LFX Skills suite. + +- **`/lfx-install`** — guides users through agents.md setup after clone. Walks them through scope, config dirs, repos, and dev root, then runs the CLI installer. +- **`/lfx-skills-doctor`** — diagnoses problems with an existing install. Use when the user reports skills not loading, missing autocomplete entries, or unexpected behavior. +- **`/lfx-skills-helper`** — manages the install: lists what's installed, installs/uninstalls in this repo, updates from upstream, shows config. Skill management only — *not* a router. +- **`/lfx-new-skill`** — scaffolds a new skill in this repo. Use when the contributor wants to add a new lfx skill or asks "how do I create a new skill". + +The CLI itself is at `cli/lfx-skills`. Run `cli/lfx-skills help` for the full command reference. + +## Claude vs agents.md + +- Claude Code install/update happens through the in-repo plugin marketplace: `/plugin marketplace add linuxfoundation/lfx-skills`, then `/plugin install lfx-skills@lfx-skills`. +- agents.md install/update happens through `cli/lfx-skills`. +- The CLI has no platform picker anymore. It installs agents.md symlinks only. +- To remove old Claude symlink installs from before the plugin pivot, run `cli/lfx-skills uninstall --legacy-claude-only`. +- To remove the complete local agents.md install, CLI symlink, config, and legacy Claude symlinks owned by this clone, run `cli/lfx-skills uninstall --all`. + +## When to use which + +- "How do I install this?" / "I just cloned, what now?" → `/lfx-install` +- "Skills aren't loading" / "is my setup OK?" → `/lfx-skills-doctor` +- "What's installed?" / "add to this repo" / "what does X do?" → `/lfx-skills-helper` +- "Create a new skill called …" → `/lfx-new-skill` +- Anything else (modifying an existing skill, writing docs, reviewing changes): proceed normally — use the existing user-facing skills (`/lfx`, `/lfx-coordinator`, `/lfx-preflight`, etc.) as you would in any LFX repo. + +## Repo layout + +- `cli/lfx-skills` — multi-subcommand bash CLI. +- `lib/*.sh` — sourced by the CLI (probe, config, symlinks, doctor, ui, targets). +- `skills/lfx*/` — runtime LFX Skills suite. Each directory is one skill, with `SKILL.md` and optional `references/`. Everything here is exposed by the Claude plugin and installed by the agents.md CLI. +- `.agents/skills/{lfx-install,lfx-skills-doctor,lfx-skills-helper,lfx-new-skill}/` — committed repo-local helpers for agents.md-compatible tools working inside this source repo. The CLI installs only `lfx-skills-doctor` and `lfx-skills-helper` from here outside the repo. +- `.claude-plugin/plugin.json` — Claude Code plugin manifest. It exposes `./skills/` as the runtime suite and intentionally does not include repo/install/meta helpers. +- `.claude/skills/{lfx-install,lfx-skills-doctor,lfx-skills-helper,lfx-new-skill}/` — committed repo-local helpers for Claude Code working inside this source repo. These are not part of the distributed plugin. +- `.claude-plugin/marketplace.json` — Claude Code marketplace manifest. It lists the `lfx-skills` plugin and points its source at `"./"`. +- `install.sh` — thin shim that execs `cli/lfx-skills install "$@"`. +- `~/.lfx-skills/config.json` — agents.md install manifest written by the CLI (not in this repo). New installs do not record a platform. +- `~/.lfx-skills/dev-root` — single-line text file the 3 dev-root-aware skills `cat` to resolve `LFX_DEV_ROOT` without depending on shell env. +- `~/.local/bin/lfx-skills` (or `~/bin/...`, or `/usr/local/bin/...`) — symlink the installer creates so `lfx-skills` is on PATH everywhere. No shell rc edit needed. + +## Conventions + +- Every `SKILL.md` starts with `---` on line 1, with `# Copyright …` and `# SPDX-License-Identifier:` as YAML comments on lines 2–3. +- The `name:` frontmatter field equals the directory basename. +- DCO-signed and cryptographically signed manual commits are required (`git commit -s -S`). + +## Versioning + +- One skill change or a batch of skill changes can ship in the same release. +- Claude Code plugin changes must bump `.claude-plugin/plugin.json` `version`; otherwise Claude Code will keep using the cached plugin version. +- New LFX Skills suite skills go under `skills/`; repo/install/meta helpers for this source repo go under `.agents/skills/` and `.claude/skills/`. +- The marketplace follows the LFX Skills default branch and uses `"./"` as the local plugin source. + +Version bump guidelines: + +| Change type | Version component | +|---|---| +| Typo fixes, prompt wording tweaks, docs, installer fixes, CI fixes | **patch** | +| New skills, substantial skill behavior updates, new supported platform behavior | **minor** | +| Breaking command names, plugin name changes, removing or renaming skills, install layout breaks | **major** (only when explicitly instructed) | + +Bump the plugin version with the skill changes: + +```bash +git add .claude-plugin/plugin.json skills/<changed-skill> +git commit -s -S -m "feat: update lfx skills plugin" +``` + +After the change is on `main`, Claude users update with `/plugin marketplace update lfx-skills` and `/plugin update lfx-skills@lfx-skills`. agents.md users update with `lfx-skills update --pull` and `lfx-skills doctor`. diff --git a/README.md b/README.md index 6e021b2..cedbdd8 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,71 @@ +<!-- Copyright The Linux Foundation and each contributor to LFX. --> +<!-- SPDX-License-Identifier: MIT --> + # LFX Skills A collection of AI coding skills that encode the full development workflow for the LFX Self-Service platform. These skills turn your AI coding assistant into a context-aware development partner that understands LFX conventions, architecture, and code patterns — eliminating the need to repeatedly explain project structure, naming rules, or coding standards. ## Quick Install +### Claude Code + +Install the Claude Code plugin from the LFX marketplace: + +```text +/plugin marketplace add linuxfoundation/lfx-skills +/plugin install lfx-skills@lfx-skills +``` + +After installation, start with the `lfx` skill. In Claude Code that command is namespaced as `/lfx-skills:lfx`; describe what you want in plain language and it will route you to the right workflow. + +The marketplace metadata lives in `.claude-plugin/marketplace.json` in LFX Skills. It lists the local plugin source as `"./"`; the Claude plugin version is tracked in `.claude-plugin/plugin.json`. + +The Claude Code plugin is skills-only: it exposes the runtime LFX Skills suite from `skills/` and does not install or expose the CLI. + +If you previously installed LFX Skills into Claude Code with symlinks: + +- Clone this repo. +- Start your coding agent in the clone. +- Ask it: `Uninstall the legacy Claude setup for LFX Skills.` + +Manual fallback: + +```bash +./cli/lfx-skills uninstall --legacy-claude-only +``` + +### agents.md-Compatible Tools + +For Codex, Gemini CLI, OpenCode, and other agents.md-compatible tools, the setup is agent-first: + +```bash +git clone https://github.com/linuxfoundation/lfx-skills.git +cd lfx-skills +``` + +Start your coding agent in the cloned repo and ask it to set up LFX Skills. This repo ships four local helper skills out of the box through committed `.agents/skills/` and `.claude/skills/` files, so agents can work on and contribute to this repository without running the installer first: + +- `lfx-install` — guided first-time setup +- `lfx-skills-doctor` — install health checks and repair guidance +- `lfx-skills-helper` — list, update, uninstall, and inspect the setup +- `lfx-new-skill` — scaffold a new skill in this repo + +These helpers are local to this source repo. They are not part of the published LFX Skills suite. + +If you prefer to run the installer manually: + ```bash -git clone https://github.com/linuxfoundation/skills.git -cd skills ./install.sh ``` -Then restart your AI coding assistant, open any LFX repo, and type `/lfx` to get started. +The CLI installs skill symlinks into agents.md skill directories outside this repo and records the install in `~/.lfx-skills/config.json`. +agents.md installs include the runtime suite from `skills/` plus `/lfx-skills-doctor` and `/lfx-skills-helper` from `.agents/skills/`. `/lfx-install` and `/lfx-new-skill` stay local to this clone. + +After setup: + +- Restart your AI coding assistant. +- Open any LFX repo. +- Type `/lfx` to get started. ## How It Works @@ -24,43 +79,28 @@ The `/lfx` skill auto-detects your repo, branch, and context, then routes you to New to LFX development? Type `/lfx` and say **"show me an example"** for a walkthrough. -## Prerequisites +## Authoring Skills -- An AI coding assistant that supports skill-based workflows (e.g., Claude Code, Gemini CLI). See [docs/platform-install.md](docs/platform-install.md) for setup instructions. -- Access to LFX repositories (for the skills to operate on) +New skills should be authored from the cloned LFX Skills repo. Start your coding agent in this repo and use `/lfx-new-skill`; it guides the authoring flow, creates the `skills/lfx-<name>/SKILL.md` structure, applies the repo conventions, and explains how to test the skill locally. -## Manual Installation +Use `/lfx-new-skill` whether you are starting from an idea or from a fully written skill. If you already have the skill body, give it to the agent and it should preserve the content while normalizing the repo-specific details such as frontmatter, license header, path, and validation. -> **Note:** These manual instructions are for Claude Code. For other platforms, see [docs/platform-install.md](docs/platform-install.md). - -If you prefer to install manually instead of using `./install.sh`: - -### Step 1: Clone this repo - -```bash -git clone https://github.com/linuxfoundation/skills.git -``` +The authoring helper keeps Claude Code plugin testing separate from agents.md testing. For Claude Code, it points you at plugin validation and local `--plugin-dir` testing. For agents.md-compatible tools, it points you at the CLI update flow. It also offers to help prepare a signed commit with `git commit -s -S`. -### Step 2: Install the skills +All new LFX Skills suite skills go under `skills/`. Note that Repo/install/meta helpers that are not part of the LFX Skills suite, i.e. used only for working on this repository go under `.agents/skills/` and `.claude/skills/`. -Claude Code auto-discovers skills from `~/.claude/skills/`. Symlink each skill: - -```bash -# From the cloned repo directory -mkdir -p ~/.claude/skills -for skill in lfx-*/ lfx/; do - ln -sf "$(pwd)/$skill" ~/.claude/skills/"$(basename "$skill")" -done -``` +## Prerequisites -This makes all `/lfx*` skills available globally. +- An AI coding assistant that supports skill-based workflows. Claude Code uses the plugin path; Codex, Gemini CLI, OpenCode, and similar tools use the CLI installer. +- Access to LFX repositories (for the skills to operate on) +- **Optional: `LFX_DEV_ROOT`** — environment variable pointing to the directory where you keep your LFX repo clones. Defaults to `~/lf/`. The CLI records the chosen path in `~/.lfx-skills/dev-root` so skills can discover local repos without shell rc edits. Skills that use this will prompt the user to either set it or use default. -### Step 3: Verify +## Verify -Restart your AI coding assistant (or open a new session) in any LFX repo and type `/lfx` — you should see all skills in the autocomplete list: +Restart your AI coding assistant (or open a new session) in any LFX repo and type `/lfx`. ``` -/lfx ← start here (plain-language entry point) +/lfx ← start here (plain-language entry point) /lfx-coordinator /lfx-research /lfx-backend-builder @@ -68,35 +108,71 @@ Restart your AI coding assistant (or open a new session) in any LFX repo and typ /lfx-product-architect /lfx-preflight /lfx-pr-catchup +/lfx-pr-resolve /lfx-setup /lfx-test-journey +/lfx-git-setup /lfx-intercom +/lfx-snowflake-access +/lfx-cdp-snowflake-connectors ``` -### Alternative: Per-repo installation +agents.md CLI installs also include: -If you prefer skills scoped to a specific repo instead of global: +``` +/lfx-skills-doctor ← health check + auto-fix for the install +/lfx-skills-helper ← manage what's installed where (install/uninstall/update/list) +``` -```bash -# From inside a target repo (e.g., lfx-v2-ui) -mkdir -p .claude/skills -for skill in /path/to/skills/lfx-*/ /path/to/skills/lfx/; do - ln -sf "$skill" .claude/skills/"$(basename "$skill")" -done +## Versioning and Updates -# Keep symlinks out of version control -echo '.claude/skills/' >> .gitignore -``` +Skill changes can be shipped one at a time or batched together. For Claude Code users to receive plugin changes, update the `version` field in `.claude-plugin/plugin.json` before merging or pushing the change to `main`. + +The marketplace follows the LFX Skills default branch. Because `.claude-plugin/plugin.json` defines `version`, Claude Code uses that value for cache and update detection. If skill content changes but the plugin version stays the same, Claude Code will keep using the already cached plugin version. -### Uninstall +For Claude Code users to receive suite changes, bump `version` using the patch/minor/major guidance below. -> **Note:** These instructions are for Claude Code. For other platforms, remove the skill references from your tool's configuration. +### Version bump guidelines + +| Change type | Version component | +| ----------------------------------------------------------------------------------------------- | ------------------------------------------- | +| Typo fixes, prompt wording tweaks, docs, installer fixes, CI fixes | **patch** | +| New skills, substantial skill behavior updates, new supported platform behavior | **minor** | +| Breaking command names, plugin name changes, removing or renaming skills, install layout breaks | **major** (only when explicitly instructed) | + +Update `.claude-plugin/plugin.json`: + +```json +{ + "version": "0.1.0" +} +``` + +Commit the version bump with the skill changes, using DCO and cryptographic signing: ```bash -rm -f ~/.claude/skills/lfx-* -rm -f ~/.claude/skills/lfx +git add .claude-plugin/plugin.json skills/<changed-skill> +git commit -s -S -m "feat: update lfx skills plugin" ``` +After the change is on `main`, Claude Code users update from Claude: + +```text +/plugin marketplace update lfx-skills +/plugin update lfx-skills@lfx-skills +``` + +They can also enable auto-update. + +For agents.md-compatible installs: + +- Ask your coding agent to update LFX Skills, or run: + + ```bash + lfx-skills update --pull + lfx-skills doctor + ``` + ## Architecture The skills form a layered system where each skill has a clear responsibility and mode of operation: @@ -122,20 +198,23 @@ The skills form a layered system where each skill has a clear responsibility and ## Skill Overview -| Skill | Purpose | Mode | Tools | -|-------|---------|------|-------| -| **`/lfx`** | **Start here.** Describe what you want in plain language — auto-detects context and routes to the right skill | Router | Bash, Read, Glob, Grep, AskUserQuestion, **Skill** | -| `/lfx-coordinator` | Orchestrates full feature development — researches, plans, delegates to builders in parallel, validates | Read + delegate | Bash, Read, Glob, Grep, AskUserQuestion, **Skill** | -| `/lfx-research` | Explores upstream APIs, discovers code patterns, reads architecture docs, validates contracts via MCP | Read-only | Bash, Read, Glob, Grep, AskUserQuestion, **WebFetch** | -| `/lfx-backend-builder` | Generates Express.js proxy endpoints, Go microservice code, shared types. Encodes three-file pattern, logging, Goa DSL, NATS messaging | Code gen | Bash, Read, **Write, Edit**, Glob, Grep, AskUserQuestion | -| `/lfx-ui-builder` | Generates Angular 20 components, services, drawers, pagination UI, styling. Encodes signal patterns, PrimeNG wrappers | Code gen | Bash, Read, **Write, Edit**, Glob, Grep, AskUserQuestion | -| `/lfx-product-architect` | Answers "where should this go?", traces data flows, makes placement decisions, explains design patterns | Read-only | Bash, Read, Glob, Grep, AskUserQuestion | -| `/lfx-preflight` | Pre-PR validation — Phase 1 auto-fixes (format, license, lint, build), Phase 2 code review (15 report-only checks for Angular). Pass `--skip-review` to skip Phase 2 | Validate + review | Bash, Read, **Write, Edit**, Glob, Grep, AskUserQuestion | -| `/lfx-pr-catchup` | Morning PR dashboard — unresolved comments, status changes, stale PRs, approved-but-not-merged across all your open PRs | Read-only | Bash, Read, Glob, Grep, AskUserQuestion | -| `/lfx-setup` | Environment setup — prerequisites, clone, install, env vars, dev server. Adapts to Angular or Go repos | Interactive guide | Bash, Read, Glob, Grep, AskUserQuestion | -| `/lfx-test-journey` | Combine branches from multiple repos into worktrees for journey testing | Interactive | Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion | -| `/lfx-intercom` | Add or fix Intercom integration against the LFX canonical pattern — audits JWT setup, shutdown, Auth0 claim, app IDs, CSP | Audit + fix | Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion | -| `/lfx-cdp-snowflake-connectors` | Streamlines adding a new Snowflake connector to CDP — requires knowledge of the source specs | Interactive and guided | Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion | +| Skill | Purpose | Mode | Tools | +| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ---------------------------------------------------------------------------- | +| **`/lfx`** | **Start here.** Describe what you want in plain language — auto-detects context and routes to the right skill | Router | Bash, Read, Glob, Grep, AskUserQuestion, **Skill** | +| `/lfx-coordinator` | Orchestrates full feature development — researches, plans, delegates to builders in parallel, validates | Read + delegate | Bash, Read, Glob, Grep, AskUserQuestion, **Skill** | +| `/lfx-research` | Explores upstream APIs, discovers code patterns, reads architecture docs, validates contracts via MCP | Read-only | Bash, Read, Glob, Grep, AskUserQuestion, **WebFetch** | +| `/lfx-backend-builder` | Generates Express.js proxy endpoints, Go microservice code, shared types. Encodes three-file pattern, logging, Goa DSL, NATS messaging | Code gen | Bash, Read, **Write, Edit**, Glob, Grep, AskUserQuestion | +| `/lfx-ui-builder` | Generates Angular 20 components, services, drawers, pagination UI, styling. Encodes signal patterns, PrimeNG wrappers | Code gen | Bash, Read, **Write, Edit**, Glob, Grep, AskUserQuestion | +| `/lfx-product-architect` | Answers "where should this go?", traces data flows, makes placement decisions, explains design patterns | Read-only | Bash, Read, Glob, Grep, AskUserQuestion | +| `/lfx-preflight` | Pre-PR validation — Phase 1 auto-fixes (format, license, lint, build), Phase 2 code review (15 report-only checks for Angular). Pass `--skip-review` to skip Phase 2 | Validate + review | Bash, Read, **Write, Edit**, Glob, Grep, AskUserQuestion | +| `/lfx-pr-catchup` | Morning PR dashboard — unresolved comments, status changes, stale PRs, approved-but-not-merged across all your open PRs | Read-only | Bash, Read, Glob, Grep, AskUserQuestion | +| `/lfx-pr-resolve` | Address PR review feedback end-to-end — fetches threads, makes changes, commits, responds per-thread, resolves, posts summary | Audit + fix | Bash, Read, **Write, Edit**, Glob, Grep, AskUserQuestion, **Skill** | +| `/lfx-setup` | Environment setup — prerequisites, clone, install, env vars, dev server. Adapts to Angular or Go repos | Interactive guide | Bash, Read, Glob, Grep, AskUserQuestion | +| `/lfx-test-journey` | Combine branches from multiple repos into worktrees for journey testing | Interactive | Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion | +| `/lfx-git-setup` | Interactive setup for DCO sign-off and GPG-signed commits. Use when commits aren't showing as Verified or onboarding to LFX | Interactive guide | Bash, Read, Glob, Grep, AskUserQuestion, **WebFetch** | +| `/lfx-intercom` | Add or fix Intercom integration against the LFX canonical pattern — audits JWT setup, shutdown, Auth0 claim, app IDs, CSP | Audit + fix | Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion | +| `/lfx-snowflake-access` | Guide users requesting Snowflake access — generates Terraform HCL for `users.tf` or `service_accounts.tf` and walks through the PR | Interactive guide | Bash, Read, Glob, Grep, AskUserQuestion, **WebFetch** | +| `/lfx-cdp-snowflake-connectors` | Streamlines adding a new Snowflake connector to CDP — requires knowledge of the source specs. Requires LFX BI Layer MCP server | Interactive and guided | Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion, **MCP (LFX BI Layer)** | > **Note:** Tool names in the table above follow Claude Code conventions. See [docs/tool-mapping.md](docs/tool-mapping.md) for equivalents on other platforms. @@ -148,6 +227,7 @@ The skills form a layered system where each skill has a clear responsibility and The top-level orchestrator for any feature development. It **never writes code directly** — instead, it researches the codebase, builds a delegation plan, and invokes `/lfx-backend-builder` and `/lfx-ui-builder` in parallel. **Workflow:** + 1. **Setup** — detects repo type (Angular or Go), checks/creates feature branch 2. **Plan** — determines scope, build order (upstream Go → shared types → Express proxy → frontend) 3. **Research** — inline exploration (5–10 tool calls) to find existing patterns, upstream APIs, file paths @@ -157,6 +237,7 @@ The top-level orchestrator for any feature development. It **never writes code d 7. **Summary** — structured completion report with files changed, validation results, and next steps **Key behaviors:** + - Identifies the upstream Go service by reading Express proxy code API paths (e.g., `/committees/...` → `lfx-v2-committee-service`) - Includes upstream Go service changes when the data model needs modification - Handles validation failures by re-invoking only the skill that owns the broken file @@ -169,6 +250,7 @@ The top-level orchestrator for any feature development. It **never writes code d A **read-only** exploration agent that gathers all context needed before code generation. Returns structured, compact findings (under 30 lines) that the coordinator consumes. **Research tasks:** + - **Upstream API validation** — reads OpenAPI specs via `gh api` or local files to check if endpoints/fields exist - **Codebase exploration** — finds existing services, components, controllers, domain models - **Architecture doc reading** — checks placement rules, protected files, dependencies @@ -177,16 +259,16 @@ A **read-only** exploration agent that gathers all context needed before code ge **Upstream service mapping:** -| Domain | Repo | -|--------|------| -| Committees | `lfx-v2-committee-service` | -| Meetings | `lfx-v2-meeting-service` | -| Voting | `lfx-v2-voting-service` | +| Domain | Repo | +| ------------- | ----------------------------- | +| Committees | `lfx-v2-committee-service` | +| Meetings | `lfx-v2-meeting-service` | +| Voting | `lfx-v2-voting-service` | | Mailing Lists | `lfx-v2-mailing-list-service` | -| Members | `lfx-v2-member-service` | -| Projects | `lfx-v2-project-service` | -| Surveys | `lfx-v2-survey-service` | -| Queries | `lfx-v2-query-service` | +| Members | `lfx-v2-member-service` | +| Projects | `lfx-v2-project-service` | +| Surveys | `lfx-v2-survey-service` | +| Queries | `lfx-v2-query-service` | --- @@ -195,6 +277,7 @@ A **read-only** exploration agent that gathers all context needed before code ge Generates **PR-ready backend code** for both the Express.js proxy layer (in `lfx-v2-ui`) and Go microservices (in `lfx-v2-*-service` repos). Always reads target files before generating code — never works from memory alone. **Express.js proxy (Angular repo):** + - Follows the **three-file pattern**: service → controller → route - Services use `MicroserviceProxyService` for all upstream calls (never raw `fetch`/`axios`) - Controllers use `logger.startOperation()` / `logger.success()` / `logger.error()` lifecycle @@ -202,6 +285,7 @@ Generates **PR-ready backend code** for both the Express.js proxy layer (in `lfx - Encodes logging conventions, error handling (`next(error)`, never `res.status(500)`), pagination (`page_size`), and auth defaults (user bearer token) **Go microservices:** + - Goa v3 DSL for API design (`cmd/{service}/design/`) with `make apigen` for code generation - Domain models in `internal/domain/model/` with `Tags()` method for OpenSearch indexing - NATS messaging — publish index + access messages on every write operation @@ -210,18 +294,18 @@ Generates **PR-ready backend code** for both the Express.js proxy layer (in `lfx **Reference docs included:** -| Reference | Content | -|-----------|---------| -| `getting-started.md` | Repo map, deployment overview, local dev setup | -| `goa-patterns.md` | Goa DSL conventions, `make apigen`, ETag/If-Match optimistic locking | -| `nats-messaging.md` | Subject naming, service-to-service communication, KV storage | +| Reference | Content | +| --------------------- | --------------------------------------------------------------------- | +| `getting-started.md` | Repo map, deployment overview, local dev setup | +| `goa-patterns.md` | Goa DSL conventions, `make apigen`, ETag/If-Match optimistic locking | +| `nats-messaging.md` | Subject naming, service-to-service communication, KV storage | | `indexer-patterns.md` | IndexerMessageEnvelope, IndexingConfig, OpenSearch document structure | -| `fga-patterns.md` | OpenFGA tuples, permission inheritance, debugging access | -| `service-types.md` | Native vs wrapper services, which template to follow | -| `query-service.md` | Query service API, OpenSearch queries, FGA-based filtering | -| `helm-chart.md` | Deployment, HTTPRoute, Heimdall rules, KV buckets, secrets | -| `new-service.md` | End-to-end checklist for building a new resource service | -| `backend-endpoint.md` | Three-file pattern, authentication, pagination, error handling | +| `fga-patterns.md` | OpenFGA tuples, permission inheritance, debugging access | +| `service-types.md` | Native vs wrapper services, which template to follow | +| `query-service.md` | Query service API, OpenSearch queries, FGA-based filtering | +| `helm-chart.md` | Deployment, HTTPRoute, Heimdall rules, KV buckets, secrets | +| `new-service.md` | End-to-end checklist for building a new resource service | +| `backend-endpoint.md` | Three-file pattern, authentication, pagination, error handling | --- @@ -230,6 +314,7 @@ Generates **PR-ready backend code** for both the Express.js proxy layer (in `lfx Generates **PR-ready Angular 20 frontend code** — components, services, drawers, pagination, and styling. Only activates in Angular repos. **Components:** + - Standalone with direct imports (no barrel exports) - Strict 11-section class structure: injections → inputs → forms → model signals → writable signals → computed/toSignal → constructor → public methods → protected methods → private init functions → private helpers - Signal-based reactivity: `signal()`, `input()`, `output()`, `computed()`, `model()`, `toSignal()` @@ -237,25 +322,28 @@ Generates **PR-ready Angular 20 frontend code** — components, services, drawer - PrimeNG components wrapped with `lfx-` prefix and `descendants: false` on `@ContentChild` **Services:** + - `@Injectable({ providedIn: 'root' })` with `inject(HttpClient)` - GET requests: `catchError(() => of(default))` for graceful degradation - POST/PUT/DELETE: `take(1)`, let errors propagate - Interfaces from `@lfx-one/shared/interfaces`, relative API paths (`/api/...`) **Drawers:** + - `model<boolean>(false)` for visibility - Lazy data loading via `toObservable(visible).pipe(skip(1), switchMap(...))` - `forkJoin` for parallel API calls, responsive width classes **Pagination:** + - Infinite scroll with `page_token`, `scan()` accumulator, separate first-page and next-page streams **Reference docs included:** -| Reference | Content | -|-----------|---------| +| Reference | Content | +| ----------------------- | -------------------------------------------------------------------------------------- | | `frontend-component.md` | Component placement, class structure, signal types, template rules, drawer conventions | -| `frontend-service.md` | Service patterns, state management, signals vs RxJS guidance | +| `frontend-service.md` | Service patterns, state management, signals vs RxJS guidance | --- @@ -264,6 +352,7 @@ Generates **PR-ready Angular 20 frontend code** — components, services, drawer A **read-only** advisory skill that answers architectural questions without generating code. Provides decision trees, data flow traces, and placement recommendations. **Decision trees:** + - "Where does my component go?" — route vs module-specific vs shared vs PrimeNG wrapper - "Do I need a new module?" — distinct domain + own routes + enough isolation - "Where does my type go?" — shared package vs local definition @@ -272,6 +361,7 @@ A **read-only** advisory skill that answers architectural questions without gene - "User token or M2M token?" — default to user bearer, M2M only for public/privileged calls **Data flow tracing:** + - Frontend → Backend → Upstream: Angular component → HttpClient → Express proxy → MicroserviceProxyService → Go microservice - Write flow: HTTP → Heimdall auth → Goa handler → Storage → concurrent NATS publish (index + FGA) - Read flow: query-service → OpenSearch → batch FGA check → filtered results @@ -285,6 +375,7 @@ A **read-only** advisory skill that answers architectural questions without gene Runs a two-phase **pre-PR validation**. Adapts all checks to the repo type. **Phase 1: Validation (auto-fix):** + 1. **Working tree status** — uncommitted changes, commits ahead of main, JIRA references, `--signoff` 2. **License headers** — verifies and auto-fixes missing headers on `.ts`, `.html`, `.scss`, `.go` files 3. **Formatting** — `yarn format` (Angular) or `gofmt -w .` (Go), reports which files changed @@ -307,6 +398,7 @@ Runs a two-phase **pre-PR validation**. Adapts all checks to the repo type. A **read-only** morning dashboard that shows all your open PRs across repos, classified by urgency. **Workflow:** + 1. **Auth check** — verifies `gh auth status` 2. **Config** — uses defaults immediately (org filter and stale threshold can be passed inline, e.g., `/lfx-pr-catchup linuxfoundation`) 3. **Fetch** — `gh search prs --author=@me --state=open` (up to 50) @@ -324,16 +416,20 @@ A **read-only** morning dashboard that shows all your open PRs across repos, cla Combines feature branches from one or more repos into isolated git worktrees for end-to-end journey testing. Use this when you have a user journey spread across multiple PRs/branches and need to test everything together before merging. **Quick start:** + ``` /lfx-test-journey ``` + This starts the interactive create flow: -1. Select which repos are involved (auto-discovers repos in `~/lf/`) + +1. Select which repos are involved (auto-discovers repos in `$LFX_DEV_ROOT`, defaults to `~/lf/`) 2. Pick branches to include per repo (shows your unmerged branches) 3. Name the journey 4. The skill creates worktrees, merges branches, and tells you exactly where to `cd` and how to run the app **After creating a journey:** + ```bash # Go to the worktree and run the app as usual cd ~/.lfx-journeys/<journey-name>/<repo-name>/ @@ -341,6 +437,7 @@ yarn start # (Angular) or go run cmd/*/main.go (Go) ``` **Managing journeys:** + ``` /lfx-test-journey list # See all active journeys /lfx-test-journey status # Check if branches have new commits @@ -358,6 +455,7 @@ yarn start # (Angular) or go run cmd/*/main.go (Go) An **interactive setup guide** that walks through environment configuration step by step, verifying each step before proceeding. **Angular repo setup (lfx-v2-ui):** + 1. Prerequisites: Node.js v22+, Yarn v4.9.2+, Git 2. Clone the repository 3. Environment variables from `.env.example` + 1Password credentials @@ -366,6 +464,7 @@ An **interactive setup guide** that walks through environment configuration step 6. Verification with HTTP status check **Go microservice setup:** + 1. Prerequisites: Go 1.22+, Git, Make (optional: Helm, Docker) 2. Clone the repository 3. Environment variables for local or shared dev stack @@ -381,6 +480,7 @@ An **interactive setup guide** that walks through environment configuration step ## Typical Workflows ### Start here — just describe what you want + ``` /lfx → "Add a bio field to committee members" → auto-routes to coordinator → builds → validates → PR /lfx → "How does meeting data work?" → auto-routes to product architect → explains architecture @@ -388,32 +488,38 @@ An **interactive setup guide** that walks through environment configuration step ``` ### Build a new feature end-to-end + ``` /lfx-coordinator → researches → plans → delegates to /lfx-backend-builder + /lfx-ui-builder → validates → /lfx-preflight → PR ``` ### Understand the architecture before coding + ``` /lfx-product-architect → "where should this component go?" / "how does the data flow?" ``` ### Explore what exists before planning + ``` /lfx-research → upstream API contract + codebase patterns + example files ``` ### Quick backend-only or frontend-only change + ``` /lfx-backend-builder → generates Express proxy + shared types /lfx-ui-builder → generates Angular component + service ``` ### Morning PR catch-up + ``` /lfx-pr-catchup → fetches open PRs → enriches via GraphQL → renders attention dashboard → drill-down ``` ### Test a multi-branch user journey + ``` /lfx-test-journey → pick repos → pick branches → creates worktrees → cd into worktree → yarn start /lfx-test-journey status → shows which branches have new commits → /lfx-test-journey refresh <name> @@ -421,12 +527,14 @@ An **interactive setup guide** that walks through environment configuration step ``` ### Validate before submitting a PR + ``` /lfx-preflight → Phase 1 (license, format, lint, build, protected files) → Phase 2 (15 code review checks, Angular only) → PR /lfx-preflight --skip-review → Phase 1 only (useful during dev) ``` ### Set up a new developer environment + ``` /lfx-setup → prerequisites → clone → env vars → install → dev server ``` @@ -434,49 +542,38 @@ An **interactive setup guide** that walks through environment configuration step ## Project Structure ``` -├── lfx/ -│ ├── SKILL.md # Entry point — plain-language router -│ └── references/ -│ ├── glossary.md # LFX terms explained in plain language -│ └── quickstart.md # Example workflow transcripts -├── lfx-coordinator/ -│ ├── SKILL.md # Orchestrator — plans, delegates, validates -│ └── references/ -│ └── shared-types.md # Shared package conventions -├── lfx-research/ -│ └── SKILL.md # Read-only exploration and API validation -├── lfx-backend-builder/ -│ ├── SKILL.md # Express.js proxy + Go microservice codegen -│ └── references/ -│ ├── backend-endpoint.md # Three-file pattern for Express endpoints -│ ├── fga-patterns.md # OpenFGA access control patterns -│ ├── getting-started.md # Repo map and deployment overview -│ ├── goa-patterns.md # Goa v3 DSL conventions -│ ├── helm-chart.md # Service Helm chart structure -│ ├── indexer-patterns.md # OpenSearch indexing patterns -│ ├── nats-messaging.md # NATS subject naming and messaging -│ ├── new-service.md # New resource service checklist -│ ├── query-service.md # Query service API reference -│ └── service-types.md # Native vs wrapper service types -├── lfx-ui-builder/ -│ ├── SKILL.md # Angular 20 frontend codegen -│ └── references/ -│ ├── frontend-component.md # Component patterns and conventions -│ └── frontend-service.md # Service patterns and state management -├── lfx-product-architect/ -│ └── SKILL.md # Architecture guidance and decision trees -├── lfx-preflight/ -│ └── SKILL.md # Pre-PR validation and auto-fix -├── lfx-pr-catchup/ -│ └── SKILL.md # Morning PR catch-up dashboard -├── lfx-setup/ -│ └── SKILL.md # Environment setup guide -├── lfx-test-journey/ -│ └── SKILL.md # Multi-branch journey testing -├── lfx-intercom/ -│ └── SKILL.md # Intercom integration — add or fix to LFX standard -└── lfx-cdp-snowflake-connectors/ - └── SKILL.md # Snowflake connector scaffolding for CDP +├── .claude-plugin/ +│ └── plugin.json # Claude Code plugin manifest +├── cli/ +│ └── lfx-skills # CLI installer and management command +├── lib/ +│ └── *.sh # CLI support libraries +└── skills/ + ├── lfx/ # Entry point — plain-language router + ├── lfx-coordinator/ # Orchestrator — plans, delegates, validates + ├── lfx-research/ # Read-only exploration and API validation + ├── lfx-backend-builder/ # Express.js proxy + Go microservice codegen + ├── lfx-ui-builder/ # Angular 20 frontend codegen + ├── lfx-product-architect/ # Architecture guidance and decision trees + ├── lfx-preflight/ # Pre-PR validation and auto-fix + ├── lfx-pr-catchup/ # Morning PR catch-up dashboard + ├── lfx-pr-resolve/ # Address PR review feedback end-to-end + ├── lfx-setup/ # Environment setup guide + ├── lfx-test-journey/ # Multi-branch journey testing + ├── lfx-git-setup/ # DCO sign-off and GPG-signed commit setup + ├── lfx-intercom/ # Intercom integration — add or fix + ├── lfx-snowflake-access/ # Request Snowflake access via Terraform PR + └── lfx-cdp-snowflake-connectors/# Snowflake connector scaffolding for CDP +├── .agents/skills/ # Repo-local helpers for agents.md tools +│ ├── lfx-skills-doctor/ +│ ├── lfx-skills-helper/ +│ ├── lfx-install/ +│ └── lfx-new-skill/ +├── .claude/skills/ # Repo-local helpers for Claude Code +│ ├── lfx-skills-doctor/ +│ ├── lfx-skills-helper/ +│ ├── lfx-install/ +│ └── lfx-new-skill/ ``` ## License diff --git a/cli/lfx-skills b/cli/lfx-skills new file mode 100755 index 0000000..e455fad --- /dev/null +++ b/cli/lfx-skills @@ -0,0 +1,939 @@ +#!/usr/bin/env bash +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +# +# lfx-skills — install, manage, and diagnose the LFX Skills collection. +# See `lfx-skills help` for the full command reference. + +set -euo pipefail + +SCRIPT_PATH="${BASH_SOURCE[0]}" +while [ -L "$SCRIPT_PATH" ]; do + SCRIPT_DIR="$(cd -P "$(dirname "$SCRIPT_PATH")" && pwd)" + SCRIPT_PATH="$(readlink "$SCRIPT_PATH")" + case "$SCRIPT_PATH" in + /*) ;; + *) SCRIPT_PATH="$SCRIPT_DIR/$SCRIPT_PATH" ;; + esac +done +SCRIPT_DIR="$(cd -P "$(dirname "$SCRIPT_PATH")" && pwd)" +CLONE_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +export CLONE_ROOT + +# shellcheck source=../lib/ui.sh +. "$CLONE_ROOT/lib/ui.sh" +# shellcheck source=../lib/probe.sh +. "$CLONE_ROOT/lib/probe.sh" +# shellcheck source=../lib/targets.sh +. "$CLONE_ROOT/lib/targets.sh" +# shellcheck source=../lib/config.sh +. "$CLONE_ROOT/lib/config.sh" +# shellcheck source=../lib/symlinks.sh +. "$CLONE_ROOT/lib/symlinks.sh" +# shellcheck source=../lib/doctor.sh +. "$CLONE_ROOT/lib/doctor.sh" + +# Hard requirement: jq for JSON I/O. Doctor and most subcommands need it. +require_jq() { + if ! probe_have_jq; then + ui_error "jq is required but not found on PATH." + ui_info "Install:" + ui_info " macOS: brew install jq" + ui_info " Linux: apt-get install jq / dnf install jq / pacman -S jq" + exit 1 + fi +} + +# ─── version ───────────────────────────────────────────────────────────── +cmd_version() { + if [ -d "$CLONE_ROOT/.git" ]; then + (cd "$CLONE_ROOT" && git describe --tags --always --dirty 2>/dev/null) || echo "unknown" + elif [ -f "$CLONE_ROOT/VERSION" ]; then + cat "$CLONE_ROOT/VERSION" + else + echo "unknown" + fi +} + +# ─── help ──────────────────────────────────────────────────────────────── +cmd_help() { + local sub="${1:-}" + case "$sub" in + install) _help_install ;; + uninstall) _help_uninstall ;; + update) _help_update ;; + check) _help_check ;; + doctor) _help_doctor ;; + list) _help_list ;; + info) _help_info ;; + config) _help_config ;; + repos) _help_repos ;; + version) echo "Usage: lfx-skills version — print version of this clone." ;; + "") _help_root ;; + *) ui_error "Unknown command: $sub"; _help_root; exit 1 ;; + esac +} + +_help_root() { + cat <<'EOF' +lfx-skills — install, manage, and diagnose the LFX Skills collection + +USAGE + lfx-skills <command> [options] + +COMMANDS + install Install LFX skills for agents.md-compatible tools + uninstall Remove installed symlinks (recorded in config.json) + update Re-apply the manifest; with --pull, git-pull the clone first + check Lightweight verification: does config match filesystem? (exit 0/1) + doctor Full diagnostics across 7 categories; --fix to repair interactively + list List installed or available skills + info SKILL Show frontmatter + install locations for a skill + config Show or modify ~/.lfx-skills/config.json (get/set) + repos List lf* git repos under $LFX_DEV_ROOT + version Print version of this lfx-skills clone + help [CMD] Detailed help for CMD, or this overview + +EXAMPLES + lfx-skills install # interactive wizard + lfx-skills install --yes --scope=global + lfx-skills doctor + lfx-skills doctor --fix + lfx-skills list --available + lfx-skills uninstall + +CONFIG + ~/.lfx-skills/config.json manifest (single source of truth) + ~/.lfx-skills/dev-root one-line text file: the resolved dev root, + read by skills via `cat` (no env var or + shell rc needed) + ~/.local/bin/lfx-skills (or symlink so you can type `lfx-skills` from + ~/bin, /usr/local/bin) anywhere — picked from a writable PATH + dir at install time. Recorded in + config.json.cli_symlink. + +Run `lfx-skills help <command>` for command-specific options. +EOF +} + +_help_install() { + cat <<'EOF' +USAGE + lfx-skills install [flags] + +INTERACTIVE + Run with no flags to step through: scope → dev root → config dirs + (if global) → repos (if per-repo) → preview → apply → verify. + This CLI creates symlinks only for agents.md-compatible tools. + +NON-INTERACTIVE FLAGS + --yes Skip all confirmations (use defaults / supplied flags) + --scope=global|repo|global,repo|both 'both' is sugar for 'global,repo' + --lfx-dev-root=PATH + --agents-config=PATH[,PATH...] Multi-config: pass several agents config dirs + --repos=PATH[,PATH...] Required if --scope contains 'repo' + --no-cli-symlink Skip installing the `lfx-skills` CLI + symlink into a PATH dir (default: install + if a writable PATH dir is available) + --dry-run Show what would happen, then exit + +EXAMPLE + lfx-skills install --yes \ + --scope=both \ + --lfx-dev-root=$HOME/lf \ + --repos=$HOME/lf/lfx-v2-ui,$HOME/lf/lfx-v2-meeting-service +EOF +} + +_help_uninstall() { + cat <<'EOF' +USAGE + lfx-skills uninstall [flags] + + Removes recorded agents.md symlinks by default. + +FLAGS + --yes Skip confirmations + --scope=global|repo Limit removal to a scope + --repos=PATH[,PATH...] With --scope=repo: only remove from these repos + --legacy-claude-only Only remove legacy Claude symlinks owned by this clone + --all Remove the whole installation: recorded symlinks, + legacy Claude symlinks, CLI symlink, and config + --keep-config Keep config.json + dev-root on disk +EOF +} + +_help_update() { + cat <<'EOF' +USAGE + lfx-skills update [--pull] + + Re-apply the install manifest. With --pull, git-pull the canonical clone + first. Detects skills present in the clone but missing from the manifest + (newly added upstream OR locally created via /lfx-new-skill) and reports + them so you can re-run install. +EOF +} + +_help_check() { + cat <<'EOF' +USAGE + lfx-skills check + + Lightweight verification of the install manifest vs filesystem. + Exit code: 0 = consistent, 1 = drift detected. + Use this in CI; use `lfx-skills doctor` for full human-readable output. +EOF +} + +_help_doctor() { + cat <<'EOF' +USAGE + lfx-skills doctor [--json] [--fix] + lfx-skills doctor --skill-formatting-only --skill=lfx-name [--json] + +FLAGS + --json Emit results as a JSON array (for scripts / skills) + --fix Interactive fix: walk through fixable issues + --skill-formatting-only Check only SKILL.md frontmatter and license header + --skill=NAME Limit formatting checks to one skill directory +EOF +} + +_help_list() { + cat <<'EOF' +USAGE + lfx-skills list [--scope=global|repo] [--repo=PATH] [--available] + +FLAGS + --available Show every installable skill in this clone (regardless of install) + --scope=... Restrict to global or per-repo scope + --repo=PATH With --scope=repo, restrict to a specific repo +EOF +} + +_help_info() { + cat <<'EOF' +USAGE + lfx-skills info SKILL + + Print the SKILL.md frontmatter for SKILL plus every recorded install location. +EOF +} + +_help_config() { + cat <<'EOF' +USAGE + lfx-skills config Show full config.json (pretty-printed) + lfx-skills config get KEY Print one value + lfx-skills config set KEY=VAL Update a value (re-writes dev-root if relevant) +EOF +} + +_help_repos() { + cat <<'EOF' +USAGE + lfx-skills repos + + List lf* git repos under $LFX_DEV_ROOT (or the recorded dev root). +EOF +} + +# ─── repos ─────────────────────────────────────────────────────────────── +cmd_repos() { + local devroot="${LFX_DEV_ROOT:-}" + if [ -z "$devroot" ]; then + require_jq + devroot="$(config_get lfx_dev_root)" + fi + if [ -z "$devroot" ] || [ ! -d "$devroot" ]; then + ui_error "No usable dev root. Set LFX_DEV_ROOT or run \`lfx-skills install\`." + exit 1 + fi + probe_repos_in "$devroot" +} + +# ─── config ────────────────────────────────────────────────────────────── +cmd_config() { + require_jq + local sub="${1:-show}" + case "$sub" in + show|"") + if config_exists; then + config_read | jq . + else + ui_warn "No config.json yet. Run \`lfx-skills install\` first." + exit 1 + fi + ;; + get) + shift + local key="${1:-}" + [ -z "$key" ] && ui_die "Usage: lfx-skills config get KEY" + config_get "$key" + ;; + set) + shift + local kv="${1:-}" + [ -z "$kv" ] && ui_die "Usage: lfx-skills config set KEY=VALUE" + local key="${kv%%=*}" + local val="${kv#*=}" + [ "$key" = "$kv" ] && ui_die "Missing = in argument: $kv" + config_set "$key" "$val" + if [ "$key" = "lfx_dev_root" ] || [ "$key" = "canonical_clone" ]; then + write_dev_root_file + ui_success "Updated config and dev-root" + else + ui_success "Updated config" + fi + ;; + *) + ui_error "Unknown config subcommand: $sub" + _help_config + exit 1 + ;; + esac +} + +# ─── list ──────────────────────────────────────────────────────────────── +cmd_list() { + require_jq + local available=0 scope="" repo="" + while [ $# -gt 0 ]; do + case "$1" in + --available) available=1 ;; + --scope=*) scope="${1#*=}" ;; + --repo=*) repo="${1#*=}" ;; + *) ui_error "Unknown flag: $1"; exit 1 ;; + esac + shift + done + if [ "$available" = "1" ]; then + symlinks_eligible_skills "$CLONE_ROOT" + return + fi + if ! config_exists; then + ui_warn "No config.json yet. Run \`lfx-skills install\` or pass --available." + exit 1 + fi + local jq_filter='true' + if [ -n "$scope" ]; then + jq_filter=".scope == \"$scope\"" + if [ "$scope" = "repo" ] && [ -n "$repo" ]; then + jq_filter="$jq_filter and .repo == \"$repo\"" + fi + fi + config_read | jq -r --argjson _ 0 "(.symlinks // [])[] | select($jq_filter) | \"\(.scope)\t\(.skill)\t\(.link)\"" | sort -u +} + +# ─── info ──────────────────────────────────────────────────────────────── +cmd_info() { + require_jq + local skill="${1:-}" + [ -z "$skill" ] && ui_die "Usage: lfx-skills info SKILL" + local skill_source skill_md + skill_source="$(symlinks_skill_source "$CLONE_ROOT" "$skill" 2>/dev/null || true)" + skill_md="$skill_source/SKILL.md" + if [ ! -f "$skill_md" ]; then + ui_error "No SKILL.md for \`$skill\` in $CLONE_ROOT/skills or $CLONE_ROOT/.agents/skills" + exit 1 + fi + ui_section "$skill" + printf 'Source: %s\n' "$skill_source" + printf '\n' + ui_bold "Frontmatter:" + awk 'NR==1 && /^---$/ {start=1; next} start && /^---$/ {exit} start {print " " $0}' "$skill_md" + if config_exists; then + printf '\n' + ui_bold "Install locations:" + local n_locations + n_locations="$(config_read | jq -r --arg s "$skill" '(.symlinks // []) | map(select(.skill == $s)) | length')" + if [ "$n_locations" -eq 0 ]; then + printf ' (not installed anywhere)\n' + else + config_read | jq -r --arg s "$skill" '(.symlinks // [])[] | select(.skill == $s) | " \(.scope) \(.link)"' + fi + fi +} + +# ─── check (lightweight verify) ────────────────────────────────────────── +cmd_check() { + require_jq + if ! config_exists; then + ui_error "No config.json. Run \`lfx-skills install\` first." + exit 1 + fi + local link source missing=0 + while IFS= read -r link; do + [ -z "$link" ] && continue + if [ ! -L "$link" ]; then + missing=$((missing + 1)) + continue + fi + source="$(readlink "$link" 2>/dev/null || true)" + [ -d "$source" ] || missing=$((missing + 1)) + done < <(config_list_symlinks) + if [ "$missing" -gt 0 ]; then + ui_error "$missing symlink(s) broken or missing. Run \`lfx-skills doctor\` for details." + exit 1 + fi + ui_success "Manifest matches filesystem." +} + +# ─── doctor ────────────────────────────────────────────────────────────── +cmd_doctor() { + require_jq + local emit_json=0 do_fix=0 skill_formatting_only=0 skill_filter="" + while [ $# -gt 0 ]; do + case "$1" in + --json) emit_json=1 ;; + --fix) do_fix=1 ;; + --skill-formatting-only) skill_formatting_only=1 ;; + --skill=*) skill_filter="${1#--skill=}" ;; + --skill) + shift + [ $# -gt 0 ] || { ui_error "--skill requires a value"; exit 1; } + skill_filter="$1" + ;; + *) ui_error "Unknown flag: $1"; exit 1 ;; + esac + shift + done + + if [ "$skill_formatting_only" = "1" ]; then + [ "$do_fix" = "0" ] || { ui_error "--fix is not supported with --skill-formatting-only"; exit 1; } + [ -n "$skill_filter" ] || { ui_error "--skill-formatting-only requires --skill=NAME"; exit 1; } + [ -d "$CLONE_ROOT/skills/$skill_filter" ] || { ui_error "Unknown skill: $skill_filter"; exit 1; } + elif [ -n "$skill_filter" ]; then + ui_error "--skill can only be used with --skill-formatting-only" + exit 1 + fi + + # Run every check once; records flow through a shell variable. + local results + if [ "$skill_formatting_only" = "1" ]; then + DOCTOR_SKILL_FILTER="$skill_filter" + results="$(doctor_run_skill_formatting)" + unset DOCTOR_SKILL_FILTER + else + results="$(doctor_run_all)" + fi + + if [ "$emit_json" = "1" ]; then + printf '%s\n' "$results" | doctor_format_json + return + fi + + local exit_code=0 + printf '%s\n' "$results" | doctor_format_human || exit_code=$? + + if [ "$do_fix" = "1" ]; then + ui_blank + ui_section "Auto-fix" + local any_fixable=0 + local sev id cat title detail fixable payload + # Read records from FD 3 so ui_confirm's `read` can still consume FD 0 (the + # user's terminal). Without this, the outer loop and ui_confirm fight over + # stdin and ui_confirm ends up "reading" the next record line as the reply. + while IFS='|' read -r sev id cat title detail fixable payload <&3; do + [ "$fixable" = "yes" ] || continue + any_fixable=1 + ui_blank + printf ' [%s] %s\n' "$id" "$title" + [ -n "$detail" ] && printf ' %s%s%s\n' "$_UI_DIM" "$detail" "$_UI_RESET" + if ui_confirm " Fix this?" N; then + doctor_fix_one "$id" "$payload" || ui_warn "Fix did not complete." + fi + done 3<<EOF +$results +EOF + if [ "$any_fixable" = "0" ]; then + ui_info "Nothing fixable." + else + ui_blank + ui_step "Re-running checks…" + results="$(doctor_run_all)" + printf '%s\n' "$results" | doctor_format_human || exit_code=$? + fi + fi + return "$exit_code" +} + +# ─── install (the wizard + non-interactive flags) ──────────────────────── +cmd_install() { + require_jq + + local scope="" lfx_dev_root="" agents_config="" repos="" dry_run=0 + local no_cli_symlink=0 + while [ $# -gt 0 ]; do + case "$1" in + --yes) LFX_SKILLS_YES=1; export LFX_SKILLS_YES ;; + --platform=*) ui_die "--platform is no longer supported; the CLI installs agents.md skills only." ;; + --scope=*) scope="${1#*=}" ;; + --lfx-dev-root=*) lfx_dev_root="${1#*=}" ;; + --claude-config=*) ui_die "--claude-config is no longer supported; Claude Code uses the lfx-skills plugin." ;; + --agents-config=*) agents_config="${1#*=}" ;; + --repos=*) repos="${1#*=}" ;; + --no-cli-symlink) no_cli_symlink=1 ;; + --dry-run) dry_run=1 ;; + -h|--help) _help_install; return 0 ;; + *) ui_error "Unknown flag: $1"; _help_install; exit 1 ;; + esac + shift + done + + ui_section "LFX Skills Install" + + # Step 1: PROBE + ui_step "Probing your system…" + # Initialize all dynamic arrays to empty up-front so `${arr[@]}` is safe under + # bash 3.2 with `set -u` even when the probe yields nothing. + local -a agents_dirs_probed=() dev_root_probed=() + while IFS= read -r d; do agents_dirs_probed+=("$d"); done < <(probe_agents_config_dirs) + while IFS= read -r d; do dev_root_probed+=("$d"); done < <(probe_dev_root_candidates) + + ui_dim " Agents config dirs: ${agents_dirs_probed[*]:-(none found)}" + ui_dim " Dev root candidates: ${dev_root_probed[*]:-(none found)}" + + # Step 2: SCOPE (multi-select: global, per-repo, or both) + if [ -z "$scope" ]; then + local -a _picked=() + local _line + while IFS= read -r _line; do _picked+=("$_line"); done < <( + ui_checkbox_select "Install scope? (select one or both)" "1" 1 \ + "global (available in every AI session)" "per-repo (only in selected repos)" + ) + if [ "${#_picked[@]}" -eq 0 ]; then ui_info "Aborted."; return 0; fi + local _s _parts="" + for _s in "${_picked[@]}"; do + case "$_s" in + global*) _parts="${_parts:+$_parts,}global" ;; + per-repo*) _parts="${_parts:+$_parts,}repo" ;; + esac + done + scope="$_parts" + ui_blank + fi + case "$scope" in + both) scope="global,repo" ;; + global|repo|global,repo|repo,global) ;; + *) ui_die "--scope must be a combination of global,repo (or 'both')" ;; + esac + local include_global=0 include_repo=0 + case ",$scope," in *,global,*) include_global=1 ;; esac + case ",$scope," in *,repo,*) include_repo=1 ;; esac + + # Step 3: LFX DEV ROOT + if [ -z "$lfx_dev_root" ]; then + if [ -n "${LFX_DEV_ROOT:-}" ]; then + lfx_dev_root="$LFX_DEV_ROOT" + elif [ "${#dev_root_probed[@]}" -gt 0 ]; then + lfx_dev_root="${dev_root_probed[0]}" + lfx_dev_root="$(ui_input "Where do you keep your LFX repos?" "$lfx_dev_root")" + else + lfx_dev_root="$(ui_input "Where do you keep your LFX repos?" "$HOME/lf")" + fi + fi + local repo_count + if [ -d "$lfx_dev_root" ]; then + repo_count="$(probe_count_repos_in "$lfx_dev_root")" + else + repo_count=0 + fi + ui_dim " $lfx_dev_root contains $repo_count lf* git repo(s)." + ui_blank + + # Step 4a: CONFIG DIR PICKER (only if global scope) + # Empty defaults so `${arr[@]}` is safe under bash 3.2 + `set -u`. + local -a chosen_agents_dirs=() + if [ "$include_global" -eq 1 ]; then + if [ -n "$agents_config" ]; then + local d + while IFS= read -r d; do chosen_agents_dirs+=("$d"); done < <(printf '%s\n' "$agents_config" | tr ',' '\n') + elif [ "${#agents_dirs_probed[@]}" -gt 0 ]; then + # If multiple, let the user pick. Single = silent default. + if [ "${#agents_dirs_probed[@]}" -eq 1 ]; then + chosen_agents_dirs=("${agents_dirs_probed[0]}") + else + local picked + while IFS= read -r picked; do chosen_agents_dirs+=("$picked"); done < <( + ui_checkbox_select "Which agents config dir(s)? (select one or more)" "1" 1 \ + "${agents_dirs_probed[@]}" + ) + if [ "${#chosen_agents_dirs[@]}" -eq 0 ]; then ui_info "Aborted."; return 0; fi + ui_blank + fi + else + chosen_agents_dirs=("$(default_agents_config_dir)") + fi + fi + + # Step 4b: REPO PICKER (only if per-repo scope) + local -a chosen_repos=() + if [ "$include_repo" -eq 1 ]; then + if [ -n "$repos" ]; then + local r + while IFS= read -r r; do chosen_repos+=("$r"); done < <(printf '%s\n' "$repos" | tr ',' '\n') + else + local -a candidate_repos=() + while IFS= read -r r; do candidate_repos+=("$r"); done < <(probe_repos_in "$lfx_dev_root") + if [ "${#candidate_repos[@]}" -eq 0 ]; then + ui_warn "No lf* git repos found under $lfx_dev_root." + ui_die "Per-repo install requires at least one repo. Aborting." + else + local picked + while IFS= read -r picked; do chosen_repos+=("$picked"); done < <( + ui_checkbox_select "Which repos to install into? (select one or more)" "all" 1 \ + "${candidate_repos[@]}" + ) + if [ "${#chosen_repos[@]}" -eq 0 ]; then ui_info "Aborted."; return 0; fi + ui_blank + fi + fi + fi + + # Step 5: PREVIEW + ui_blank + ui_section "Plan" + local skill_count + skill_count="$(symlinks_eligible_skills "$CLONE_ROOT" | wc -l | tr -d ' ')" + + # Compute number of install targets so the preview shows the actual symlink + # count (skills × targets), not just the number of unique skills. + local n_targets=0 + if [ "$include_global" -eq 1 ]; then + n_targets=$((n_targets + ${#chosen_agents_dirs[@]})) + fi + if [ "$include_repo" -eq 1 ]; then + n_targets=$((n_targets + ${#chosen_repos[@]})) + fi + local total_symlinks=$((skill_count * n_targets)) + + printf ' Skills: %s unique\n' "$skill_count" + printf ' Scope: %s\n' "$scope" + printf ' Targets: %s\n' "$n_targets" + printf ' Symlinks: %s total (skills × targets)\n' "$total_symlinks" + printf ' LFX_DEV_ROOT: %s\n' "$lfx_dev_root" + if [ ! -d "$lfx_dev_root" ]; then + printf ' Will create LFX_DEV_ROOT directory.\n' + fi + if [ "${#chosen_agents_dirs[@]}" -gt 0 ]; then + printf ' Agents config dirs:\n' + for d in "${chosen_agents_dirs[@]}"; do printf ' %s\n' "$d"; done + fi + if [ "${#chosen_repos[@]}" -gt 0 ]; then + printf ' Repos:\n' + for r in "${chosen_repos[@]}"; do printf ' %s\n' "$r"; done + fi + + if [ "$dry_run" = "1" ]; then + ui_blank + ui_info "Dry run — exiting without changes." + return 0 + fi + + ui_blank + if ! ui_confirm "Proceed?" Y; then + ui_info "Aborted." + return 0 + fi + + # Step 6: APPLY + ui_blank + if [ ! -d "$lfx_dev_root" ]; then + ui_step "Creating $lfx_dev_root…" + mkdir -p "$lfx_dev_root" + fi + + ui_step "Writing config…" + config_init "$CLONE_ROOT" "$lfx_dev_root" + config_prune_obsolete_platforms + config_set canonical_clone "$CLONE_ROOT" + config_set lfx_dev_root "$lfx_dev_root" + local -a rcs=() + while IFS= read -r r; do rcs+=("$r"); done < <(probe_shell_rcs) + if [ "${#rcs[@]}" -gt 0 ]; then + config_set_shell_rcs "${rcs[@]}" + fi + + local total_installed=0 total_updated=0 total_skipped=0 + if [ "$include_global" -eq 1 ]; then + if [ "${#chosen_agents_dirs[@]}" -gt 0 ]; then + for d in "${chosen_agents_dirs[@]}"; do + ui_blank + ui_step "agents · global · $d/skills/" + local s; s="$(symlinks_install_all "$CLONE_ROOT" global "$d")" + _accumulate_install_summary "$s" + done + fi + fi + if [ "$include_repo" -eq 1 ] && [ "${#chosen_repos[@]}" -gt 0 ]; then + for r in "${chosen_repos[@]}"; do + ui_blank + ui_step "agents · per-repo · $r/.agents/skills/" + local s; s="$(symlinks_install_all "$CLONE_ROOT" repo "$r")" + _accumulate_install_summary "$s" + done + fi + + ui_step "Writing dev-root…" + write_dev_root_file + + # Install lfx-skills CLI into a writable PATH dir so the user can type + # `lfx-skills` from anywhere. Tracked in config.json for clean uninstall. + # Capture only stdout (warnings/errors flow to stderr, which the user sees + # directly and we don't want polluting our path variable). + local cli_symlink_path="" + if [ "$no_cli_symlink" = "0" ]; then + cli_symlink_path="$(install_cli_symlink "$CLONE_ROOT" || true)" + if [ -L "$cli_symlink_path" ]; then + if ! config_set_cli_symlink "$cli_symlink_path"; then + ui_warn "Created CLI symlink at $cli_symlink_path but failed to record it in config — uninstall won't clean it up automatically." + fi + else + cli_symlink_path="" + fi + fi + + # Step 7: VERIFY + summary + ui_blank + ui_section "Done" + printf ' %d installed, %d updated, %d skipped\n' \ + "$total_installed" "$total_updated" "$total_skipped" + + if [ -n "$cli_symlink_path" ]; then + ui_success "lfx-skills CLI installed to $cli_symlink_path" + elif [ "$no_cli_symlink" = "0" ]; then + ui_warn "Couldn't safely install the CLI symlink into a writable dir on your PATH." + printf ' To use %slfx-skills%s from anywhere, add an alias to your shell rc:\n' \ + "$_UI_BOLD" "$_UI_RESET" + printf ' alias lfx-skills='\''%s/cli/lfx-skills'\''\n' "$CLONE_ROOT" + printf ' Or extend PATH to include one of: ~/.local/bin, ~/bin, /usr/local/bin\n' + fi + + ui_blank + ui_bold "Next steps:" + printf ' 1. Restart your AI coding assistant (or open a new session).\n' + printf ' 2. Type /lfx in any LFX repo to get started.\n' + + if [ "${#chosen_repos[@]}" -gt 0 ]; then + ui_blank + ui_bold "Per-repo install: add to each repo's .gitignore" + printf ' .agents/skills/lfx-*\n' + printf ' .agents/skills/lfx\n' + printf ' %s(narrow patterns preserve any of your own committed skills)%s\n' "$_UI_DIM" "$_UI_RESET" + fi +} + +# Helper used inside cmd_install to sum `installed/updated/skipped/total` strings. +_accumulate_install_summary() { + local s="$1" + local i u sk t IFS=/ + read -r i u sk t <<EOF +$s +EOF + total_installed=$((total_installed + i)) + total_updated=$((total_updated + u)) + total_skipped=$((total_skipped + sk)) +} + +# ─── uninstall ─────────────────────────────────────────────────────────── +cmd_uninstall() { + require_jq + local scope="" repos="" keep_config=0 legacy_only=0 remove_all=0 + while [ $# -gt 0 ]; do + case "$1" in + --yes) LFX_SKILLS_YES=1; export LFX_SKILLS_YES ;; + --scope=*) scope="${1#*=}" ;; + --repos=*) repos="${1#*=}" ;; + --legacy-claude-only) legacy_only=1 ;; + --all) remove_all=1 ;; + --keep-config) keep_config=1 ;; + -h|--help) _help_uninstall; return 0 ;; + *) ui_error "Unknown flag: $1"; _help_uninstall; exit 1 ;; + esac + shift + done + + if [ "$legacy_only" = "1" ] && [ "$remove_all" = "1" ]; then + ui_die "--legacy-claude-only and --all cannot be combined." + fi + if [ "$remove_all" = "1" ] && { [ -n "$scope" ] || [ -n "$repos" ] || [ "$keep_config" = "1" ]; }; then + ui_die "--all cannot be combined with --scope, --repos, or --keep-config." + fi + if [ "$legacy_only" = "1" ]; then + ui_section "Legacy Claude Cleanup" + if ! ui_confirm "Remove legacy lfx-skills Claude symlinks only?" N; then + ui_info "Aborted." + return 0 + fi + _cleanup_legacy_claude + config_prune_obsolete_platforms + ui_success "Legacy Claude cleanup complete." + return 0 + fi + + if ! config_exists; then + ui_warn "No config.json — no recorded agents.md install to uninstall." + ui_section "Legacy Claude Cleanup" + if ui_confirm "Check ~/.claude/skills and remove lfx-skills symlinks that point into this clone?" N; then + local legacy_summary + legacy_summary="$(symlinks_uninstall_legacy_claude_root "$CLONE_ROOT")" + ui_dim " $legacy_summary removed/refused/missing/total" + fi + return 0 + fi + + ui_section "LFX Skills Uninstall" + if [ "$remove_all" = "1" ]; then + printf ' Mode: remove whole installation\n' + fi + if [ -n "$scope" ]; then + printf ' Scope filter: %s\n' "$scope" + fi + if [ -n "$repos" ]; then + printf ' Repo filter: %s\n' "$repos" + fi + + local confirm_msg="Remove the recorded agents.md symlinks?" + if [ "$remove_all" = "1" ]; then + confirm_msg="Remove the whole lfx-skills installation?" + fi + if ! ui_confirm "$confirm_msg" N; then + ui_info "Aborted." + return 0 + fi + + if [ "$scope" = "repo" ] && [ -n "$repos" ]; then + local r + while IFS= read -r r; do + ui_step "Removing per-repo symlinks for $r…" + symlinks_uninstall_all repo "$r" >/dev/null + done < <(printf '%s\n' "$repos" | tr ',' '\n') + else + local s + s="$(symlinks_uninstall_all "$scope" "")" + ui_dim " $s removed/refused/missing/total" + fi + + if [ "$remove_all" = "1" ]; then + _cleanup_legacy_claude + elif [ -z "$scope" ] || [ "$scope" = "global" ]; then + _cleanup_legacy_claude + fi + + config_prune_obsolete_platforms + + if { [ "$keep_config" = "0" ] && [ -z "$scope" ]; } || [ "$remove_all" = "1" ]; then + # Remove the lfx-skills CLI symlink (e.g. ~/.local/bin/lfx-skills) if we + # installed it. Safe-by-default: only removed if it points into the + # canonical clone. + local cli_path canonical + cli_path="$(config_get cli_symlink)" + canonical="$(config_get canonical_clone)" + if [ -n "$cli_path" ] && [ -L "$cli_path" ]; then + if remove_cli_symlink "$cli_path" "$canonical"; then + ui_success "Removed lfx-skills CLI symlink at $cli_path" + fi + fi + if [ "$remove_all" = "1" ] || ui_confirm "Remove ~/.lfx-skills/ entirely?" N; then + config_clear + ui_success "Removed config dir." + else + ui_info "Kept config dir." + fi + fi + ui_success "Uninstall complete." +} + +_cleanup_legacy_claude() { + local recorded_summary root_summary + ui_step "Removing legacy Claude symlinks recorded in config…" + recorded_summary="$(symlinks_uninstall_legacy_claude_recorded)" + ui_dim " $recorded_summary removed/refused/missing/total" + ui_step "Checking legacy Claude root symlinks in ~/.claude/skills…" + root_summary="$(symlinks_uninstall_legacy_claude_root "$CLONE_ROOT")" + ui_dim " $root_summary removed/refused/missing/total" +} + +# ─── update ────────────────────────────────────────────────────────────── +cmd_update() { + require_jq + local do_pull=0 + while [ $# -gt 0 ]; do + case "$1" in + --pull) do_pull=1 ;; + --yes) LFX_SKILLS_YES=1; export LFX_SKILLS_YES ;; + -h|--help) _help_update; return 0 ;; + *) ui_error "Unknown flag: $1"; _help_update; exit 1 ;; + esac + shift + done + + if ! config_exists; then + ui_die "No config.json — run \`lfx-skills install\` first." + fi + + if [ "$do_pull" = "1" ]; then + ui_step "git pull in $CLONE_ROOT…" + (cd "$CLONE_ROOT" && git pull --ff-only) || ui_die "git pull failed." + fi + + # Re-apply: walk every recorded agents.md (scope, base) tuple and re-install. + ui_step "Re-applying manifest…" + local rec + config_read | jq -c ' + [(.symlinks // [])[] + | select((.platform // "agents") != "claude") + | {scope, base: (if .scope == "global" then .config_dir else .repo end)}] + | unique_by(.scope + "/" + .base) + | .[] + ' | while IFS= read -r rec; do + local scope base + scope="$(echo "$rec" | jq -r .scope)" + base="$(echo "$rec" | jq -r .base)" + ui_dim " → $scope at $base" + symlinks_install_all "$CLONE_ROOT" "$scope" "$base" >/dev/null + done + config_prune_obsolete_platforms + + # Detect new skills (in clone, not in manifest) + local installed_set new_skills + installed_set="$(config_read | jq -r '(.symlinks // [])[] | .skill' | sort -u)" + new_skills="" + while IFS= read -r skill; do + if ! printf '%s\n' "$installed_set" | grep -qx "$skill"; then + new_skills="${new_skills}${skill}"$'\n' + fi + done < <(symlinks_eligible_skills "$CLONE_ROOT") + if [ -n "$new_skills" ]; then + ui_blank + ui_warn "New skills found in clone but not in manifest:" + printf '%s' "$new_skills" | sed 's/^/ /' + ui_info "Run \`lfx-skills install\` again to add them at all configured locations." + fi + + ui_success "Update complete." +} + +# ─── dispatch ──────────────────────────────────────────────────────────── +main() { + local cmd="${1:-help}" + shift || true + case "$cmd" in + install) cmd_install "$@" ;; + uninstall) cmd_uninstall "$@" ;; + update) cmd_update "$@" ;; + check) cmd_check "$@" ;; + doctor) cmd_doctor "$@" ;; + list) cmd_list "$@" ;; + info) cmd_info "$@" ;; + config) cmd_config "$@" ;; + repos) cmd_repos "$@" ;; + version) cmd_version "$@" ;; + help|-h|--help) cmd_help "$@" ;; + *) ui_error "Unknown command: $cmd"; _help_root; exit 1 ;; + esac +} + +main "$@" diff --git a/docs/platform-install.md b/docs/platform-install.md deleted file mode 100644 index b68a1fd..0000000 --- a/docs/platform-install.md +++ /dev/null @@ -1,63 +0,0 @@ -<!-- Copyright The Linux Foundation and each contributor to LFX. --> -<!-- SPDX-License-Identifier: MIT --> - -# Platform Installation Guide - -LFX Skills work with any AI coding assistant that can load context from Markdown files. This guide covers installation for specific platforms. - -## Claude Code - -Claude Code is the reference implementation. Skills are auto-discovered from `~/.claude/skills/`. - -**Automatic installation:** - -```bash -git clone https://github.com/linuxfoundation/skills.git -cd skills -./install.sh -``` - -**Manual installation:** - -```bash -mkdir -p ~/.claude/skills -for skill in lfx-*/ lfx/; do - ln -sf "$(pwd)/$skill" ~/.claude/skills/"$(basename "$skill")" -done -``` - -**Verify:** Restart Claude Code (or open a new session) and type `/lfx`. - -**Per-repo installation** (scoped to a single repo instead of global): - -```bash -# From inside a target repo (e.g., lfx-v2-ui) -mkdir -p .claude/skills -for skill in /path/to/skills/lfx-*/ /path/to/skills/lfx/; do - ln -sf "$skill" .claude/skills/"$(basename "$skill")" -done -echo '.claude/skills/' >> .gitignore -``` - -**Uninstall:** - -```bash -rm -f ~/.claude/skills/lfx-* -rm -f ~/.claude/skills/lfx -``` - -## Gemini CLI - -Reference the SKILL.md files in your project's `GEMINI.md` configuration. Consult [Gemini CLI documentation](https://github.com/google-gemini/gemini-cli) for details on loading external context files. - -## Other Platforms - -Most AI coding tools support loading context from Markdown files. To use LFX Skills with your tool: - -1. Clone this repository -2. Point your tool at the SKILL.md files in each skill directory -3. Consult [docs/tool-mapping.md](tool-mapping.md) to translate tool names used in SKILL.md files to your platform's equivalents - -## Contributing - -To add installation instructions for a new platform, submit a PR updating this file and the capability table in [docs/tool-mapping.md](tool-mapping.md). diff --git a/install.sh b/install.sh index 4f1721f..ca5cdb5 100755 --- a/install.sh +++ b/install.sh @@ -1,64 +1,7 @@ #!/usr/bin/env bash # Copyright The Linux Foundation and each contributor to LFX. # SPDX-License-Identifier: MIT - -# LFX Skills Installer -# Symlinks all LFX skills into ~/.claude/skills/ so they're available globally in your AI coding assistant. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -SKILLS_DIR="$HOME/.claude/skills" - -echo "Installing LFX skills..." -echo "" - -# Create the skills directory if it doesn't exist -mkdir -p "$SKILLS_DIR" - -# Track results -installed=0 -updated=0 -failed=0 - -# Install each skill directory (lfx-* and lfx/) -for skill_path in "$SCRIPT_DIR"/lfx-*/ "$SCRIPT_DIR"/lfx/; do - [ -d "$skill_path" ] || continue - - skill_name="$(basename "${skill_path%/}")" - target="$SKILLS_DIR/$skill_name" - - if [ -L "$target" ]; then - # Symlink exists — update it - rm "$target" - ln -s "$skill_path" "$target" - echo " Updated $skill_name" - updated=$((updated + 1)) - elif [ -e "$target" ]; then - # Something else exists at the target path - echo " Skipped $skill_name (non-symlink already exists at $target)" - failed=$((failed + 1)) - else - ln -s "$skill_path" "$target" - echo " Installed $skill_name" - installed=$((installed + 1)) - fi -done - -echo "" -echo "Done! $((installed + updated)) skills ready ($installed new, $updated updated)." - -if [ $failed -gt 0 ]; then - echo "$failed skills skipped due to conflicts — check the paths above." -fi - -echo "" -echo "Next steps:" -echo " 1. Restart your AI coding assistant (or open a new session)" -echo " 2. Type /lfx to get started" -echo "" -echo "Available skills:" -for skill_path in "$SKILLS_DIR"/lfx*; do - [ -e "$skill_path" ] || continue - echo " /$(basename "$skill_path")" -done +# +# Thin shim: the real installer lives at cli/lfx-skills. +# Preserved as `./install.sh` because that's the documented entry point. +exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/cli/lfx-skills" install "$@" diff --git a/lib/config.sh b/lib/config.sh new file mode 100644 index 0000000..3998a33 --- /dev/null +++ b/lib/config.sh @@ -0,0 +1,225 @@ +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +# +# Config + dev-root I/O for cli/lfx-skills. Sourced; do not execute directly. +# Requires: jq (caller validates with probe_have_jq). + +# Config dir lives at $HOME/.lfx-skills. Each config artefact is a sibling file +# inside that dir: +# config.json manifest of every symlink the CLI installed (incl. cli_symlink) +# dev-root single-line text file containing the resolved LFX_DEV_ROOT. +# Skills `cat` this to get the dev root without depending on +# env vars or shell rc edits. +# Nothing in $HOME/.lfx-skills/ is sourced by the user's shell. The CLI is made +# available system-wide via a symlink installed into a writable PATH dir +# (~/.local/bin, ~/bin, or /usr/local/bin) — see install_cli_symlink. +CONFIG_DIR_DEFAULT="$HOME/.lfx-skills" +CONFIG_JSON_DEFAULT="$CONFIG_DIR_DEFAULT/config.json" +DEV_ROOT_FILE_DEFAULT="$CONFIG_DIR_DEFAULT/dev-root" + +# Allow overrides via env (used by tests). +config_dir() { printf '%s\n' "${LFX_SKILLS_CONFIG_DIR:-$CONFIG_DIR_DEFAULT}"; } +config_json_path() { printf '%s\n' "${LFX_SKILLS_CONFIG_JSON:-$(config_dir)/config.json}"; } +dev_root_path() { printf '%s\n' "${LFX_SKILLS_DEV_ROOT_FILE:-$(config_dir)/dev-root}"; } + +# config_exists → return 0 if config.json exists, 1 otherwise. +config_exists() { [ -f "$(config_json_path)" ]; } + +# config_init [canonical_clone] [lfx_dev_root] +# Ensure config dir + config.json exist with schema_version=1 baseline. +# Idempotent: leaves existing config alone, only fills missing top-level keys. +config_init() { + local clone="${1:-}" devroot="${2:-}" + mkdir -p "$(config_dir)" + local cfg + cfg="$(config_json_path)" + if [ ! -f "$cfg" ]; then + local now + now="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + jq -n \ + --arg clone "$clone" \ + --arg devroot "$devroot" \ + --arg now "$now" \ + '{ + schema_version: "1", + lfx_dev_root: $devroot, + canonical_clone: $clone, + shell_rc_detected: [], + installed_at: $now, + last_updated_at: $now, + symlinks: [] + }' > "$cfg" + fi +} + +# config_read → cat config.json to stdout (or an empty default if missing). +config_read() { + if config_exists; then + cat "$(config_json_path)" + else + echo '{}' + fi +} + +# config_get KEY → echo the JSON value (raw) at .KEY. Empty if missing. +# KEY may be a dotted path: e.g., "shell_rc_detected" +config_get() { + local key="$1" + config_read | jq -r --arg k "$key" ' + def get(p): reduce (p / ".")[] as $part (.; if . == null then null else .[$part] end); + get($k) // empty + ' +} + +# config_get_json KEY → echo the JSON value (preserving structure) at .KEY. +config_get_json() { + local key="$1" + config_read | jq --arg k "$key" ' + def get(p): reduce (p / ".")[] as $part (.; if . == null then null else .[$part] end); + get($k) + ' +} + +# config_set KEY VALUE → set .KEY to a STRING value (top-level or dotted path). +# Updates last_updated_at automatically. +config_set() { + local key="$1" value="$2" + local cfg now tmp + cfg="$(config_json_path)" + now="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + tmp="$cfg.tmp.$$" + jq --arg k "$key" --arg v "$value" --arg now "$now" ' + def setpath_dotted(p; v): + (p / ".") as $parts | setpath($parts; v); + setpath_dotted($k; $v) | .last_updated_at = $now + ' < "$cfg" > "$tmp" && mv "$tmp" "$cfg" +} + +# config_set_json KEY JSON_VALUE → set .KEY to a JSON value (object/array/etc.) +config_set_json() { + local key="$1" value="$2" + local cfg now tmp + cfg="$(config_json_path)" + now="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + tmp="$cfg.tmp.$$" + jq --arg k "$key" --argjson v "$value" --arg now "$now" ' + def setpath_dotted(p; v): + (p / ".") as $parts | setpath($parts; v); + setpath_dotted($k; $v) | .last_updated_at = $now + ' < "$cfg" > "$tmp" && mv "$tmp" "$cfg" +} + +# config_add_symlink SCOPE LINK SOURCE [BASE] +# Append a symlink record to the symlinks array. +# SCOPE: global | repo +# LINK: absolute path of the symlink we created +# SOURCE: absolute path of the skill directory the symlink points at +# BASE: for global → the config_dir; for repo → the repo path +# Skill name is inferred from basename(LINK). +config_add_symlink() { + local scope="$1" link="$2" source="$3" base="${4:-}" + local skill cfg now tmp + skill="$(basename "$link")" + cfg="$(config_json_path)" + now="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + tmp="$cfg.tmp.$$" + local entry + if [ "$scope" = "global" ]; then + entry="$(jq -n \ + --arg scope "$scope" \ + --arg config_dir "$base" \ + --arg skill "$skill" \ + --arg link "$link" \ + --arg source "$source" \ + '{scope:$scope, config_dir:$config_dir, skill:$skill, link:$link, source:$source}')" + else + entry="$(jq -n \ + --arg scope "$scope" \ + --arg repo "$base" \ + --arg skill "$skill" \ + --arg link "$link" \ + --arg source "$source" \ + '{scope:$scope, repo:$repo, skill:$skill, link:$link, source:$source}')" + fi + jq --argjson entry "$entry" --arg now "$now" ' + .symlinks = ((.symlinks // []) | map(select(.link != $entry.link)) + [$entry]) + | .last_updated_at = $now + ' < "$cfg" > "$tmp" && mv "$tmp" "$cfg" +} + +# config_remove_symlink LINK → drop the entry whose .link matches. +config_remove_symlink() { + local link="$1" + local cfg now tmp + cfg="$(config_json_path)" + now="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + tmp="$cfg.tmp.$$" + jq --arg link "$link" --arg now "$now" ' + .symlinks = ((.symlinks // []) | map(select(.link != $link))) + | .last_updated_at = $now + ' < "$cfg" > "$tmp" && mv "$tmp" "$cfg" +} + +# config_list_symlinks [SCOPE] [TARGET] +# Echo absolute symlink paths, one per line. +# SCOPE empty → all symlinks +# SCOPE=global → only global +# SCOPE=repo, TARGET=PATH → only symlinks whose .repo matches PATH +config_list_symlinks() { + local scope="${1:-}" target="${2:-}" + if [ -z "$scope" ]; then + config_read | jq -r '(.symlinks // [])[] | .link' + elif [ "$scope" = "global" ]; then + config_read | jq -r '(.symlinks // [])[] | select(.scope == "global") | .link' + elif [ "$scope" = "repo" ] && [ -n "$target" ]; then + config_read | jq -r --arg t "$target" '(.symlinks // [])[] | select(.scope == "repo" and .repo == $t) | .link' + elif [ "$scope" = "repo" ]; then + config_read | jq -r '(.symlinks // [])[] | select(.scope == "repo") | .link' + fi +} + +# config_list_symlink_records [JQ_FILTER] → echo full records as compact JSON, one per line. +# If JQ_FILTER is provided, use it as a select expression. +config_list_symlink_records() { + local filter="${1:-true}" + config_read | jq -c --argjson _ 0 "(.symlinks // [])[] | select($filter)" +} + +# config_set_shell_rcs RC... → record the list of detected shell rcs. +config_set_shell_rcs() { + local rcs_json + rcs_json="$(printf '%s\n' "$@" | jq -R . | jq -sc .)" + config_set_json "shell_rc_detected" "$rcs_json" +} + +# config_clear → delete config.json and dev-root. +config_clear() { + rm -f "$(config_json_path)" "$(dev_root_path)" + rmdir "$(config_dir)" 2>/dev/null || true +} + +# config_prune_obsolete_platforms → remove legacy platform metadata. +config_prune_obsolete_platforms() { + config_exists || return 0 + local cfg now tmp + cfg="$(config_json_path)" + now="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + tmp="$cfg.tmp.$$" + jq --arg now "$now" 'del(.platforms) | .last_updated_at = $now' < "$cfg" > "$tmp" && mv "$tmp" "$cfg" +} + +# write_dev_root_file → write the resolved LFX_DEV_ROOT path to +# $HOME/.lfx-skills/dev-root as a single line. Skills read this with `cat` so +# they don't have to source shell config, parse JSON, or depend on env vars. +write_dev_root_file() { + local devroot path + devroot="$(config_get lfx_dev_root)" + path="$(dev_root_path)" + mkdir -p "$(dirname "$path")" + printf '%s\n' "$devroot" > "$path" +} + +# config_set_cli_symlink PATH → record the lfx-skills CLI symlink path. +config_set_cli_symlink() { + config_set cli_symlink "$1" +} diff --git a/lib/doctor.sh b/lib/doctor.sh new file mode 100644 index 0000000..fa3e7b8 --- /dev/null +++ b/lib/doctor.sh @@ -0,0 +1,373 @@ +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +# +# Diagnostic checks for cli/lfx-skills doctor. Sourced; do not execute directly. +# +# Architecture: records flow as text. Each check writes pipe-delimited records +# to stdout; aggregators concatenate; formatters consume stdin and render either +# a human report or a JSON array. No temp files. +# +# Record shape: SEVERITY|CHECK_ID|CATEGORY|TITLE|DETAIL|FIXABLE|PAYLOAD +# SEVERITY: pass | warn | fail +# FIXABLE: yes | no +# PAYLOAD: optional structured datum the auto-fix can use directly +# (e.g. the symlink path for symlink-missing). Empty when N/A. +# Pipe character is forbidden in DETAIL/PAYLOAD (none of our checks use it). + +# _emit SEVERITY ID CATEGORY TITLE DETAIL FIXABLE [PAYLOAD] +_emit() { + printf '%s|%s|%s|%s|%s|%s|%s\n' "$1" "$2" "$3" "$4" "$5" "$6" "${7:-}" +} + +_doctor_skill_filter_matches() { + local skill_name="$1" + [ -z "${DOCTOR_SKILL_FILTER:-}" ] || [ "$skill_name" = "$DOCTOR_SKILL_FILTER" ] +} + +# ─── Category 1: Symlinks ──────────────────────────────────────────────── +check_symlinks() { + if ! config_exists; then + _emit warn no-config symlinks "No config.json found" \ + "Run \`lfx-skills install\` first." no + return + fi + local link source skill_md_target n=0 + while IFS= read -r link; do + [ -z "$link" ] && continue + n=$((n + 1)) + if [ ! -L "$link" ]; then + _emit fail symlink-missing symlinks \ + "Symlink missing: $(basename "$link")" \ + "Expected at $link" yes "$link" + continue + fi + source="$(readlink "$link" || true)" + if [ -z "$source" ] || [ ! -d "$source" ]; then + _emit fail symlink-broken symlinks \ + "Broken symlink: $(basename "$link")" \ + "$link → $source (target not a directory)" yes "$link" + continue + fi + skill_md_target="$source/SKILL.md" + if [ ! -f "$skill_md_target" ]; then + # Not fixable by the CLI: the symlink itself is correct; the source skill + # has no SKILL.md, which is a content gap, not an install issue. The Phase + # /lfx-skills-doctor can still act on this (e.g. offer to scaffold one). + _emit warn symlink-no-skillmd symlinks \ + "Symlink target missing SKILL.md: $(basename "$link")" \ + "$skill_md_target does not exist" no + continue + fi + _emit pass symlink-ok symlinks \ + "Symlink OK: $(basename "$link")" \ + "$link → $source" no + done < <(config_list_symlinks) + if [ "$n" -eq 0 ]; then + _emit warn no-symlinks symlinks "No symlinks recorded in config" \ + "Run \`lfx-skills install\` to install skills." no + fi +} + +# ─── Category 2: Source clone ──────────────────────────────────────────── +check_source_clone() { + local recorded current + recorded="$(config_get canonical_clone)" + current="${CLONE_ROOT:-$(probe_canonical_clone "${BASH_SOURCE[0]:-$0}")}" + if [ -z "$recorded" ]; then + _emit warn clone-not-recorded source_clone \ + "Canonical clone not recorded" \ + "config.json has no canonical_clone." no + elif [ "$recorded" != "$current" ]; then + _emit warn clone-mismatch source_clone \ + "Source clone path drifted" \ + "Recorded: $recorded; running from: $current" no + else + _emit pass clone-match source_clone \ + "Source clone matches config" "$current" no + fi + if [ -d "$current/.git" ]; then + if (cd "$current" && [ -z "$(git status --porcelain 2>/dev/null)" ]); then + _emit pass clone-clean source_clone "Clone working tree clean" "$current" no + else + _emit warn clone-dirty source_clone \ + "Clone working tree has uncommitted changes" \ + "Run \`git status\` in $current" no + fi + fi +} + +# ─── Category 3: LFX dev root ──────────────────────────────────────────── +check_lfx_dev_root() { + local recorded dev_root_file + recorded="$(config_get lfx_dev_root)" + dev_root_file="$(dev_root_path)" + + # The recorded path itself + if [ -z "$recorded" ]; then + _emit warn dev-root-not-recorded lfx_dev_root \ + "lfx_dev_root not recorded in config" \ + "Re-run \`lfx-skills install\` to capture it." no + elif [ ! -d "$recorded" ]; then + _emit fail dev-root-missing lfx_dev_root \ + "Recorded LFX dev root path does not exist" \ + "Recorded: $recorded" no + else + local repo_count + repo_count="$(probe_count_repos_in "$recorded")" + if [ "$repo_count" -eq 0 ]; then + _emit warn dev-root-empty lfx_dev_root \ + "LFX dev root contains no lf* git repos" \ + "$recorded — clone some LFX repos here." no + else + _emit pass dev-root-ok lfx_dev_root \ + "LFX dev root has $repo_count lf* repo(s)" "$recorded" no + fi + fi + + # The dev-root cache file that skills `cat` to resolve LFX_DEV_ROOT + if [ ! -f "$dev_root_file" ]; then + _emit fail dev-root-file-missing lfx_dev_root \ + "Skills' dev-root cache file is missing" \ + "Expected at $dev_root_file. Run \`lfx-skills config set lfx_dev_root=...\` to regenerate." yes "$dev_root_file" + else + local file_value + file_value="$(cat "$dev_root_file" 2>/dev/null)" + if [ "$file_value" != "$recorded" ]; then + _emit warn dev-root-file-mismatch lfx_dev_root \ + "dev-root file out of sync with config.json" \ + "File: $file_value; config: $recorded" yes "$dev_root_file" + else + _emit pass dev-root-file-ok lfx_dev_root \ + "dev-root cache file in sync with config" "$dev_root_file" no + fi + fi +} + +# ─── Category 4: Frontmatter ───────────────────────────────────────────── +check_frontmatter() { + local clone + clone="${CLONE_ROOT:-$(probe_canonical_clone "${BASH_SOURCE[0]:-$0}")}" + local skill_md skill_name name_in_md desc_in_md skill_issue + for skill_md in "$clone"/skills/lfx*/SKILL.md; do + [ -f "$skill_md" ] || continue + skill_name="$(basename "$(dirname "$skill_md")")" + _doctor_skill_filter_matches "$skill_name" || continue + skill_issue=0 + if [ "$(head -1 "$skill_md")" != "---" ]; then + _emit fail frontmatter-missing frontmatter \ + "$skill_name: SKILL.md missing frontmatter" \ + "$skill_md does not start with ---" no + continue + fi + local fm + fm="$(awk 'NR==1 && /^---$/ {start=1; next} start && /^---$/ {exit} start {print}' "$skill_md")" + name_in_md="$(printf '%s\n' "$fm" | awk -F': *' '/^name: */ {print $2; exit}')" + desc_in_md="$(printf '%s\n' "$fm" | awk -F': *' '/^description: */ {print $2; exit}')" + + if [ -z "$name_in_md" ]; then + skill_issue=1 + _emit fail frontmatter-no-name frontmatter \ + "$skill_name: missing required \`name\` field" "$skill_md" no + elif [ "$name_in_md" != "$skill_name" ]; then + skill_issue=1 + _emit fail frontmatter-name-mismatch frontmatter \ + "$skill_name: name field is \`$name_in_md\`, expected \`$skill_name\`" \ + "$skill_md" no + fi + if [ -z "$desc_in_md" ]; then + if ! printf '%s\n' "$fm" | awk '/^description:/{f=1; next} f && /^[a-z_-]+:/{exit} f && /^ +./{print; exit}' | grep -q '.'; then + skill_issue=1 + _emit warn frontmatter-no-description frontmatter \ + "$skill_name: missing or empty \`description\` field" "$skill_md" no + fi + fi + if [ "${DOCTOR_EMIT_FORMATTING_PASS:-0}" = "1" ] && [ "$skill_issue" -eq 0 ]; then + _emit pass frontmatter-ok frontmatter "$skill_name frontmatter OK" "$skill_md" no + fi + done +} + +# ─── Category 5: Routing ───────────────────────────────────────────────── +check_routing() { + local clone lfx_md + clone="${CLONE_ROOT:-$(probe_canonical_clone "${BASH_SOURCE[0]:-$0}")}" + lfx_md="$clone/skills/lfx/SKILL.md" + if [ ! -f "$lfx_md" ]; then + _emit warn routing-no-lfx routing "/lfx skill not found" \ + "$lfx_md missing — routing check skipped." no + return + fi + # Match /lfx-xxx only when the leading slash is at start-of-line or after a + # non-word, non-slash char. Excludes path references like apps/lfx-one/foo. + local routed s + routed="$(grep -oE '(^|[^a-zA-Z0-9_/])/lfx-[a-z0-9-]+' "$lfx_md" | sed 's|^[^/]*||' | sort -u)" + while IFS= read -r s; do + [ -z "$s" ] && continue + local skill_name="${s#/}" + if [ ! -d "$clone/skills/$skill_name" ]; then + _emit warn routing-dangling routing \ + "/lfx mentions $s but skill directory missing" \ + "Expected $clone/skills/$skill_name" no + fi + done <<EOF +$routed +EOF + # Inverse: every runtime/user-facing skill mentioned in /lfx? + # Skip /lfx itself and skills that intentionally don't belong in the user-task + # router: internal builders only invoked by /lfx-coordinator, and management + # surfaces (doctor / skills-helper) which the user reaches by name, not via + # plain-language routing. + local routing_exempt="lfx lfx-backend-builder lfx-ui-builder" + local skill skipped + while IFS= read -r skill; do + [ -z "$skill" ] && continue + skipped=0 + for ex in $routing_exempt; do + if [ "$skill" = "$ex" ]; then skipped=1; break; fi + done + [ "$skipped" -eq 1 ] && continue + if ! grep -qE "/${skill}([^a-z0-9-]|\$)" "$lfx_md" 2>/dev/null; then + _emit warn routing-uncovered routing \ + "/lfx routing table does not mention /$skill" \ + "Add an entry for /$skill in $lfx_md" no + fi + done < <(symlinks_runtime_skills "$clone") +} + +# ─── Category 6: License headers ───────────────────────────────────────── +check_license_headers() { + local clone + clone="${CLONE_ROOT:-$(probe_canonical_clone "${BASH_SOURCE[0]:-$0}")}" + local skill_md skill_name + for skill_md in "$clone"/skills/lfx*/SKILL.md; do + [ -f "$skill_md" ] || continue + skill_name="$(basename "$(dirname "$skill_md")")" + _doctor_skill_filter_matches "$skill_name" || continue + if head -4 "$skill_md" | grep -qF "Copyright The Linux Foundation"; then + _emit pass license-ok license_headers "$skill_name has license header" "$skill_md" no + else + _emit fail license-missing license_headers \ + "$skill_name missing license header in first 4 lines" "$skill_md" no + fi + done +} + +# ─── Aggregator ────────────────────────────────────────────────────────── +# doctor_run_all → emit every check's records to stdout, in category order. +doctor_run_all() { + check_symlinks + check_source_clone + check_lfx_dev_root + check_frontmatter + check_routing + check_license_headers +} + +# doctor_run_skill_formatting → only content-format checks. Used by +# /lfx-new-skill so scaffolding validation does not require an agents.md install. +doctor_run_skill_formatting() { + DOCTOR_EMIT_FORMATTING_PASS=1 check_frontmatter + check_license_headers +} + +# doctor_format_human → render results read from stdin as a colourised report. +# Single pass, buffering errors and warnings into shell variables before printing. +# Returns 0 if no failures, 1 otherwise. +doctor_format_human() { + local pass=0 warn=0 fail=0 + local errors_buf="" warnings_buf="" + local sev id cat title detail fixable payload line + while IFS='|' read -r sev id cat title detail fixable payload; do + case "$sev" in + pass) pass=$((pass + 1)) ;; + fail) + fail=$((fail + 1)) + line="$(printf ' %s✗%s [%s] %s' "$_UI_RED" "$_UI_RESET" "$id" "$title")" + errors_buf="${errors_buf}${line}"$'\n' + if [ -n "$detail" ]; then + errors_buf="${errors_buf}$(printf ' %s%s%s' "$_UI_DIM" "$detail" "$_UI_RESET")"$'\n' + fi + if [ "$fixable" = "yes" ]; then + errors_buf="${errors_buf}$(printf ' %s(fixable: lfx-skills doctor --fix)%s' "$_UI_DIM" "$_UI_RESET")"$'\n' + fi + ;; + warn) + warn=$((warn + 1)) + line="$(printf ' %s⚠%s [%s] %s' "$_UI_YELLOW" "$_UI_RESET" "$id" "$title")" + warnings_buf="${warnings_buf}${line}"$'\n' + if [ -n "$detail" ]; then + warnings_buf="${warnings_buf}$(printf ' %s%s%s' "$_UI_DIM" "$detail" "$_UI_RESET")"$'\n' + fi + if [ "$fixable" = "yes" ]; then + warnings_buf="${warnings_buf}$(printf ' %s(fixable: lfx-skills doctor --fix)%s' "$_UI_DIM" "$_UI_RESET")"$'\n' + fi + ;; + esac + done + + ui_section "LFX Skills Health Check" + printf '%s%d%s passed, %s%d%s warnings, %s%d%s errors\n\n' \ + "$_UI_GREEN" "$pass" "$_UI_RESET" \ + "$_UI_YELLOW" "$warn" "$_UI_RESET" \ + "$_UI_RED" "$fail" "$_UI_RESET" + + if [ "$fail" -gt 0 ]; then + ui_bold "Errors:" + printf '%s\n' "$errors_buf" + fi + if [ "$warn" -gt 0 ]; then + ui_bold "Warnings:" + printf '%s\n' "$warnings_buf" + fi + if [ "$fail" -eq 0 ] && [ "$warn" -eq 0 ]; then + ui_success "All checks passed." + fi + + [ "$fail" -eq 0 ] +} + +# doctor_format_json → render results read from stdin as a JSON array on stdout. +doctor_format_json() { + jq -Rsn ' + [inputs | split("\n")[] | select(length > 0) | split("|") | + {severity: .[0], id: .[1], category: .[2], title: .[3], detail: .[4], + fixable: (.[5] == "yes"), payload: (.[6] // "")}] + ' +} + +# doctor_fix_one ID PAYLOAD → apply the auto-fix for the given check. +# Returns 0 on success, 1 on failure or unknown ID. PAYLOAD comes straight from +# the structured 7th column of the result record — no string parsing required. +doctor_fix_one() { + local id="$1" payload="${2:-}" + case "$id" in + symlink-missing|symlink-broken) + local link="$payload" source + if [ -z "$link" ]; then + ui_error "Internal: $id record has no payload." + return 1 + fi + source="$(config_read | jq -r --arg l "$link" '(.symlinks // [])[] | select(.link == $l) | .source' | head -1)" + if [ -z "$source" ] || [ "$source" = "null" ]; then + ui_error "No manifest entry for $link — re-run \`lfx-skills install\` to recreate." + return 1 + fi + if [ ! -d "$source" ]; then + ui_error "Source skill missing on disk: $source" + return 1 + fi + [ -L "$link" ] && rm -f "$link" + mkdir -p "$(dirname "$link")" + ln -s "$source" "$link" + ui_success "Recreated symlink: $link → $source" + ;; + dev-root-file-missing|dev-root-file-mismatch) + write_dev_root_file + ui_success "Wrote $(dev_root_path)" + ;; + *) + ui_warn "No auto-fix available for: $id" + return 1 + ;; + esac +} diff --git a/lib/probe.sh b/lib/probe.sh new file mode 100644 index 0000000..c3700ae --- /dev/null +++ b/lib/probe.sh @@ -0,0 +1,103 @@ +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +# +# System probes for cli/lfx-skills. Sourced; do not execute directly. +# All probe_* functions have no side effects — they only inspect the system. + +# probe_clis → echo each detected agents.md-compatible CLI on its own line. +# Detects: codex, gemini, opencode. +probe_clis() { + local cli + for cli in codex gemini opencode; do + if command -v "$cli" >/dev/null 2>&1; then + printf '%s\n' "$cli" + fi + done +} + +# probe_agents_config_dirs → echo each ~/.agents*/ directory that exists. +probe_agents_config_dirs() { + local d + for d in "$HOME"/.agents*; do + [ -d "$d" ] || continue + printf '%s\n' "$d" + done +} + +# probe_dev_root_candidates → echo each existing path among common LFX dev root locations. +# Order matters: first match wins as the suggested default. +probe_dev_root_candidates() { + local d + for d in "$HOME/lf" "$HOME/lfx" "$HOME/code/lfx" "$HOME/work/lfx" "$HOME/dev/lfx"; do + [ -d "$d" ] || continue + printf '%s\n' "$d" + done +} + +# probe_repos_in DIR → echo each path under DIR that: +# - has a basename starting with "lf" +# - contains a .git directory or file (worktrees use .git as a file) +probe_repos_in() { + local dir="$1" + [ -d "$dir" ] || return 0 + local entry + for entry in "$dir"/lf*/; do + [ -d "$entry" ] || continue + if [ -e "${entry%/}/.git" ]; then + # strip trailing slash for display + printf '%s\n' "${entry%/}" + fi + done +} + +# probe_count_repos_in DIR → echo number of lf* git repos under DIR. +probe_count_repos_in() { + probe_repos_in "$1" | wc -l | tr -d ' ' +} + +# probe_shell_rcs → echo each shell rc file that exists. +# Kept for manifest context; install does not edit shell rc files. +probe_shell_rcs() { + local rc + for rc in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.profile" "$HOME/.config/fish/config.fish"; do + [ -f "$rc" ] && printf '%s\n' "$rc" + done +} + +# probe_have_jq → return 0 if jq is on PATH, 1 otherwise. +probe_have_jq() { + command -v jq >/dev/null 2>&1 +} + +# probe_writable_path_dir → echo the best dir on the user's PATH where we can +# create a symlink to `lfx-skills` without sudo. Returns empty if none qualifies. +# Preference (user-owned first): +# ~/.local/bin pipx convention; many Linux distros add this to PATH +# ~/bin older convention; some users still use it +# /opt/homebrew/bin Apple Silicon Homebrew; user-writable since brew chowns it +# /usr/local/bin Intel Macs and Linux; often root-owned, but writable for some setups +# A dir qualifies if it exists, is writable by the current user, and is on PATH. +probe_writable_path_dir() { + local d + for d in "$HOME/.local/bin" "$HOME/bin" "/opt/homebrew/bin" "/usr/local/bin"; do + [ -d "$d" ] || continue + [ -w "$d" ] || continue + case ":$PATH:" in + *":$d:"*) printf '%s\n' "$d"; return 0 ;; + esac + done + return 1 +} + +# probe_canonical_clone → echo the absolute path of the lfx-skills clone this script lives in. +# Caller passes the script's $0 (or any path inside the clone). +# Walks up from cli/ or lib/ to the clone root. +probe_canonical_clone() { + local script_path="$1" + local script_dir + script_dir="$(cd "$(dirname "$script_path")" && pwd)" + case "$(basename "$script_dir")" in + cli|lib) (cd "$script_dir/.." && pwd) ;; + *) printf '%s\n' "$script_dir" ;; + esac +} diff --git a/lib/symlinks.sh b/lib/symlinks.sh new file mode 100644 index 0000000..56805bb --- /dev/null +++ b/lib/symlinks.sh @@ -0,0 +1,364 @@ +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +# +# Symlink create/remove logic for cli/lfx-skills. Sourced; do not execute directly. +# 3-way create logic lifted from install.sh and battle-tested. + +# agents.md helper skills that should be installed alongside the runtime suite. +# These live under .agents/skills because they are install/manage helpers for +# this repo and are not part of the published lfx-skills runtime suite. +SYMLINKS_AGENTS_META_INSTALL="lfx-skills-doctor lfx-skills-helper" + +# symlinks_runtime_skills CLONE → echo each runtime skill directory name. +# Runtime/user-facing skills all live under skills/. Anything placed there is +# part of the LFX Skills suite and is installed for agents.md and exposed by +# the Claude Code plugin. +symlinks_runtime_skills() { + local clone="$1" + local skills_dir="$clone/skills" + local skill_path skill_name + for skill_path in "$skills_dir"/lfx*/; do + [ -d "$skill_path" ] || continue + [ -f "${skill_path}SKILL.md" ] || continue + skill_name="$(basename "${skill_path%/}")" + printf '%s\n' "$skill_name" + done +} + +# symlinks_agents_meta_skills CLONE → echo installable agents.md helper skills. +# Only doctor/helper are installed outside this repo. lfx-install and +# lfx-new-skill stay repo-local contributor helpers. +symlinks_agents_meta_skills() { + local clone="$1" + local skill + for skill in $SYMLINKS_AGENTS_META_INSTALL; do + [ -f "$clone/.agents/skills/$skill/SKILL.md" ] || continue + printf '%s\n' "$skill" + done +} + +# symlinks_eligible_skills CLONE → echo each agents.md-installable skill name. +symlinks_eligible_skills() { + local clone="$1" + symlinks_runtime_skills "$clone" + symlinks_agents_meta_skills "$clone" +} + +# symlinks_skill_source CLONE SKILL → echo the source directory for SKILL. +symlinks_skill_source() { + local clone="$1" skill="$2" + if [ -f "$clone/skills/$skill/SKILL.md" ]; then + printf '%s/skills/%s\n' "$clone" "$skill" + elif [ -f "$clone/.agents/skills/$skill/SKILL.md" ]; then + printf '%s/.agents/skills/%s\n' "$clone" "$skill" + else + return 1 + fi +} + +# symlinks_create_one SOURCE TARGET [PREVIOUS_SOURCE] → create or update one symlink. +# Echoes one of: installed | updated | skipped | failed +# Stderr gets a one-line human-readable note. +# Existing symlinks are updated only when they already point to SOURCE, or when +# config.json records the existing source as one this CLI previously installed. +symlinks_create_one() { + local source="$1" target="$2" previous="${3:-}" + local name; name="$(basename "$target")" + if [ -L "$target" ]; then + local actual + actual="$(readlink "$target" 2>/dev/null || true)" + if [ "$actual" != "$source" ] && { [ -z "$previous" ] || [ "$actual" != "$previous" ]; }; then + ui_warn " skipped $name (symlink points to $actual, not this lfx-skills install)" + printf 'skipped\n' + return 0 + fi + rm "$target" + if ln -s "$source" "$target"; then + ui_dim " updated $name" >&2 + printf 'updated\n' + else + ui_warn " failed $name" + printf 'failed\n' + fi + elif [ -e "$target" ]; then + ui_warn " skipped $name (non-symlink already exists at $target)" + printf 'skipped\n' + else + if ln -s "$source" "$target"; then + ui_dim " installed $name" >&2 + printf 'installed\n' + else + ui_warn " failed $name" + printf 'failed\n' + fi + fi +} + +# symlinks_install_all CLONE SCOPE BASE +# CLONE: absolute path to lfx-skills clone +# SCOPE: global | repo +# BASE: for global → config dir; for repo → repo path +# Creates one symlink per eligible skill in CLONE into the agents.md target dir. +# Records each created symlink in config.json. +# Echoes a summary line: "<installed>/<updated>/<skipped>/<total>" +symlinks_install_all() { + local clone="$1" scope="$2" base="$3" + local target_dir + target_dir="$(target_dir_for_scope "$scope" "$base")" || { + ui_error "Unknown scope: $scope" + return 1 + } + mkdir -p "$target_dir" + + local n_installed=0 n_updated=0 n_skipped=0 n_total=0 + local skill source target outcome + + while IFS= read -r skill; do + [ -z "$skill" ] && continue + n_total=$((n_total + 1)) + source="$(symlinks_skill_source "$clone" "$skill")" || { + ui_warn " skipped $skill (source skill missing)" + n_skipped=$((n_skipped + 1)) + continue + } + target="$target_dir/$skill" + local previous_source="" + if config_exists; then + previous_source="$(config_read | jq -r --arg l "$target" '(.symlinks // [])[] | select(.link == $l) | .source' | head -1)" + [ "$previous_source" = "null" ] && previous_source="" + fi + outcome="$(symlinks_create_one "$source" "$target" "$previous_source")" + case "$outcome" in + installed) + n_installed=$((n_installed + 1)) + config_add_symlink "$scope" "$target" "$source" "$base" + ;; + updated) + n_updated=$((n_updated + 1)) + config_add_symlink "$scope" "$target" "$source" "$base" + ;; + skipped) + n_skipped=$((n_skipped + 1)) + ;; + esac + done < <(symlinks_eligible_skills "$clone") + + printf '%d/%d/%d/%d\n' "$n_installed" "$n_updated" "$n_skipped" "$n_total" +} + +# symlinks_remove_one TARGET [EXPECTED_SOURCE] +# Safely remove one symlink. If EXPECTED_SOURCE is given, only remove if it matches. +# Echoes: removed | not-a-symlink | wrong-source | missing +symlinks_remove_one() { + local target="$1" expected="${2:-}" + if [ ! -e "$target" ] && [ ! -L "$target" ]; then + printf 'missing\n' + return 0 + fi + if [ ! -L "$target" ]; then + ui_warn " refused $target (not a symlink — skipping for safety)" + printf 'not-a-symlink\n' + return 0 + fi + if [ -n "$expected" ]; then + local actual + actual="$(readlink "$target" || true)" + if [ "$actual" != "$expected" ]; then + ui_warn " refused $target (points to $actual, not expected $expected)" + printf 'wrong-source\n' + return 0 + fi + fi + rm "$target" + ui_dim " removed $target" >&2 + printf 'removed\n' +} + +# symlinks_uninstall_all [SCOPE_FILTER] [TARGET_FILTER] +# Walk config.json's symlinks array and remove each. +# Filters mirror config_list_symlinks: empty=all, global, repo+target. +# Updates config.json to drop removed entries. +# Echoes summary: "<removed>/<refused>/<missing>/<total>" +symlinks_uninstall_all() { + local scope_filter="${1:-}" target_filter="${2:-}" + local n_removed=0 n_refused=0 n_missing=0 n_total=0 + local link source outcome + + # Snapshot the list (config will mutate as we remove) + local snapshot + snapshot="$(config_list_symlinks "$scope_filter" "$target_filter")" + + while IFS= read -r link; do + [ -z "$link" ] && continue + n_total=$((n_total + 1)) + # Look up expected source from config (defensive) + source="$(config_read | jq -r --arg l "$link" '(.symlinks // [])[] | select(.link == $l) | .source' | head -1)" + outcome="$(symlinks_remove_one "$link" "$source")" + case "$outcome" in + removed) + n_removed=$((n_removed + 1)) + config_remove_symlink "$link" + ;; + missing) + n_missing=$((n_missing + 1)) + # Drop from config too — it's stale. + config_remove_symlink "$link" + ;; + *) + n_refused=$((n_refused + 1)) + ;; + esac + done <<EOF +$snapshot +EOF + + printf '%d/%d/%d/%d\n' "$n_removed" "$n_refused" "$n_missing" "$n_total" +} + +# symlinks_uninstall_legacy_claude_recorded +# Remove legacy config-recorded Claude symlinks, and drop stale records for +# missing links. Refuses links that no longer point to the recorded source. +symlinks_uninstall_legacy_claude_recorded() { + config_exists || { printf '0/0/0/0\n'; return 0; } + local n_removed=0 n_refused=0 n_missing=0 n_total=0 + local snapshot link source outcome + snapshot="$(config_read | jq -r '(.symlinks // [])[] | select(.platform == "claude") | .link')" + + while IFS= read -r link; do + [ -z "$link" ] && continue + n_total=$((n_total + 1)) + source="$(config_read | jq -r --arg l "$link" '(.symlinks // [])[] | select(.link == $l) | .source' | head -1)" + outcome="$(symlinks_remove_one "$link" "$source")" + case "$outcome" in + removed) + n_removed=$((n_removed + 1)) + config_remove_symlink "$link" + ;; + missing) + n_missing=$((n_missing + 1)) + config_remove_symlink "$link" + ;; + *) + n_refused=$((n_refused + 1)) + ;; + esac + done <<EOF +$snapshot +EOF + + printf '%d/%d/%d/%d\n' "$n_removed" "$n_refused" "$n_missing" "$n_total" +} + +# symlinks_uninstall_legacy_claude_root CLONE +# Remove old root ~/.claude/skills links only when they are LFX skill links and +# their target points into this lfx-skills clone. This intentionally does not +# touch arbitrary Claude skills or non-symlink files. +symlinks_uninstall_legacy_claude_root() { + local clone="$1" + local target_dir="$HOME/.claude/skills" + local n_removed=0 n_refused=0 n_missing=0 n_total=0 + [ -d "$target_dir" ] || { printf '0/0/0/0\n'; return 0; } + + local link name actual resolved dir base + for link in "$target_dir"/lfx "$target_dir"/lfx-*; do + [ -e "$link" ] || [ -L "$link" ] || continue + n_total=$((n_total + 1)) + name="$(basename "$link")" + case "$name" in + lfx|lfx-*) ;; + *) n_refused=$((n_refused + 1)); continue ;; + esac + if [ ! -L "$link" ]; then + ui_warn " refused $link (not a symlink — skipping for safety)" + n_refused=$((n_refused + 1)) + continue + fi + actual="$(readlink "$link" || true)" + case "$actual" in + /*) resolved="$actual" ;; + *) + dir="$(dirname "$link")/$(dirname "$actual")" + base="$(basename "$actual")" + if [ -d "$dir" ]; then + resolved="$(cd "$dir" && pwd -P)/$base" + else + resolved="$actual" + fi + ;; + esac + case "$resolved" in + "$clone"/*) + rm "$link" + ui_dim " removed $link" >&2 + n_removed=$((n_removed + 1)) + ;; + *) + ui_warn " refused $link (points to $actual, not this lfx-skills install)" + n_refused=$((n_refused + 1)) + ;; + esac + done + + printf '%d/%d/%d/%d\n' "$n_removed" "$n_refused" "$n_missing" "$n_total" +} + +# install_cli_symlink CLONE [TARGET_DIR] +# Symlink <CLONE>/cli/lfx-skills into a writable PATH dir so the user can type +# `lfx-skills` from anywhere — no shell rc edit required. If TARGET_DIR is not +# given, picks the best candidate via probe_writable_path_dir. +# Echoes the symlink path on success (e.g. "/Users/x/.local/bin/lfx-skills"). +# Returns 1 if no writable PATH dir is available, OR if the target is occupied +# by anything other than the exact symlink this clone owns. Never edits PATH, +# and never silently clobbers a user's own script or a foreign symlink. The +# caller should print fallback instructions. +install_cli_symlink() { + local clone="$1" target_dir="${2:-}" + local source target + source="$clone/cli/lfx-skills" + [ -x "$source" ] || return 1 + if [ -z "$target_dir" ]; then + target_dir="$(probe_writable_path_dir)" || return 1 + fi + [ -d "$target_dir" ] || return 1 + [ -w "$target_dir" ] || return 1 + target="$target_dir/lfx-skills" + if [ -L "$target" ]; then + # An existing symlink at the target is only safe to replace when it already + # points exactly where this install wants it to point. Anything else may be + # user-managed, from another checkout, or from another tool. + local actual; actual="$(readlink "$target" 2>/dev/null || true)" + if [ "$actual" = "$source" ]; then + : # already correct — fall through to ln (will recreate identically) + rm "$target" + else + ui_warn "Refused to overwrite existing symlink at $target → $actual" + return 1 + fi + elif [ -e "$target" ]; then + ui_warn "Refused to overwrite existing non-symlink at $target" + return 1 + fi + if ln -s "$source" "$target"; then + printf '%s\n' "$target" + return 0 + fi + return 1 +} + +# remove_cli_symlink TARGET CLONE → remove TARGET only if it's a symlink that +# points into the canonical clone we passed in. Safe-by-default: refuses to +# delete anything that doesn't look like our install. Refuses on empty CLONE. +remove_cli_symlink() { + local target="$1" clone="${2:-}" + [ -L "$target" ] || return 0 + if [ -z "$clone" ]; then + ui_warn "Refused to remove $target (no canonical_clone in config — can't verify ownership)" + return 1 + fi + local actual; actual="$(readlink "$target" 2>/dev/null || true)" + if [ "${actual#"$clone/"}" = "$actual" ]; then + ui_warn "Refused to remove $target (points to $actual, outside $clone)" + return 1 + fi + rm -f "$target" +} diff --git a/lib/targets.sh b/lib/targets.sh new file mode 100644 index 0000000..bd166ec --- /dev/null +++ b/lib/targets.sh @@ -0,0 +1,21 @@ +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +# +# agents.md target path construction. Sourced; do not execute directly. + +# target_dir_for_scope SCOPE BASE +# SCOPE: global | repo +# BASE: for global → the agents config dir (e.g., $HOME/.agents) +# for repo → the repo path (e.g., $HOME/lf/lfx-v2-ui) +# Echoes the absolute target directory where skills should be symlinked. +target_dir_for_scope() { + local scope="$1" base="$2" + case "$scope" in + global) printf '%s/skills\n' "$base" ;; + repo) printf '%s/.agents/skills\n' "$base" ;; + *) return 1 ;; + esac +} + +# default_agents_config_dir → echo the conventional global agents config dir. +default_agents_config_dir() { printf '%s/.agents\n' "$HOME"; } diff --git a/lib/ui.sh b/lib/ui.sh new file mode 100644 index 0000000..a043097 --- /dev/null +++ b/lib/ui.sh @@ -0,0 +1,403 @@ +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +# +# UI helpers for cli/lfx-skills. Sourced; do not execute directly. +# Targets bash 3.2+ (stock macOS). + +# Color codes — only emitted when stdout is a TTY and NO_COLOR is unset. +if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then + _UI_RED=$'\033[31m' + _UI_GREEN=$'\033[32m' + _UI_YELLOW=$'\033[33m' + _UI_BLUE=$'\033[34m' + _UI_BOLD=$'\033[1m' + _UI_DIM=$'\033[2m' + _UI_RESET=$'\033[0m' +else + _UI_RED= + _UI_GREEN= + _UI_YELLOW= + _UI_BLUE= + _UI_BOLD= + _UI_DIM= + _UI_RESET= +fi + +ui_info() { printf '%s\n' "$*"; } +ui_step() { printf '%s==>%s %s\n' "$_UI_BLUE" "$_UI_RESET" "$*"; } +ui_success() { printf '%s✓%s %s\n' "$_UI_GREEN" "$_UI_RESET" "$*"; } +ui_warn() { printf '%s⚠%s %s\n' "$_UI_YELLOW" "$_UI_RESET" "$*" >&2; } +ui_error() { printf '%s✗%s %s\n' "$_UI_RED" "$_UI_RESET" "$*" >&2; } +ui_dim() { printf '%s%s%s\n' "$_UI_DIM" "$*" "$_UI_RESET"; } +ui_bold() { printf '%s%s%s\n' "$_UI_BOLD" "$*" "$_UI_RESET"; } +ui_blank() { printf '\n'; } + +# ui_die MSG → print error and exit 1. +ui_die() { ui_error "$*"; exit 1; } + +# ui_confirm "Question?" [default=N] → returns 0 if yes, 1 if no. +# Auto-answers Y when LFX_SKILLS_YES=1 (set by --yes flag). +# default arg: "Y" or "N" (case-insensitive). +ui_confirm() { + local prompt="$1" + local default="${2:-N}" + local hint reply + case "$default" in + [Yy]*) hint="[Y/n]" ;; + *) hint="[y/N]" ;; + esac + if [ "${LFX_SKILLS_YES:-0}" = "1" ]; then + printf '%s %s (auto-yes)\n' "$prompt" "$hint" + return 0 + fi + printf '%s %s ' "$prompt" "$hint" >&2 + read -r reply || reply="" + reply="${reply:-$default}" + case "$reply" in + [Yy]|[Yy][Ee][Ss]) return 0 ;; + *) return 1 ;; + esac +} + +# ui_input "Question?" [default] → echo user input to stdout (default if empty). +# Auto-uses default when LFX_SKILLS_YES=1. +ui_input() { + local prompt="$1" + local default="${2:-}" + local reply + if [ "${LFX_SKILLS_YES:-0}" = "1" ]; then + printf '%s\n' "$default" + return 0 + fi + if [ -n "$default" ]; then + printf '%s [%s]: ' "$prompt" "$default" >&2 + else + printf '%s: ' "$prompt" >&2 + fi + read -r reply || reply="" + printf '%s\n' "${reply:-$default}" +} + +# ui_select "Question?" OPT1 OPT2 ... → echo chosen option to stdout. +# Auto-picks first option when LFX_SKILLS_YES=1. +ui_select() { + local prompt="$1"; shift + local n=$# + if [ "$n" -eq 0 ]; then + ui_die "ui_select: no options provided" + fi + if [ "${LFX_SKILLS_YES:-0}" = "1" ]; then + printf '%s\n' "$1" + return 0 + fi + printf '%s\n' "$prompt" >&2 + local i=1 + for opt in "$@"; do + printf ' %d) %s\n' "$i" "$opt" >&2 + i=$((i + 1)) + done + local reply + while true; do + printf 'Choose 1-%d: ' "$n" >&2 + read -r reply || reply="" + case "$reply" in + ''|*[!0-9]*) ui_warn "Enter a number 1-$n." ;; + *) + if [ "$reply" -ge 1 ] && [ "$reply" -le "$n" ]; then + # shift is 1-indexed, so use eval to grab nth arg + eval "printf '%s\n' \"\${$reply}\"" + return 0 + else + ui_warn "Out of range. Pick 1-$n." + fi + ;; + esac + done +} + +# ui_multiselect "Question?" "default-spec" OPT1 OPT2 ... +# default-spec: comma-separated 1-indexed positions selected by default +# (e.g., "1,3" or "" for none, "all" for everything). +# Echoes one selected option per line. +# Auto-picks defaults when LFX_SKILLS_YES=1. +ui_multiselect() { + local prompt="$1"; shift + local default_spec="$1"; shift + local n=$# + if [ "$n" -eq 0 ]; then + return 0 + fi + local -a opts + local i=1 + for opt in "$@"; do + opts[i]="$opt" + i=$((i + 1)) + done + + _expand_default_spec() { + case "$1" in + all) seq 1 "$n" | tr '\n' ',' | sed 's/,$//' ;; + none|"") echo "" ;; + *) echo "$1" ;; + esac + } + + if [ "${LFX_SKILLS_YES:-0}" = "1" ]; then + local resolved + resolved="$(_expand_default_spec "$default_spec")" + if [ -z "$resolved" ]; then + return 0 + fi + local IFS=',' + for idx in $resolved; do + printf '%s\n' "${opts[$idx]}" + done + return 0 + fi + + printf '%s\n' "$prompt" >&2 + i=1 + for opt in "$@"; do + printf ' %d) %s\n' "$i" "$opt" >&2 + i=$((i + 1)) + done + + local hint default_resolved + default_resolved="$(_expand_default_spec "$default_spec")" + if [ -n "$default_resolved" ]; then + hint="comma-separated, e.g. \"1,3\"; \"all\"; \"none\"; or Enter for default [$default_resolved]" + else + hint="comma-separated, e.g. \"1,3\"; \"all\"; \"none\"; or Enter for none" + fi + + local reply + while true; do + printf '%s: ' "$hint" >&2 + read -r reply || reply="" + if [ -z "$reply" ]; then + reply="$default_resolved" + fi + case "$reply" in + all) reply="$(seq 1 "$n" | tr '\n' ',' | sed 's/,$//')" ;; + none) return 0 ;; + esac + if [ -z "$reply" ]; then + return 0 + fi + # Validate each token is a number in range + local valid=1 token + local IFS=',' + for token in $reply; do + token="${token# }"; token="${token% }" + case "$token" in + ''|*[!0-9]*) valid=0; break ;; + *) + if [ "$token" -lt 1 ] || [ "$token" -gt "$n" ]; then + valid=0; break + fi + ;; + esac + done + if [ "$valid" -eq 1 ]; then + for token in $reply; do + token="${token# }"; token="${token% }" + printf '%s\n' "${opts[$token]}" + done + return 0 + fi + ui_warn "Invalid selection. Use comma-separated numbers in 1-$n, \"all\", or \"none\"." + done +} + +# ui_checkbox_select PROMPT YES_DEFAULT_SPEC MIN_REQUIRED OPT1 OPT2 ... +# PROMPT text shown above the menu +# YES_DEFAULT_SPEC selection used ONLY when LFX_SKILLS_YES=1 +# (interactive starts with nothing selected). +# Same format as ui_multiselect: "1,2", "all", "none"/"" +# MIN_REQUIRED minimum number of options that must be checked before +# the [continue] action is accepted (use 1 to require +# at least one selection) +# OPT* option strings (also the values emitted on confirm) +# +# Interactive UX: +# ↑ / ↓ or k / j move the cursor (wraps top↔continue) +# enter or space toggle the current checkbox +# enter on [continue] proceed (refused if fewer than MIN_REQUIRED checked) +# a select all +# n select none +# q or Esc cancel (returns 1, no output on stdout) +# +# Nothing is pre-selected in the interactive path. The user must explicitly +# toggle each checkbox they want and then move to [continue] to proceed. +# +# When LFX_SKILLS_YES=1, emits whatever YES_DEFAULT_SPEC resolves to and +# returns immediately — no rendering, no prompt. +# When stdin or stderr is not a TTY, falls back to the number-prompt +# implementation in ui_multiselect (so CI / piped input keeps working). +ui_checkbox_select() { + local prompt="$1" yes_default_spec="$2" min_required="$3" + shift 3 + local n=$# + [ "$n" -eq 0 ] && return 0 + + local i opt + local -a opts=() + for opt in "$@"; do opts[${#opts[@]}]="$opt"; done + + # Auto-pick path: resolve YES_DEFAULT_SPEC, emit immediately. + if [ "${LFX_SKILLS_YES:-0}" = "1" ]; then + case "$yes_default_spec" in + all) for ((i=0; i<n; i++)); do printf '%s\n' "${opts[i]}"; done ;; + none|"") ;; + *) + local _idx + for _idx in $(printf '%s' "$yes_default_spec" | tr ',' ' '); do + case "$_idx" in + ''|*[!0-9]*) continue ;; + *) + if [ "$_idx" -ge 1 ] && [ "$_idx" -le "$n" ]; then + printf '%s\n' "${opts[_idx-1]}" + fi + ;; + esac + done + ;; + esac + return 0 + fi + + # Non-TTY fallback: use the existing numbered ui_multiselect. + if ! [ -t 0 ] || ! [ -t 2 ]; then + ui_multiselect "$prompt" "$yes_default_spec" "${opts[@]}" + return $? + fi + + # Interactive: nothing pre-selected. User toggles, then moves to [continue]. + local -a selected=() + for ((i=0; i<n; i++)); do selected[i]=0; done + + # Total visible items = n options + 1 [continue] item at index n. + local total=$((n + 1)) + local cursor=0 + + # Save terminal state and ensure restore on any exit. + local _stty_saved + _stty_saved="$(stty -g 2>/dev/null || true)" + trap 'stty '"'$_stty_saved'"' 2>/dev/null; printf "\033[?25h" >&2' EXIT INT TERM + stty -icanon -echo 2>/dev/null + printf '\033[?25l' >&2 # hide cursor + + # Header (printed once). + printf '%s\n' "$prompt" >&2 + printf ' %s↑/↓ move • enter or space toggle • a all • n none • [continue] to proceed • q cancel%s\n' \ + "$_UI_DIM" "$_UI_RESET" >&2 + + # Render the option block + the [continue] action line. Called repeatedly. + _ui_cb_render() { + local _i marker arrow count=0 + for ((_i=0; _i<n; _i++)); do + [ "${selected[_i]}" = "1" ] && count=$((count + 1)) + done + for ((_i=0; _i<n; _i++)); do + marker=' ' + arrow=' ' + [ "${selected[_i]}" = "1" ] && marker='x' + if [ "$_i" -eq "$cursor" ]; then + arrow='›' + printf '\033[K %s%s%s [%s] %s\n' "$_UI_BOLD" "$arrow" "$_UI_RESET" "$marker" "${opts[_i]}" >&2 + else + printf '\033[K %s [%s] %s\n' "$arrow" "$marker" "${opts[_i]}" >&2 + fi + done + # Continue line at index n. + if [ "$cursor" -eq "$n" ]; then + printf '\033[K %s›%s %s→ continue%s %s(%d selected)%s\n' \ + "$_UI_BOLD" "$_UI_RESET" "$_UI_BOLD" "$_UI_RESET" "$_UI_DIM" "$count" "$_UI_RESET" >&2 + else + printf '\033[K %s→ continue%s %s(%d selected)%s\n' \ + "$_UI_DIM" "$_UI_RESET" "$_UI_DIM" "$count" "$_UI_RESET" >&2 + fi + } + + _ui_cb_render + + # Event loop. + local key rest cancelled=0 count + while true; do + IFS= read -rsn1 key || break + case "$key" in + $'\e') + # Arrow keys arrive as ESC '[' 'A'/'B'/'C'/'D' in one burst — the + # next two bytes are already in the buffer, so the read returns + # instantly. A bare Esc waits for the timeout. Bash 3.2 only supports + # integer `read -t`, so 1 second is the floor for bare Esc. + IFS= read -rsn2 -t 1 rest || rest="" + case "$rest" in + '[A') cursor=$(( (cursor - 1 + total) % total )) ;; # Up + '[B') cursor=$(( (cursor + 1) % total )) ;; # Down + '') cancelled=1; break ;; # bare Esc + *) ;; # ignore other + esac + ;; + 'k') cursor=$(( (cursor - 1 + total) % total )) ;; # vim up + 'j') cursor=$(( (cursor + 1) % total )) ;; # vim down + ' '|'') + # Enter or space: on a checkbox toggles it; on [continue] proceeds. + if [ "$cursor" -lt "$n" ]; then + if [ "${selected[cursor]}" = "1" ]; then + selected[cursor]=0 + else + selected[cursor]=1 + fi + else + # Cursor on [continue] + count=0 + for ((i=0; i<n; i++)); do + [ "${selected[i]}" = "1" ] && count=$((count + 1)) + done + if [ "$count" -ge "$min_required" ]; then + break + fi + # Below threshold — render an inline warning beneath the menu and + # continue waiting. The next redraw overwrites it. + printf '\033[K %s(select at least %d before continuing)%s' \ + "$_UI_YELLOW" "$min_required" "$_UI_RESET" >&2 + # Sleep briefly so the user sees the warning before redraw clears it. + sleep 1 + fi + ;; + 'a'|'A') for ((i=0; i<n; i++)); do selected[i]=1; done ;; + 'n'|'N') for ((i=0; i<n; i++)); do selected[i]=0; done ;; + 'q'|'Q') cancelled=1; break ;; + *) ;; + esac + # Move cursor up to start of options block (n options + continue line), + # then redraw the whole block. + printf '\033[%dA' "$total" >&2 + _ui_cb_render + done + + # Restore terminal state. + stty "$_stty_saved" 2>/dev/null + printf '\033[?25h' >&2 + trap - EXIT INT TERM + + if [ "$cancelled" -eq 1 ]; then + printf '\n' >&2 + return 1 + fi + + # Emit selected options on stdout. + for ((i=0; i<n; i++)); do + [ "${selected[i]}" = "1" ] && printf '%s\n' "${opts[i]}" + done + return 0 +} + +# ui_section "Title" → bold heading + underline. +ui_section() { + local title="$1" + local len=${#title} + printf '\n%s%s%s\n' "$_UI_BOLD" "$title" "$_UI_RESET" + printf '%s\n' "$(printf '%*s' "$len" '' | tr ' ' '─')" +} diff --git a/lfx-backend-builder/SKILL.md b/skills/lfx-backend-builder/SKILL.md similarity index 99% rename from lfx-backend-builder/SKILL.md rename to skills/lfx-backend-builder/SKILL.md index 8a72dcc..8a74e80 100644 --- a/lfx-backend-builder/SKILL.md +++ b/skills/lfx-backend-builder/SKILL.md @@ -1,4 +1,6 @@ --- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT name: lfx-backend-builder description: > Generate compliant backend code for LFX repos — Express.js proxy endpoints @@ -7,8 +9,6 @@ description: > allowed-tools: Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion --- -<!-- Copyright The Linux Foundation and each contributor to LFX. --> -<!-- SPDX-License-Identifier: MIT --> <!-- Tool names in this file use Claude Code vocabulary. See docs/tool-mapping.md for other platforms. --> # LFX Backend Code Generation diff --git a/lfx-backend-builder/references/backend-endpoint.md b/skills/lfx-backend-builder/references/backend-endpoint.md similarity index 100% rename from lfx-backend-builder/references/backend-endpoint.md rename to skills/lfx-backend-builder/references/backend-endpoint.md diff --git a/lfx-backend-builder/references/fga-patterns.md b/skills/lfx-backend-builder/references/fga-patterns.md similarity index 100% rename from lfx-backend-builder/references/fga-patterns.md rename to skills/lfx-backend-builder/references/fga-patterns.md diff --git a/lfx-backend-builder/references/getting-started.md b/skills/lfx-backend-builder/references/getting-started.md similarity index 100% rename from lfx-backend-builder/references/getting-started.md rename to skills/lfx-backend-builder/references/getting-started.md diff --git a/lfx-backend-builder/references/goa-patterns.md b/skills/lfx-backend-builder/references/goa-patterns.md similarity index 100% rename from lfx-backend-builder/references/goa-patterns.md rename to skills/lfx-backend-builder/references/goa-patterns.md diff --git a/lfx-backend-builder/references/helm-chart.md b/skills/lfx-backend-builder/references/helm-chart.md similarity index 100% rename from lfx-backend-builder/references/helm-chart.md rename to skills/lfx-backend-builder/references/helm-chart.md diff --git a/lfx-backend-builder/references/indexer-patterns.md b/skills/lfx-backend-builder/references/indexer-patterns.md similarity index 100% rename from lfx-backend-builder/references/indexer-patterns.md rename to skills/lfx-backend-builder/references/indexer-patterns.md diff --git a/lfx-backend-builder/references/nats-messaging.md b/skills/lfx-backend-builder/references/nats-messaging.md similarity index 100% rename from lfx-backend-builder/references/nats-messaging.md rename to skills/lfx-backend-builder/references/nats-messaging.md diff --git a/lfx-backend-builder/references/new-service.md b/skills/lfx-backend-builder/references/new-service.md similarity index 100% rename from lfx-backend-builder/references/new-service.md rename to skills/lfx-backend-builder/references/new-service.md diff --git a/lfx-backend-builder/references/query-service.md b/skills/lfx-backend-builder/references/query-service.md similarity index 100% rename from lfx-backend-builder/references/query-service.md rename to skills/lfx-backend-builder/references/query-service.md diff --git a/lfx-backend-builder/references/service-types.md b/skills/lfx-backend-builder/references/service-types.md similarity index 100% rename from lfx-backend-builder/references/service-types.md rename to skills/lfx-backend-builder/references/service-types.md diff --git a/lfx-cdp-snowflake-connectors/SKILL.md b/skills/lfx-cdp-snowflake-connectors/SKILL.md similarity index 97% rename from lfx-cdp-snowflake-connectors/SKILL.md rename to skills/lfx-cdp-snowflake-connectors/SKILL.md index d5058b8..54eff7f 100644 --- a/lfx-cdp-snowflake-connectors/SKILL.md +++ b/skills/lfx-cdp-snowflake-connectors/SKILL.md @@ -1,4 +1,6 @@ --- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT name: lfx-cdp-snowflake-connectors description: Use when adding a new snowflake-connector data source to crowd.dev — a new platform or a new source within an existing platform that needs buildSourceQuery, transformer, activity types, migration, and all associated type registrations scaffolded. allowed-tools: Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion, mcp__claude_ai_LFX_BI_Layer__get_all_sources, mcp__claude_ai_LFX_BI_Layer__get_source_details, mcp__claude_ai_LFX_BI_Layer__list_metrics, mcp__claude_ai_LFX_BI_Layer__get_dimensions, mcp__claude_ai_LFX_BI_Layer__query_metrics @@ -6,6 +8,18 @@ allowed-tools: Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion, mcp__claude # Scaffold Snowflake Connector +## Prerequisites + +This skill requires the **LFX BI Layer MCP server** to be configured in your AI tool. The skill calls these MCP tools to discover Snowflake source schemas, dimensions, and metrics: + +- `mcp__claude_ai_LFX_BI_Layer__get_all_sources` +- `mcp__claude_ai_LFX_BI_Layer__get_source_details` +- `mcp__claude_ai_LFX_BI_Layer__list_metrics` +- `mcp__claude_ai_LFX_BI_Layer__get_dimensions` +- `mcp__claude_ai_LFX_BI_Layer__query_metrics` + +If these tools are not available in your environment, the skill will fail in Phase 2 (Schema Collection) when querying source schemas. Set up the LFX BI Layer MCP server before invoking this skill — see your team's MCP onboarding docs. + ## Overview This skill scaffolds all code required to add a new snowflake-connector data source to crowd.dev. It covers up to 11 touch points, enforces zero-assumption practices, and requires explicit user validation for every piece of business logic before writing any file to disk. diff --git a/lfx-coordinator/SKILL.md b/skills/lfx-coordinator/SKILL.md similarity index 85% rename from lfx-coordinator/SKILL.md rename to skills/lfx-coordinator/SKILL.md index 55f861b..052a18d 100644 --- a/lfx-coordinator/SKILL.md +++ b/skills/lfx-coordinator/SKILL.md @@ -1,4 +1,6 @@ --- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT name: lfx-coordinator description: > Guided development workflow for building, fixing, updating, or refactoring @@ -8,14 +10,23 @@ description: > allowed-tools: Bash, Read, Glob, Grep, AskUserQuestion, Skill --- -<!-- Copyright The Linux Foundation and each contributor to LFX. --> -<!-- SPDX-License-Identifier: MIT --> <!-- Tool names in this file use Claude Code vocabulary. See docs/tool-mapping.md for other platforms. --> # LFX Development Coordinator You coordinate development across LFX repos. You NEVER write code — you delegate ALL code changes to `/lfx-backend-builder` and `/lfx-ui-builder`. You do not have Write or Edit tools. +## Defer to specialized skills when applicable + +Before starting your normal coordination workflow, check whether the request belongs to a specialized skill that handles its own end-to-end flow. If so, tell the user and stop: + +- **Intercom integration work** (add Intercom to an Angular app, fix JWT setup, audit CSP, fix shutdown, fix Auth0 claim) → defer to `/lfx-intercom`. That skill audits and fixes against the LFX canonical Intercom pattern. +- **CDP / crowd.dev Snowflake connectors** (add a new snowflake-connector data source, scaffold buildSourceQuery + transformer + activity types + migration) → defer to `/lfx-cdp-snowflake-connectors`. That skill enforces zero-assumption scaffolding with explicit user validation per business-logic decision. + +If the request matches one of these, respond with: "This is best handled by `/<specialized-skill>` — it owns the full workflow for this. Want me to hand off?" Do not attempt to coordinate it yourself. + +For everything else (full-stack feature work, bug fixes, refactors across the regular LFX repos), continue with the workflow below. + ## Input Validation Before starting, verify you have enough context. Auto-detect as much as possible — minimize questions to the user. @@ -62,16 +73,46 @@ Step 7: Summary — report results, suggest /lfx-preflight Read the relevant reference before planning work that involves query service usage, indexing changes, or adding fields to indexed resources. +## Dev Root Setup + +Before relying on local upstream service repos, resolve the LFX dev root: + +```bash +if [ -f "$HOME/.lfx-skills/dev-root" ]; then + LFX_DEV_ROOT="$(cat "$HOME/.lfx-skills/dev-root")" +else + LFX_DEV_ROOT="$HOME/lf" +fi +printf '%s\n' "$LFX_DEV_ROOT" +``` + +If `~/.lfx-skills/dev-root` already exists, use it without asking and continue. Do not list or scan repos during setup. + +If `~/.lfx-skills/dev-root` is missing, ask a quick question: whether the user wants to set an LFX dev root now or use the default `~/lf`. Explain that setting it lets skills read local LFX repos directly, which is faster and more reliable than falling back to GitHub API calls. Suggest likely locations: `~/lf`, `~/lfx`, `~/src/lfx`, or the parent directory containing their LFX clones. + +If they accept, ask for the path, then write it: + +```bash +mkdir -p ~/.lfx-skills +printf '%s\n' "<chosen-dev-root>" > ~/.lfx-skills/dev-root +``` + +If they choose the default, continue with `~/lf` and use GitHub fallbacks when local repos are unavailable. + ## Step 3: Research (do this inline — NOT via Skill delegation) Use your Read, Glob, Grep, and Bash tools to quickly check: - **Identify the upstream Go service from the code.** Read the Express proxy service file (e.g., `committee.service.ts`) and find which API paths it calls via `MicroserviceProxyService` (e.g., `/committees/...` means `lfx-v2-committee-service`). All services use `'LFX_V2_SERVICE'` as the env var — the API path prefix tells you which upstream Go repo owns the data. Then check if that repo exists locally: ```bash + # Resolve the LFX dev root from the one-line file written by `lfx-skills install`, + # falling back to $HOME/lf when not installed. No shell rc / env var required. + LFX_DEV_ROOT="${LFX_DEV_ROOT:-$(cat ~/.lfx-skills/dev-root 2>/dev/null || echo "$HOME/lf")}" + # Find the upstream service from the proxy code grep -r "proxyRequest" apps/lfx-one/src/server/services/<domain>.service.ts | head -5 # Check for local Go repos - ls -d ~/lf/lfx-v2-*-service 2>/dev/null + ls -d "$LFX_DEV_ROOT"/lfx-v2-*-service 2>/dev/null ``` - **Check the upstream Go service for the needed field.** Once you've identified the repo, check its domain model, Goa design, and conversions: ```bash @@ -132,7 +173,7 @@ Risk flags: When research reveals that the upstream Go microservice is missing a field or endpoint that the feature requires, the delegation plan MUST include a `/lfx-backend-builder` call for the Go repo. This is where the data model lives — without it, the field won't persist. The Go service is the source of truth. -**You discover the upstream service during research (Step 3)** by reading the Express proxy code. Do NOT hardcode paths — use what you found. For example, if `committee.service.ts` calls `proxyRequest(req, 'LFX_V2_SERVICE', '/committees/...')`, the API path prefix `/committees/` tells you the upstream is `lfx-v2-committee-service` and you check `~/lf/lfx-v2-committee-service/` for local availability. +**You discover the upstream service during research (Step 3)** by reading the Express proxy code. Do NOT hardcode paths — use what you found. For example, if `committee.service.ts` calls `proxyRequest(req, 'LFX_V2_SERVICE', '/committees/...')`, the API path prefix `/committees/` tells you the upstream is `lfx-v2-committee-service` and you check `${LFX_DEV_ROOT:-$HOME/lf}/lfx-v2-committee-service/` for local availability. **Common Go service changes for adding a field:** - Domain model: `internal/domain/model/*.go` — add the field to the struct diff --git a/lfx-coordinator/references/fga-protected-types.md b/skills/lfx-coordinator/references/fga-protected-types.md similarity index 100% rename from lfx-coordinator/references/fga-protected-types.md rename to skills/lfx-coordinator/references/fga-protected-types.md diff --git a/lfx-coordinator/references/indexed-data-types.md b/skills/lfx-coordinator/references/indexed-data-types.md similarity index 100% rename from lfx-coordinator/references/indexed-data-types.md rename to skills/lfx-coordinator/references/indexed-data-types.md diff --git a/lfx-coordinator/references/shared-types.md b/skills/lfx-coordinator/references/shared-types.md similarity index 100% rename from lfx-coordinator/references/shared-types.md rename to skills/lfx-coordinator/references/shared-types.md diff --git a/lfx-git-setup/SKILL.md b/skills/lfx-git-setup/SKILL.md similarity index 98% rename from lfx-git-setup/SKILL.md rename to skills/lfx-git-setup/SKILL.md index 4d57c70..d9c54ea 100644 --- a/lfx-git-setup/SKILL.md +++ b/skills/lfx-git-setup/SKILL.md @@ -1,4 +1,6 @@ --- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT name: lfx-git-setup description: > Interactive setup guide for LFX contributors to configure Git for DCO signoff @@ -13,9 +15,6 @@ description: > allowed-tools: Bash, Read, Glob, Grep, AskUserQuestion, WebFetch --- -<!-- Copyright The Linux Foundation and each contributor to LFX. --> -<!-- SPDX-License-Identifier: MIT --> - # LFX git Setup: DCO Signoff & GPG Signed Commits This skill walks contributors through two essential git contribution diff --git a/lfx-git-setup/references/linux.md b/skills/lfx-git-setup/references/linux.md similarity index 100% rename from lfx-git-setup/references/linux.md rename to skills/lfx-git-setup/references/linux.md diff --git a/lfx-git-setup/references/mac.md b/skills/lfx-git-setup/references/mac.md similarity index 100% rename from lfx-git-setup/references/mac.md rename to skills/lfx-git-setup/references/mac.md diff --git a/lfx-git-setup/references/windows.md b/skills/lfx-git-setup/references/windows.md similarity index 100% rename from lfx-git-setup/references/windows.md rename to skills/lfx-git-setup/references/windows.md diff --git a/lfx-intercom/SKILL.md b/skills/lfx-intercom/SKILL.md similarity index 99% rename from lfx-intercom/SKILL.md rename to skills/lfx-intercom/SKILL.md index 05313ae..691a55c 100644 --- a/lfx-intercom/SKILL.md +++ b/skills/lfx-intercom/SKILL.md @@ -1,4 +1,6 @@ --- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT name: lfx-intercom description: > Add or fix Intercom integration in an LFX Angular app. Detects existing @@ -8,8 +10,6 @@ description: > allowed-tools: Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion --- -<!-- Copyright The Linux Foundation and each contributor to LFX. --> -<!-- SPDX-License-Identifier: MIT --> <!-- Tool names in this file use Claude Code vocabulary. See docs/tool-mapping.md for other platforms. --> # LFX Intercom Integration Skill diff --git a/lfx-pr-catchup/SKILL.md b/skills/lfx-pr-catchup/SKILL.md similarity index 93% rename from lfx-pr-catchup/SKILL.md rename to skills/lfx-pr-catchup/SKILL.md index ed54646..3213f0f 100644 --- a/lfx-pr-catchup/SKILL.md +++ b/skills/lfx-pr-catchup/SKILL.md @@ -1,4 +1,6 @@ --- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT name: lfx-pr-catchup description: > Morning PR catch-up dashboard — shows unresolved comments, status changes, @@ -6,8 +8,6 @@ description: > allowed-tools: Bash, Read, Glob, Grep, AskUserQuestion --- -<!-- Copyright The Linux Foundation and each contributor to LFX. --> -<!-- SPDX-License-Identifier: MIT --> <!-- Tool names in this file use Claude Code vocabulary. See docs/tool-mapping.md for other platforms. --> # PR Catch-Up Dashboard @@ -242,7 +242,18 @@ gh pr view $NUMBER --repo $OWNER/$REPO --json comments,reviews,statusCheckRollup gh pr checks $NUMBER --repo $OWNER/$REPO ``` -Present the drill-down in a readable format, then offer to drill into another PR or end. +Present the drill-down in a readable format. + +If the drilled-down PR has unresolved review comments (HIGH signal), proactively suggest: + +``` +This PR has <N> unresolved comments. Want me to address them? +Run: /lfx-pr-resolve <pr-number> (e.g., /lfx-pr-resolve #142) +``` + +Substitute the actual PR number (the same one the user just drilled into) when surfacing the suggestion. + +Then offer to drill into another PR or end. ## Scope Boundaries diff --git a/lfx-pr-resolve/SKILL.md b/skills/lfx-pr-resolve/SKILL.md similarity index 99% rename from lfx-pr-resolve/SKILL.md rename to skills/lfx-pr-resolve/SKILL.md index 998fd70..6a6fc3c 100644 --- a/lfx-pr-resolve/SKILL.md +++ b/skills/lfx-pr-resolve/SKILL.md @@ -1,4 +1,6 @@ --- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT name: lfx-pr-resolve description: > Address PR review comments — fetches unresolved threads, makes code changes, @@ -9,8 +11,6 @@ description: > allowed-tools: Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion, Skill --- -<!-- Copyright The Linux Foundation and each contributor to LFX. --> -<!-- SPDX-License-Identifier: MIT --> <!-- Tool names in this file use Claude Code vocabulary. See docs/tool-mapping.md for other platforms. --> # PR Review Comment Resolver diff --git a/lfx-pr-resolve/evals/evals.json b/skills/lfx-pr-resolve/evals/evals.json similarity index 100% rename from lfx-pr-resolve/evals/evals.json rename to skills/lfx-pr-resolve/evals/evals.json diff --git a/lfx-preflight/SKILL.md b/skills/lfx-preflight/SKILL.md similarity index 99% rename from lfx-preflight/SKILL.md rename to skills/lfx-preflight/SKILL.md index f5d1fbb..df9309a 100644 --- a/lfx-preflight/SKILL.md +++ b/skills/lfx-preflight/SKILL.md @@ -1,4 +1,6 @@ --- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT name: lfx-preflight description: > Pre-PR validation for any LFX repo — Phase 1 auto-fixes (license, format, lint, build), @@ -7,8 +9,6 @@ description: > allowed-tools: Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion --- -<!-- Copyright The Linux Foundation and each contributor to LFX. --> -<!-- SPDX-License-Identifier: MIT --> <!-- Tool names in this file use Claude Code vocabulary. See docs/tool-mapping.md for other platforms. --> # Pre-Submission Preflight Check diff --git a/lfx-product-architect/SKILL.md b/skills/lfx-product-architect/SKILL.md similarity index 99% rename from lfx-product-architect/SKILL.md rename to skills/lfx-product-architect/SKILL.md index abc8453..7d98870 100644 --- a/lfx-product-architect/SKILL.md +++ b/skills/lfx-product-architect/SKILL.md @@ -1,4 +1,6 @@ --- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT name: lfx-product-architect description: > Understand LFX system architecture, decide where code should go, trace data flows, @@ -8,8 +10,6 @@ description: > allowed-tools: Bash, Read, Glob, Grep, AskUserQuestion --- -<!-- Copyright The Linux Foundation and each contributor to LFX. --> -<!-- SPDX-License-Identifier: MIT --> <!-- Tool names in this file use Claude Code vocabulary. See docs/tool-mapping.md for other platforms. --> # LFX Architecture Guide diff --git a/lfx-research/SKILL.md b/skills/lfx-research/SKILL.md similarity index 83% rename from lfx-research/SKILL.md rename to skills/lfx-research/SKILL.md index c1e5a9f..f9b93e8 100644 --- a/lfx-research/SKILL.md +++ b/skills/lfx-research/SKILL.md @@ -1,4 +1,6 @@ --- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT name: lfx-research description: > Read-only exploration skill for LFX repos — upstream API validation, codebase @@ -7,8 +9,6 @@ description: > allowed-tools: Bash, Read, Glob, Grep, AskUserQuestion, WebFetch --- -<!-- Copyright The Linux Foundation and each contributor to LFX. --> -<!-- SPDX-License-Identifier: MIT --> <!-- Tool names in this file use Claude Code vocabulary. See docs/tool-mapping.md for other platforms. --> # LFX Research & Exploration @@ -85,7 +85,38 @@ gh api repos/linuxfoundation/<repo-name>/contents/design/<file>.go \ | Surveys | `lfx-v2-survey-service` | | Members | `lfx-v2-member-service` | -**If the upstream Go repo exists locally** (check `~/lf/lfx-v2-*-service`), read the files directly instead of using `gh api`. Local reads are faster and more reliable. +**Dev root setup:** + +Before checking local repos, resolve the LFX dev root: + +```bash +if [ -f "$HOME/.lfx-skills/dev-root" ]; then + LFX_DEV_ROOT="$(cat "$HOME/.lfx-skills/dev-root")" +else + LFX_DEV_ROOT="$HOME/lf" +fi +printf '%s\n' "$LFX_DEV_ROOT" +``` + +If `~/.lfx-skills/dev-root` already exists, use it without asking and continue. Do not list or scan repos during setup. + +If `~/.lfx-skills/dev-root` is missing, ask a quick question: whether the user wants to set an LFX dev root now or use the default `~/lf`. Explain that setting it lets research read local LFX repos directly, which is faster and more reliable than falling back to `gh api`. Suggest likely locations: `~/lf`, `~/lfx`, `~/src/lfx`, or the parent directory containing their LFX clones. + +If they accept, ask for the path, then write it: + +```bash +mkdir -p ~/.lfx-skills +printf '%s\n' "<chosen-dev-root>" > ~/.lfx-skills/dev-root +``` + +If they choose the default, continue with `~/lf` and use GitHub fallbacks when local repos are unavailable. + +**If the upstream Go repo exists locally**, read the files directly instead of using `gh api`. Local reads are faster and more reliable. Resolve the path at the start of your bash commands by reading the dev-root file written by `lfx-skills install`: + +```bash +LFX_DEV_ROOT="${LFX_DEV_ROOT:-$(cat ~/.lfx-skills/dev-root 2>/dev/null || echo "$HOME/lf")}" +ls -d "$LFX_DEV_ROOT"/lfx-v2-*-service 2>/dev/null # check what's local +``` **Report:** - Endpoint exists? (path, method, status codes) @@ -247,4 +278,4 @@ This keeps the user informed that exploration is happening and what's being chec - **Be specific** — include file paths, method names, field names - **Flag blockers** — if an upstream API doesn't exist, say so clearly - **Include example content** — read and include the key sections of example files -- **Prefer local reads** — if a Go repo exists at `~/lf/`, read it directly instead of using `gh api` +- **Prefer local reads** — if a Go repo exists at `$LFX_DEV_ROOT` (resolved via `cat ~/.lfx-skills/dev-root` at the start of your bash commands), read it directly instead of using `gh api` diff --git a/lfx-setup/SKILL.md b/skills/lfx-setup/SKILL.md similarity index 98% rename from lfx-setup/SKILL.md rename to skills/lfx-setup/SKILL.md index 34d9838..d5bf381 100644 --- a/lfx-setup/SKILL.md +++ b/skills/lfx-setup/SKILL.md @@ -1,4 +1,6 @@ --- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT name: lfx-setup description: > Environment setup for any LFX repo — prerequisites, clone, install, env vars, @@ -7,8 +9,6 @@ description: > allowed-tools: Bash, Read, Glob, Grep, AskUserQuestion --- -<!-- Copyright The Linux Foundation and each contributor to LFX. --> -<!-- SPDX-License-Identifier: MIT --> <!-- Tool names in this file use Claude Code vocabulary. See docs/tool-mapping.md for other platforms. --> # LFX Environment Setup Guide diff --git a/lfx-snowflake-access/SKILL.md b/skills/lfx-snowflake-access/SKILL.md similarity index 99% rename from lfx-snowflake-access/SKILL.md rename to skills/lfx-snowflake-access/SKILL.md index 1962272..1b8fd96 100644 --- a/lfx-snowflake-access/SKILL.md +++ b/skills/lfx-snowflake-access/SKILL.md @@ -1,4 +1,6 @@ --- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT name: lfx-snowflake-access description: > Guide users through requesting Snowflake access at the Linux Foundation. Handles two request @@ -13,8 +15,6 @@ description: > allowed-tools: Bash, Read, Glob, Grep, AskUserQuestion, WebFetch --- -<!-- Copyright The Linux Foundation and each contributor to LFX. --> -<!-- SPDX-License-Identifier: MIT --> <!-- Tool names in this file use Claude Code vocabulary. See docs/tool-mapping.md for other platforms. --> # Snowflake Access Request Guide diff --git a/lfx-test-journey/SKILL.md b/skills/lfx-test-journey/SKILL.md similarity index 91% rename from lfx-test-journey/SKILL.md rename to skills/lfx-test-journey/SKILL.md index f34efa5..2c4c813 100644 --- a/lfx-test-journey/SKILL.md +++ b/skills/lfx-test-journey/SKILL.md @@ -1,4 +1,6 @@ --- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT name: lfx-test-journey description: > Combine multiple feature branches across repos into worktrees for @@ -7,8 +9,6 @@ description: > allowed-tools: Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion --- -<!-- Copyright The Linux Foundation and each contributor to LFX. --> -<!-- SPDX-License-Identifier: MIT --> <!-- Tool names in this file use Claude Code vocabulary. See docs/tool-mapping.md for other platforms. --> # Journey Testing — Multi-Branch Integration Worktrees @@ -62,27 +62,59 @@ If a subcommand requires a journey name and the user didn't provide one, run **L ## Create Journey +### Dev Root + +Resolve the LFX dev root: + +```bash +if [ -f "$HOME/.lfx-skills/dev-root" ]; then + LFX_DEV_ROOT="$(cat "$HOME/.lfx-skills/dev-root")" +else + LFX_DEV_ROOT="$HOME/lf" +fi +printf '%s\n' "$LFX_DEV_ROOT" +``` + +If `~/.lfx-skills/dev-root` already exists, use it without asking and continue. Do not list or scan repos during setup. + +If `~/.lfx-skills/dev-root` is missing, ask a quick question: whether the user wants to set an LFX dev root now or use the default `~/lf`. Explain that journey testing needs a parent directory containing local LFX repos so it can create worktrees from their branches. Suggest likely locations: `~/lf`, `~/lfx`, `~/src/lfx`, or the parent directory containing their LFX clones. + +If they accept, ask for the path, then write it: + +```bash +mkdir -p ~/.lfx-skills +printf '%s\n' "<chosen-dev-root>" > ~/.lfx-skills/dev-root +``` + +If they choose the default, continue with `~/lf`. If no repos are found there, stop and tell them journey testing needs local LFX repo clones. + ### Step 1: Discover Repos -Scan `~/lf/` for git repositories: +Scan the LFX dev root for git repositories. The first line resolves +`LFX_DEV_ROOT` from a one-line text file written by `lfx-skills install` in `~/.lfx-skills/dev-root`, +falling back to `~/lf` when not installed. No shell rc / env var required. ```bash -for dir in ~/lf/*/; do +LFX_DEV_ROOT="${LFX_DEV_ROOT:-$(cat ~/.lfx-skills/dev-root 2>/dev/null || echo "$HOME/lf")}" + +for dir in "$LFX_DEV_ROOT"/*/; do if [ -d "$dir/.git" ]; then echo "$dir" fi done ``` -Present as a numbered list and **STOP — use `AskUserQuestion` and wait for the user to respond before continuing**: +Present as a numbered list (substitute the resolved `$LFX_DEV_ROOT` value +in the displayed paths) and **STOP — use `AskUserQuestion` and wait for the +user to respond before continuing**: ``` -Scanning ~/lf/ for git repos... +Scanning $LFX_DEV_ROOT for git repos... Which repos are part of this journey? (type numbers, e.g. "1, 3") - 1. ~/lf/lfx-v2-ui - 2. ~/lf/lfx-v2-committee-service - 3. ~/lf/lfx-v2-meeting-service + 1. $LFX_DEV_ROOT/lfx-v2-ui + 2. $LFX_DEV_ROOT/lfx-v2-committee-service + 3. $LFX_DEV_ROOT/lfx-v2-meeting-service ``` **⛔ GATE: You MUST call `AskUserQuestion` here and wait for the user's response. Do NOT continue to Step 2 until the user has selected repos.** Parse their response (comma-separated numbers or repo names). diff --git a/lfx-ui-builder/SKILL.md b/skills/lfx-ui-builder/SKILL.md similarity index 98% rename from lfx-ui-builder/SKILL.md rename to skills/lfx-ui-builder/SKILL.md index eb6c26a..d3d7df8 100644 --- a/lfx-ui-builder/SKILL.md +++ b/skills/lfx-ui-builder/SKILL.md @@ -1,4 +1,6 @@ --- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT name: lfx-ui-builder description: > Generate compliant Angular 20 frontend code — components, services, templates, @@ -7,8 +9,6 @@ description: > allowed-tools: Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion --- -<!-- Copyright The Linux Foundation and each contributor to LFX. --> -<!-- SPDX-License-Identifier: MIT --> <!-- Tool names in this file use Claude Code vocabulary. See docs/tool-mapping.md for other platforms. --> # LFX Frontend Code Generation @@ -78,8 +78,6 @@ Every new `.ts`, `.html`, and `.scss` file MUST start with the appropriate licen **HTML (`.html`):** ```html -<!-- Copyright The Linux Foundation and each contributor to LFX. --> -<!-- SPDX-License-Identifier: MIT --> ``` **SCSS (`.scss`):** diff --git a/lfx-ui-builder/references/frontend-component.md b/skills/lfx-ui-builder/references/frontend-component.md similarity index 100% rename from lfx-ui-builder/references/frontend-component.md rename to skills/lfx-ui-builder/references/frontend-component.md diff --git a/lfx-ui-builder/references/frontend-service.md b/skills/lfx-ui-builder/references/frontend-service.md similarity index 100% rename from lfx-ui-builder/references/frontend-service.md rename to skills/lfx-ui-builder/references/frontend-service.md diff --git a/lfx/SKILL.md b/skills/lfx/SKILL.md similarity index 73% rename from lfx/SKILL.md rename to skills/lfx/SKILL.md index 37f3e9f..376d087 100644 --- a/lfx/SKILL.md +++ b/skills/lfx/SKILL.md @@ -1,4 +1,6 @@ --- +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT name: lfx description: > Starting point for LFX development. Describe what you want in plain language @@ -6,8 +8,6 @@ description: > allowed-tools: Bash, Read, Glob, Grep, AskUserQuestion, Skill --- -<!-- Copyright The Linux Foundation and each contributor to LFX. --> -<!-- SPDX-License-Identifier: MIT --> <!-- Tool names in this file use Claude Code vocabulary. See docs/tool-mapping.md for other platforms. --> # LFX — Your Starting Point @@ -75,8 +75,14 @@ Listen to what the user says and classify their intent. **Do not ask technical q | "What APIs ...", "Does ... exist?", "Find ...", "Research ..." | To explore and research | `/lfx-research` | | "Check my changes", "Ready for PR?", "Validate ...", "Preflight" | To validate before PR | `/lfx-preflight` | | "Address PR comments", "Fix review feedback", "Resolve PR threads", "Handle PR comments" | To address PR review feedback | `/lfx-pr-resolve` | +| "Show me my PRs", "Morning catch-up", "PR status overview", "Which PRs need attention", "What's stale" | To see open PR status across repos | `/lfx-pr-catchup` | | "Set up", "Install", "Environment", "Getting started" | Environment setup | `/lfx-setup` | | "Test a journey", "Combine branches", "Integration test", "Test across branches", "Multi-branch test" | To test across branches | `/lfx-test-journey` | +| "Set up Git signing", "Configure DCO", "GPG keys for commits", "Why aren't my commits Verified", "git commit -s" | Git signing / DCO setup | `/lfx-git-setup` | +| "Fix Intercom", "Audit Intercom", "Add Intercom integration", "JWT setup for Intercom", "Intercom CSP" | Intercom integration in an LFX Angular app | `/lfx-intercom` | +| "Add a Snowflake connector", "Scaffold a CDP source", "New crowd.dev data source", "snowflake-connector platform" | Scaffold a new CDP Snowflake connector | `/lfx-cdp-snowflake-connectors` | +| "I need Snowflake access", "Add me to Snowflake", "Need a service account", "Request Snowflake permissions" | Request Snowflake access via Terraform PR | `/lfx-snowflake-access` | +| "Can we add a new skill?", "How do I create a skill?", "We need another skill", "This workflow should be a skill" | Skill authoring guidance | See "Need Another Skill?" below | | "Show me an example", "How do I use this?", "Help" | Guidance | Show quickstart examples | ## Step 3: Translate and Route @@ -157,6 +163,49 @@ Skill(skill: "lfx-test-journey", args: "status") Skill(skill: "lfx-test-journey", args: "refresh committee-onboarding") ``` +### Routing to `/lfx-pr-catchup` + +Pass an org filter or stale-days override if the user mentioned one, otherwise invoke with no args: + +``` +Skill(skill: "lfx-pr-catchup") +Skill(skill: "lfx-pr-catchup", args: "linuxfoundation") +Skill(skill: "lfx-pr-catchup", args: "stale=14") +``` + +### Routing to `/lfx-git-setup` + +No translation needed — invoke directly: + +``` +Skill(skill: "lfx-git-setup") +``` + +### Routing to `/lfx-intercom` + +No translation needed — the skill audits and fixes the Intercom integration in the current Angular repo: + +``` +Skill(skill: "lfx-intercom") +``` + +### Routing to `/lfx-cdp-snowflake-connectors` + +The skill scaffolds one connector per run. Pass the platform/source name if the user mentioned it: + +``` +Skill(skill: "lfx-cdp-snowflake-connectors") +Skill(skill: "lfx-cdp-snowflake-connectors", args: "platform: discourse, source: discourse_posts") +``` + +### Routing to `/lfx-snowflake-access` + +No translation needed — the skill collects user details and generates the Terraform PR for the lfx-snowflake-terraform repo: + +``` +Skill(skill: "lfx-snowflake-access") +``` + ### Showing Examples When the user asks for examples or help, read and present the quickstart guide: @@ -205,6 +254,18 @@ Once the delegated skill completes, check back with the user: - If they validated → "Everything looks good! Want me to help create the pull request?" - If they addressed PR feedback → "Review comments are addressed and pushed! Run /lfx-pr-catchup to monitor for any follow-up." +## Need Another Skill? + +If the user asks how to add a new skill, says this workflow needs its own skill, or wonders whether another skill should exist, explain that LFX Skills has a dedicated authoring workflow in the source repository. + +Give concise guidance: + +```text +To add a new LFX skill, clone https://github.com/linuxfoundation/lfx-skills, start your coding agent in that repo, and ask it to help create the skill. The repo includes /lfx-new-skill, which can scaffold from scratch or adapt an existing SKILL.md, apply the repo conventions, validate formatting, and explain how to publish the skill through agents.md and the Claude Code plugin. +``` + +Do not try to author the new skill from a product repo. The canonical authoring flow lives in `linuxfoundation/lfx-skills`. + ## Scope Boundaries **This skill DOES:** diff --git a/lfx/references/glossary.md b/skills/lfx/references/glossary.md similarity index 100% rename from lfx/references/glossary.md rename to skills/lfx/references/glossary.md diff --git a/lfx/references/quickstart.md b/skills/lfx/references/quickstart.md similarity index 100% rename from lfx/references/quickstart.md rename to skills/lfx/references/quickstart.md