From cabe00c182b174ee476dbd09e890d92707c52eb6 Mon Sep 17 00:00:00 2001 From: posidoni <89808617+posidoni@users.noreply.github.com> Date: Sat, 18 Jul 2026 14:17:10 +0400 Subject: [PATCH 1/9] feat: prepare shell skill kit ai release --- .agents/README.md | 15 +++ .agents/skills/bash | 1 + .agents/skills/nushell | 1 + .agents/skills/posix-sh | 1 + .agents/skills/shebang | 1 + .agents/skills/shell-standards | 1 + .agents/skills/streams | 1 + .agents/skills/zsh | 1 + .claude-plugin/marketplace.json | 6 +- .claude-plugin/plugin.json | 10 +- .codex-plugin/plugin.json | 53 ++++++++ .codex/config.toml | 5 + .github/ISSUE_TEMPLATE/config.yml | 1 + .github/copilot-instructions.md | 2 + .github/dependabot.yml | 1 + .github/workflows/ci.yml | 64 +-------- .github/workflows/release.yml | 27 ---- .gitignore | 8 ++ .serena/.gitignore | 3 + .serena/memories/conventions.md | 11 ++ .serena/memories/core.md | 9 ++ .serena/memories/memory_maintenance.md | 33 +++++ .serena/memories/suggested_commands.md | 12 ++ .serena/memories/task_completion.md | 9 ++ .serena/memories/tech_stack.md | 10 ++ .serena/project.yml | 41 ++++++ AGENTS.md | 12 +- CHANGELOG.md | 31 ++++- CHATGPT.md | 33 +++++ CITATION.cff | 13 +- CLAUDE.md | 5 +- CONTRIBUTING.md | 21 ++- README.md | 69 +++++++--- Taskfile.yml | 13 ++ lefthook.yml | 12 +- llms.txt | 40 ++++++ registry/agenticskills-submission.md | 26 ++++ registry/awesome-codex-plugins-listing.md | 17 +++ schemas/codex-plugin.schema.json | 135 +++++++++++++++++++ schemas/openai-skill-metadata.schema.json | 52 ++++++++ schemas/serena-project.schema.json | 111 ++++++++++++++++ skills/bash/agents/openai.yaml | 8 ++ skills/nushell/agents/openai.yaml | 8 ++ skills/posix-sh/agents/openai.yaml | 8 ++ skills/shebang/agents/openai.yaml | 8 ++ skills/shell-standards/agents/openai.yaml | 8 ++ skills/streams/SKILL.md | 6 +- skills/streams/agents/openai.yaml | 8 ++ skills/zsh/agents/openai.yaml | 8 ++ tools/check-ai-integrations.sh | 150 ++++++++++++++++++++++ tools/check-yaml-schemas.sh | 18 +++ 51 files changed, 1017 insertions(+), 130 deletions(-) create mode 100644 .agents/README.md create mode 120000 .agents/skills/bash create mode 120000 .agents/skills/nushell create mode 120000 .agents/skills/posix-sh create mode 120000 .agents/skills/shebang create mode 120000 .agents/skills/shell-standards create mode 120000 .agents/skills/streams create mode 120000 .agents/skills/zsh create mode 100644 .codex-plugin/plugin.json create mode 100644 .codex/config.toml delete mode 100644 .github/workflows/release.yml create mode 100644 .serena/.gitignore create mode 100644 .serena/memories/conventions.md create mode 100644 .serena/memories/core.md create mode 100644 .serena/memories/memory_maintenance.md create mode 100644 .serena/memories/suggested_commands.md create mode 100644 .serena/memories/task_completion.md create mode 100644 .serena/memories/tech_stack.md create mode 100644 .serena/project.yml create mode 100644 CHATGPT.md create mode 100644 llms.txt create mode 100644 registry/agenticskills-submission.md create mode 100644 registry/awesome-codex-plugins-listing.md create mode 100644 schemas/codex-plugin.schema.json create mode 100644 schemas/openai-skill-metadata.schema.json create mode 100644 schemas/serena-project.schema.json create mode 100644 skills/bash/agents/openai.yaml create mode 100644 skills/nushell/agents/openai.yaml create mode 100644 skills/posix-sh/agents/openai.yaml create mode 100644 skills/shebang/agents/openai.yaml create mode 100644 skills/shell-standards/agents/openai.yaml create mode 100644 skills/streams/agents/openai.yaml create mode 100644 skills/zsh/agents/openai.yaml create mode 100755 tools/check-ai-integrations.sh create mode 100755 tools/check-yaml-schemas.sh diff --git a/.agents/README.md b/.agents/README.md new file mode 100644 index 0000000..d332300 --- /dev/null +++ b/.agents/README.md @@ -0,0 +1,15 @@ +# Codex Repo Skills + +This directory exposes the repository's portable `skills/` folders as +repo-scoped Codex skills. + +OpenAI's Codex skill discovery scans `$REPO_ROOT/.agents/skills`. The real +skill sources remain in `../skills`; entries here are symlinks so Claude Code, +Codex, and generic Agent Skills clients share the same `SKILL.md` files. + +When adding a skill: + +1. Create `skills//SKILL.md`. +2. Create `.agents/skills/ -> ../../skills/`. +3. Add `skills//agents/openai.yaml`. +4. Run `task ai-integrations`. diff --git a/.agents/skills/bash b/.agents/skills/bash new file mode 120000 index 0000000..8959685 --- /dev/null +++ b/.agents/skills/bash @@ -0,0 +1 @@ +../../skills/bash \ No newline at end of file diff --git a/.agents/skills/nushell b/.agents/skills/nushell new file mode 120000 index 0000000..65b9f11 --- /dev/null +++ b/.agents/skills/nushell @@ -0,0 +1 @@ +../../skills/nushell \ No newline at end of file diff --git a/.agents/skills/posix-sh b/.agents/skills/posix-sh new file mode 120000 index 0000000..fa2ec24 --- /dev/null +++ b/.agents/skills/posix-sh @@ -0,0 +1 @@ +../../skills/posix-sh \ No newline at end of file diff --git a/.agents/skills/shebang b/.agents/skills/shebang new file mode 120000 index 0000000..67fc0ce --- /dev/null +++ b/.agents/skills/shebang @@ -0,0 +1 @@ +../../skills/shebang \ No newline at end of file diff --git a/.agents/skills/shell-standards b/.agents/skills/shell-standards new file mode 120000 index 0000000..9accb86 --- /dev/null +++ b/.agents/skills/shell-standards @@ -0,0 +1 @@ +../../skills/shell-standards \ No newline at end of file diff --git a/.agents/skills/streams b/.agents/skills/streams new file mode 120000 index 0000000..003d646 --- /dev/null +++ b/.agents/skills/streams @@ -0,0 +1 @@ +../../skills/streams \ No newline at end of file diff --git a/.agents/skills/zsh b/.agents/skills/zsh new file mode 120000 index 0000000..5838056 --- /dev/null +++ b/.agents/skills/zsh @@ -0,0 +1 @@ +../../skills/zsh \ No newline at end of file diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 27c72fd..0ab0844 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -1,6 +1,6 @@ { "name": "shell-skill", - "description": "Skills for writing safe, correct shell — Bash, Zsh, POSIX sh, and Nushell — for humans and AI coding agents.", + "description": "Shell Skill Kit: test-enforced skills for safe Bash, Zsh, POSIX sh, and Nushell.", "owner": { "name": "Mikhail Kuznetsov (@posidoni)" }, @@ -8,9 +8,9 @@ { "name": "shell-skill", "source": "./", - "description": "Skills for writing safe, correct shell — Bash, Zsh, POSIX sh, and Nushell.", + "description": "Shell Skill Kit: safe shell rules for AI coding agents.", "category": "development", - "version": "0.1.0" + "version": "0.2.0" } ] } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 9720a83..0002f09 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "shell-skill", - "version": "0.1.0", - "description": "Skills for writing safe, correct shell — Bash, Zsh, POSIX sh, and Nushell.", + "version": "0.2.0", + "description": "Shell Skill Kit: test-enforced skills for safe Bash, Zsh, POSIX sh, and Nushell.", "author": { "name": "Mikhail Kuznetsov", "url": "https://github.com/posidoni" @@ -18,6 +18,10 @@ "shellcheck", "shfmt", "safety", - "agent-skills" + "agent-skills", + "codex", + "chatgpt", + "claude-code", + "shell-safety" ] } diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json new file mode 100644 index 0000000..9b02f5a --- /dev/null +++ b/.codex-plugin/plugin.json @@ -0,0 +1,53 @@ +{ + "name": "shell-skill", + "version": "0.2.0", + "description": "Shell Skill Kit: test-enforced Agent Skills for safe Bash, Zsh, POSIX sh, and Nushell.", + "author": { + "name": "Mikhail Kuznetsov", + "url": "https://github.com/posidoni" + }, + "repository": "https://github.com/posidoni/shell-skill", + "homepage": "https://github.com/posidoni/shell-skill#readme", + "license": "MIT", + "keywords": [ + "shell", + "bash", + "zsh", + "nushell", + "posix", + "shellcheck", + "shfmt", + "agent-skills", + "codex", + "chatgpt", + "claude-code", + "cursor", + "ai-coding", + "shell-safety" + ], + "skills": "./skills/", + "interface": { + "displayName": "Shell Skill Kit", + "shortDescription": "Safe shell rules for AI coding agents", + "longDescription": "A portable, cited, test-enforced skill kit for writing and reviewing shell. Covers Bash, Zsh, POSIX sh, Nushell, shebangs, streams, and the failure modes that make generated scripts quietly unsafe.", + "developerName": "Mikhail Kuznetsov", + "category": "Development", + "capabilities": [ + "Skills", + "Review", + "Linting" + ], + "websiteURL": "https://github.com/posidoni/shell-skill", + "brandColor": "#2563EB", + "composerIcon": "./assets/logo.svg", + "logo": "./assets/logo.svg", + "screenshots": [ + "./assets/social-preview.png" + ], + "defaultPrompt": [ + "Use Shell Skill Kit to review this shell script.", + "Write a portable Bash script with safe defaults.", + "Explain why this shell example is unsafe." + ] + } +} diff --git a/.codex/config.toml b/.codex/config.toml new file mode 100644 index 0000000..49d91e0 --- /dev/null +++ b/.codex/config.toml @@ -0,0 +1,5 @@ +# Repo-scoped Codex defaults. +# Loaded only for trusted checkouts; user security policy remains local. + +[features] +multi_agent = true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index a31f257..74b493f 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-issue-config.json blank_issues_enabled: false contact_links: - name: Security report diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index f6cb55f..46dea3d 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -24,6 +24,8 @@ under [`skills/`](../skills/)). `shellcheck --severity=warning` and `shfmt`. - `*.bad.sh` must be safe to run and carry an `# expect-shellcheck:` directive. - Nushell `*.nu` must pass `nu --ide-check`. +- Tracked YAML-like files (`*.yml`, `*.yaml`, `*.cff`) must start with a + `yaml-language-server` JSON Schema modeline. ## Verification diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1f8138f..919ff37 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://json.schemastore.org/dependabot-2.0.json # Keep pinned GitHub Actions up to date. https://docs.github.com/code-security/dependabot version: 2 updates: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dca4dbb..e8b639c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json name: CI on: @@ -14,66 +15,15 @@ concurrency: group: ci-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -env: - SHFMT_VERSION: v3.13.1 - NU_VERSION: "0.114.1" - TASK_VERSION: "v3.52.0" - LEFTHOOK_VERSION: "v2.1.10" - jobs: - shell: - name: shell (shellcheck + shfmt + bats) - runs-on: ubuntu-24.04 - steps: - # Third-party and first-party actions are pinned to a full commit SHA - # (with a human-readable version comment) per GitHub's security hardening - # guidance: https://docs.github.com/actions/security-guides/security-hardening-for-github-actions - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - name: Install toolchain - run: ./tools/ci-install-linux.sh - - name: Format check (shfmt) - run: task fmt-check - - name: Lint (shellcheck) - run: task lint - - name: Example contract (bad examples) - run: task examples - - name: Behavioural tests (bats) - run: task test - - shell-macos: - name: shell-macos (shellcheck + shfmt + bats on macOS) - runs-on: macos-14 - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - name: Install toolchain (Homebrew) - run: ./tools/ci-install-macos.sh - - name: Format check (shfmt) - run: task fmt-check - - name: Lint (shellcheck) - run: task lint - - name: Example contract (bad examples) - run: task examples - - name: Behavioural tests (bats) - run: task test - - nushell: - name: nushell (nu --ide-check + startup demo) - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - name: Install toolchain - run: ./tools/ci-install-linux.sh - - name: Static check (nu --ide-check) - run: task nushell - - name: Startup-order demo (parse-time source bug + fix) - run: task nushell-demo - - lefthook: - name: lefthook (mirrors CI hooks) + quality: + name: quality gate runs-on: ubuntu-24.04 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Install toolchain run: ./tools/ci-install-linux.sh - - name: Run hooks over all files - run: lefthook run pre-commit --all-files + - name: Run full local gate + run: task ci + - name: Run hook mirror + run: task hooks diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index ea2187d..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Release - -# Cut a GitHub release automatically when a v* tag is pushed. -# Tag a release with: git tag -a vX.Y.Z -m "..." && git push origin vX.Y.Z -on: - push: - tags: ["v*"] - -# Least-privilege: only the permission needed to create a release. -permissions: - contents: write - -jobs: - release: - name: create GitHub release - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - name: Create release from the tag - env: - GH_TOKEN: ${{ github.token }} - TAG: ${{ github.ref_name }} - run: | - gh release create "$TAG" \ - --title "shell-skill ${TAG#v}" \ - --generate-notes \ - --verify-tag diff --git a/.gitignore b/.gitignore index 8093bfc..f09a79d 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,11 @@ id_ed25519 .claude/settings.local.json .claude/ralph-loop.local.md .ralph/ + +# Serena project integration. Keep portable project config and curated memories +# tracked; keep caches, logs, and developer-local overrides private. +.serena/* +!.serena/.gitignore +!.serena/project.yml +!.serena/memories/ +!.serena/memories/*.md diff --git a/.serena/.gitignore b/.serena/.gitignore new file mode 100644 index 0000000..02af10f --- /dev/null +++ b/.serena/.gitignore @@ -0,0 +1,3 @@ +/cache +/logs +/project.local.yml diff --git a/.serena/memories/conventions.md b/.serena/memories/conventions.md new file mode 100644 index 0000000..3adb239 --- /dev/null +++ b/.serena/memories/conventions.md @@ -0,0 +1,11 @@ +# Conventions + +- Keep public branding as Shell Skill Kit; keep technical id/package/repo name `shell-skill`. +- Skill frontmatter stays minimal: only `name` and `description` unless intentionally adopting a broader Agent Skills spec field. Descriptions carry trigger logic; bodies carry procedure. +- Keep `SKILL.md` concise and operational; put detailed rationale/citations in `reference/`; avoid duplicate long explanations in skills. +- Example contract: `*.good.sh` self-contained/no args/exits 0/clean under ShellCheck + shfmt; `*.bad.sh` safe-to-run with exactly one `# expect-shellcheck:` directive; Nushell examples parse cleanly with `nu --ide-check`. +- Shell examples must be safe and portable; use `mktemp` + `trap` for filesystem work; no secrets, personal data, or machine-specific paths. +- Tracked YAML-like files (`*.yml`, `*.yaml`, `*.cff`) must start with `# yaml-language-server: $schema=...` and pass `task yaml-schemas`. +- Keep Codex repo-scope skill discovery as symlinks in `.agents/skills/* -> ../../skills/*`; never duplicate skill bodies there. +- Keep Serena portable config/memories tracked, but leave `.serena/cache`, `.serena/logs`, and `.serena/project.local.yml` ignored. Serena LSPs are limited to Bash, Markdown, and JSON because current YAML/TOML wrappers pass health-check but log client-configuration errors. +- Keep GitHub automation lean: one hosted Ubuntu quality gate running `task ci` and `task hooks`; do not add scanner/release/macOS workflows unless the maintainer explicitly opts in. diff --git a/.serena/memories/core.md b/.serena/memories/core.md new file mode 100644 index 0000000..1cd4de1 --- /dev/null +++ b/.serena/memories/core.md @@ -0,0 +1,9 @@ +# Core + +- Public product name: Shell Skill Kit; package/repo/plugin id remains `shell-skill`. +- Purpose: cited, test-enforced Agent Skills for safe shell generation/review across Bash, Zsh, POSIX `sh`, Nushell, shebangs, and streams. +- Source map: `skills//SKILL.md` are portable skill entrypoints; `reference/*.md` holds cited depth; `examples//` holds runnable good/bad pairs; `tools/` + `tests/` enforce the contract. +- Agent packaging: Claude Code manifest in `.claude-plugin/plugin.json`; Codex manifest in `.codex-plugin/plugin.json`; repo-scope Codex discovery in `.agents/skills`; trusted Codex defaults in `.codex/config.toml`; ChatGPT/Codex notes in `CHATGPT.md`; crawler index in `llms.txt`; OpenAI UI metadata in `skills/*/agents/openai.yaml`. +- Registry prep: `registry/agenticskills-submission.md` and `registry/awesome-codex-plugins-listing.md` are copy-ready drafts; live submission still needs maintainer email / opt-in scanner gate. +- Integration gate: `task ai-integrations` validates Codex, ChatGPT, Serena, plugin, and repo-scope skill discovery surfaces. +- Read `mem:tech_stack` for tools and pins, `mem:conventions` for editing rules, and `mem:task_completion` for the done gate. diff --git a/.serena/memories/memory_maintenance.md b/.serena/memories/memory_maintenance.md new file mode 100644 index 0000000..64fac79 --- /dev/null +++ b/.serena/memories/memory_maintenance.md @@ -0,0 +1,33 @@ +# Memory Maintenance + +## Discovery Model + +- Core principle: progressive discovery through references, building a graph of memories. +- Initially, agents are provided with the list of all memories (names only). +- Agents should read `mem:core` as the top-level entry point (graph root). + This memory should contain references to other memories covering major project domains. + The referenced memories shall, in turn, shall contain references to even more specific memories, and so on. + The depth of the graph shall depend on the project complexity. +- Use topics/folders to group related memories in order to make the content structure explicit. + Folders can mirror project structure (e.g. modules like frontend/backend) or topics like debugging, architecture, etc. +- Memory references must use a mem: prefix inside backticks, e.g. `mem:frontend/core`. + The surrounding text should clearly indicate when to read the memory/which content to expect. + The text should provide more precise guidance than the memory name alone, + i.e. avoid a reference like "frontend debugging: `mem:frontend/debugging` and instead make clear which aspects of frontend debugging are covered. +- Memories themselves should not contain information about when to read them; this is the responsibility of the referring memory. + +## Style + +Dense agent notes, not prose docs. Prefer invariants, terse bullets. +Avoid obvious context, rationale, and examples unless they prevent likely mistakes. +Keep guidance durable and generalizable, not task-local. + +## Add/update threshold + +Add or update memories only with stable, non-obvious project conventions that avoid complex rediscovery in the future. +Do not add: quick-read facts; generic language/framework knowledge; one-off task notes; volatile line-level details; behavior likely to change soon. + +## Maintenance Actions + +- Renaming memories: References are updated automatically if handled via Serena's memory rename tool. +- Checking for stale memories (e.g. after deletion): Call `serena memories check` for a report. diff --git a/.serena/memories/suggested_commands.md b/.serena/memories/suggested_commands.md new file mode 100644 index 0000000..f06ced5 --- /dev/null +++ b/.serena/memories/suggested_commands.md @@ -0,0 +1,12 @@ +# Suggested Commands + +- Discover commands: `task --list`. +- Format shell scripts: `task fmt`. +- Full local/CI quality gate: `task ci`. +- Pre-commit mirror across repo: `task hooks`. +- YAML schema modeline gate: `task yaml-schemas`. +- AI integration metadata gate: `task ai-integrations`. +- Individual gates: `task fmt-check`, `task lint`, `task examples`, `task nushell`, `task nushell-demo`, `task test`. +- Validate Codex plugin manifest after editing `.codex-plugin/plugin.json`: run the active Codex `plugin-creator` validator through `uv run --with PyYAML python .` when available. +- Validate skill frontmatter after editing `skills/*/SKILL.md`: run the active Codex `skill-creator` validator through `uv run --with PyYAML python skills/` when available. +- Darwin note: local shell tool paths are Homebrew-friendly; avoid GNU-only assumptions in examples unless explicitly guarded. diff --git a/.serena/memories/task_completion.md b/.serena/memories/task_completion.md new file mode 100644 index 0000000..0da992b --- /dev/null +++ b/.serena/memories/task_completion.md @@ -0,0 +1,9 @@ +# Task Completion + +- Before handing back any coding/release change, run `task ci` from repo root. +- Before committing/opening PRs, also run `task hooks`. +- For AI metadata changes, run `task ai-integrations`. +- For plugin packaging changes, run the active Codex `plugin-creator` validator through `uv run --with PyYAML python .` when available. +- For skill frontmatter/body changes, run the active Codex `skill-creator` validator through `uv run --with PyYAML python ` when available. +- If tests fail, fix the repo; do not weaken the good/bad example contract or remove the schema gate to get green. +- After Serena onboarding/memory edits, the user can sanity-check references with `serena memories check` from repo root. diff --git a/.serena/memories/tech_stack.md b/.serena/memories/tech_stack.md new file mode 100644 index 0000000..8dacadb --- /dev/null +++ b/.serena/memories/tech_stack.md @@ -0,0 +1,10 @@ +# Tech Stack + +- Primary content: Markdown skill/docs, shell examples (`*.sh`), Nushell examples (`*.nu`), JSON plugin manifests, YAML config. +- Task runner: Taskfile v3 (`Taskfile.yml`), local command entrypoint is `task`. +- Shell quality: ShellCheck, shfmt, bats-core; `.shellcheckrc` enables all optional checks, CI lint uses warning severity. +- Nushell quality: `nu --ide-check` for `*.nu`; runtime startup-order pitfall is tested through `tests/nushell-startup-demo.sh`. +- Serena LSPs: Bash, Markdown, and JSON only; YAML/TOML are intentionally validated by repository gates rather than Serena LSPs until the wrappers stop logging client-configuration errors. +- Hook runner: Lefthook (`lefthook.yml`) mirrors local pre-commit checks; hosted CI runs one lean Ubuntu job with `task ci` plus `task hooks`. +- Pinned CI installer: `tools/ci-install-linux.sh` owns Linux shfmt/nu/task/lefthook pins; `tools/ci-install-macos.sh` is kept for local Darwin parity, not hosted CI. +- Project-owned schemas: `schemas/openai-skill-metadata.schema.json`, `schemas/codex-plugin.schema.json`, and `schemas/serena-project.schema.json`; external schema modelines cover Taskfile, Lefthook, GitHub Actions, Dependabot, GitHub issue config, and CFF. diff --git a/.serena/project.yml b/.serena/project.yml new file mode 100644 index 0000000..63ecc87 --- /dev/null +++ b/.serena/project.yml @@ -0,0 +1,41 @@ +# yaml-language-server: $schema=../schemas/serena-project.schema.json +project_name: "shell-skill" + +languages: +- bash +- markdown +- json + +encoding: "utf-8" + +activation_command: +activation_command_timeout: 180.0 +line_ending: +language_backend: + +ignore_all_files_in_gitignore: true +ls_specific_settings: {} +ls_workspace_folders: +- "." +ls_additional_workspace_folders: [] +ignored_paths: +- ".git/**" +- ".serena/cache/**" +- ".serena/logs/**" + +read_only: false +excluded_tools: [] +included_optional_tools: [] +fixed_tools: [] + +initial_prompt: | + Shell Skill Kit publishes cited, test-enforced shell guidance as portable + Agent Skills plus Claude Code and Codex plugin metadata. Load mem:core first, + then follow mem:conventions and mem:task_completion before handoff. YAML, + TOML, and Nushell are validated by repository gates rather than Serena LSPs. + +read_only_memory_patterns: [] +ignored_memory_patterns: [] +default_modes: +added_modes: +symbol_info_budget: diff --git a/AGENTS.md b/AGENTS.md index e10b94e..40569e0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,14 +19,18 @@ through cited rules, runnable good/bad examples, and loadable agent skills. Use `$HOME`/`~` generically. 4. **Local-only by default:** do not `git push`, change remotes, or alter repository visibility unless the human explicitly asks. +5. **YAML is schema-backed:** every tracked `*.yml`, `*.yaml`, and + `CITATION.cff` starts with a `yaml-language-server` JSON Schema modeline. ## Workflow ```sh task --list # discover every entrypoint task fmt # format shell scripts (shfmt -w) -task ci # fmt-check, lint, examples, nushell, nushell-demo, test +task ci # fmt-check, lint, examples, nushell, nushell-demo, yaml-schemas, ai-integrations, test task hooks # lefthook run pre-commit --all-files +task yaml-schemas # ensure YAML-like files declare JSON Schemas +task ai-integrations # validate Codex/ChatGPT/Serena discovery ``` ## The example contract @@ -57,3 +61,9 @@ Nushell examples are `*.nu` and must pass `nu --ide-check`. | `examples//` | runnable good/bad pairs | | `tools/`, `tests/` | verification scripts and the bats suite | | `Taskfile.yml` | task runner entrypoints | +| `.agents/skills/` | Codex repo-scope skill symlinks to `skills/` | +| `.codex/config.toml` | trusted Codex project defaults | +| `.serena/project.yml`, `.serena/memories/` | portable Serena project setup | +| `.codex-plugin/`, `.claude-plugin/` | Codex and Claude Code plugin manifests | +| `CHATGPT.md`, `llms.txt` | ChatGPT/Codex handoff and crawler-friendly index | +| `schemas/`, `registry/` | JSON Schemas and directory submission drafts | diff --git a/CHANGELOG.md b/CHANGELOG.md index 77cf314..34feffb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,34 @@ follow [Semantic Versioning](https://semver.org/). ## [Unreleased] +## [0.2.0] - 2026-07-18 + +### Added + +- Codex plugin manifest at `.codex-plugin/plugin.json`, with public UI metadata + for Shell Skill Kit. +- ChatGPT/Codex companion instructions in `CHATGPT.md`, crawler-friendly + discovery in `llms.txt`, and `agents/openai.yaml` metadata for every skill. +- Codex repo-scope skill symlinks in `.agents/skills/`, trusted Codex defaults + in `.codex/config.toml`, and portable Serena setup in `.serena/`. +- Registry submission drafts for AgenticSkills and Awesome Codex Plugins. +- JSON Schema modelines for tracked YAML-like files plus `task yaml-schemas` and + `task ai-integrations` gates that enforce the agent metadata contract. + +### Changed + +- Repositioned the project publicly as **Shell Skill Kit** while keeping the + package/repo name `shell-skill`. +- Simplified hosted GitHub Actions to one Ubuntu quality gate that runs + `task ci` plus the hook mirror; macOS verification remains documented as a + local maintainer check. +- Updated Claude plugin metadata, citation metadata, README, contributing docs, + and agent instructions for the 0.2.0 release. + +### Removed + +- Automatic GitHub release workflow; releases are now cut manually from tags. + ## [0.1.0] - 2026-07-13 Initial public release. @@ -27,5 +55,6 @@ Initial public release. - Community health files: `README`, `CONTRIBUTING`, `SECURITY`, `CODE_OF_CONDUCT`, issue/PR templates, and Dependabot. -[Unreleased]: https://github.com/posidoni/shell-skill/compare/v0.1.0...HEAD +[Unreleased]: https://github.com/posidoni/shell-skill/compare/v0.2.0...HEAD +[0.2.0]: https://github.com/posidoni/shell-skill/compare/v0.1.0...v0.2.0 [0.1.0]: https://github.com/posidoni/shell-skill/releases/tag/v0.1.0 diff --git a/CHATGPT.md b/CHATGPT.md new file mode 100644 index 0000000..3ef936c --- /dev/null +++ b/CHATGPT.md @@ -0,0 +1,33 @@ +# CHATGPT.md + +ChatGPT and Codex companion instructions for this repository. The canonical +project contract remains [AGENTS.md](AGENTS.md); read it first. + +## What to load + +- Use `.codex-plugin/plugin.json` when packaging this repo as a Codex plugin. +- Use `.agents/skills/*` for Codex repo-scope discovery in trusted checkouts; + these entries are symlinks to the canonical `skills/` folders. +- Use `.codex/config.toml` for durable project defaults. It currently enables + multi-agent work only; sandbox, approval, and credentials stay user-local. +- Use `.serena/project.yml` and `mem:core` when Serena is available. +- Use `skills/*/SKILL.md` as portable Agent Skills. The `agents/openai.yaml` + files add ChatGPT/Codex UI metadata only; they do not replace `SKILL.md`. +- Use `reference/*.md` only when a task needs the cited rationale behind a rule. + +## How to work here + +- Keep skill bodies short and operational. Put depth in `reference/`. +- Preserve the good/bad example contract from `CONTRIBUTING.md`. +- Run `task ci` before handing back release work. +- Run `task hooks` before commits or pull requests. +- Run `task ai-integrations` after changing AI metadata, repo-scoped skills, or + Serena setup. +- Keep local tool state out of the release package. + +## Release packaging + +- Update both `.claude-plugin/plugin.json` and `.codex-plugin/plugin.json` for + versioned releases. +- Keep `README.md`, `CHANGELOG.md`, `CITATION.cff`, and registry notes in sync + with the public release name: Shell Skill Kit. diff --git a/CITATION.cff b/CITATION.cff index cd59933..2ee5e84 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -1,5 +1,6 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/citation-file-format/citation-file-format/main/schema.json cff-version: 1.2.0 -title: shell-skill +title: Shell Skill Kit message: >- If you use this project, please cite it using the metadata below. type: software @@ -11,8 +12,8 @@ repository-code: "https://github.com/posidoni/shell-skill" url: "https://github.com/posidoni/shell-skill" abstract: >- A cited, test-enforced guide to writing safe, correct shell (Bash, Zsh, POSIX - sh, and Nushell) for humans and AI coding agents, packaged as Claude Code - skills. + sh, and Nushell) for humans and AI coding agents, packaged as portable Agent + Skills plus Claude Code and Codex plugin metadata. keywords: - shell - bash @@ -20,6 +21,8 @@ keywords: - nushell - shellcheck - agent-skills + - codex + - claude-code license: MIT -version: 0.1.0 -date-released: "2026-07-13" +version: 0.2.0 +date-released: "2026-07-18" diff --git a/CLAUDE.md b/CLAUDE.md index 6fd83f4..33b1ec2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,8 +11,9 @@ applies here too. skill's full body only when its description matches the task (progressive disclosure), so keep descriptions precise. - The repo is packaged as an installable Claude Code plugin; see - [`.claude-plugin/`](.claude-plugin/). You can also point Claude at `skills/` - directly. + [`.claude-plugin/`](.claude-plugin/). It also ships Codex metadata in + [`.codex-plugin/`](.codex-plugin/) and ChatGPT notes in [CHATGPT.md](CHATGPT.md). + You can also point Claude at `skills/` directly. - Use the skills here — `shell-standards`, `bash`, `zsh`, `nushell` — whenever you write, review, or debug shell in this repository. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 87f7d68..6153e8c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,8 @@ verified by the toolchain before it lands. ## Toolchain -You need six tools. They are the same ones CI uses. +You need six tools. Hosted CI installs them on Ubuntu; macOS developers can use +the Homebrew command below for the same local gate. | Tool | Purpose | |------|---------| @@ -28,10 +29,12 @@ brew install shellcheck shfmt nushell bats-core go-task lefthook [`tools/ci-install-linux.sh`](tools/ci-install-linux.sh); run it or copy the commands. -CI runs the shell suite on **both Linux and macOS** -([`tools/ci-install-macos.sh`](tools/ci-install-macos.sh)) — Bash, shfmt, and -ShellCheck genuinely behave differently across the two (see -[`reference/bash.md`](reference/bash.md)), so both are checked, not just one. +Hosted CI runs one lean Ubuntu quality gate plus the full hook mirror. macOS +portability still matters: run the Homebrew toolchain locally before touching +Darwin-sensitive examples or reference text. The helper remains in +[`tools/ci-install-macos.sh`](tools/ci-install-macos.sh) because Bash, shfmt, and +ShellCheck can differ across macOS and Linux; we just do not spend hosted macOS +minutes on every PR. Then install the git hooks once: @@ -46,8 +49,10 @@ entrypoint. The important ones: ```sh task fmt # format all shell scripts in place (shfmt -w) -task ci # everything CI runs: fmt-check, lint, examples, nushell, nushell-demo, test +task ci # everything CI runs: fmt-check, lint, examples, nushell, nushell-demo, yaml-schemas, ai-integrations, test task hooks # run every git hook across the repo (lefthook) +task yaml-schemas # verify tracked YAML-like files declare a JSON schema +task ai-integrations # verify Codex/ChatGPT/Serena/plugin discovery surfaces ``` Why Task over Make? Task is a single, statically-linked Go binary that behaves @@ -88,6 +93,10 @@ runtime good/bad behaviour in the file or its README, not via the linter. 4. Commits follow [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `docs:`, `test:`, `chore:`, `build:`, `ci:`). 5. No personal data, secrets, or machine-specific paths anywhere. +6. Every tracked YAML-like file (`*.yml`, `*.yaml`, `*.cff`) has a + `yaml-language-server` JSON Schema modeline. +7. `task ai-integrations` is green after changing skills, plugin manifests, + `.agents/`, `.codex/`, `.serena/`, `CHATGPT.md`, or `llms.txt`. ## Reporting problems diff --git a/README.md b/README.md index 8cd8125..ca31039 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ shell-skill -# shell-skill +# Shell Skill Kit -**A cited, test-enforced guide to writing safe, correct shell** — Bash, Zsh, POSIX `sh`, and Nushell — for humans and AI coding agents. +**Test-enforced shell rules for AI coding agents**: Bash, Zsh, POSIX `sh`, and Nushell, packaged as portable Agent Skills plus Claude Code and Codex plugin metadata. -[Why](#why-this-exists) · [Skills](#skills) · [Quick start](#quick-start) · [Example contract](#the-example-contract) · [Use with agents](#use-it-with-ai-coding-agents) · [Contributing](#contributing) +[Why](#why-this-exists) · [Skills](#skills) · [Quick start](#quick-start) · [Use with agents](#use-it-with-ai-coding-agents) · [Registry](#registry-and-discovery) · [Contributing](#contributing) @@ -20,11 +20,11 @@ where `[[ ]]` belongs, real errors swallowed by a stray `|| true`. It works on the happy path and fails silently everywhere else. And the usual advice ("quote your variables") is folklore — unenforced, uncited, easy to skip. -**shell-skill is the antidote.** It encodes the rules as cited references, -runnable examples that CI checks, and -[Agent Skills](https://code.claude.com/docs/en/plugins) you can load into Claude, -Codex, or Copilot — so the shell your tools (and you) produce is safe by default, -not by luck. +**Shell Skill Kit is the antidote.** It encodes the rules as cited references, +runnable examples that CI checks, and portable +[Agent Skills](https://agentskills.io/specification) you can load into Claude, +Codex, Cursor, Copilot, or any client that understands `SKILL.md` folders. The +goal is simple: generated shell should be safe by default, not by luck. > [!IMPORTANT] > This repository enforces its own advice. `*.good.sh` examples must run to @@ -36,13 +36,15 @@ not by luck. - **Enforced, not asserted.** The example contract is checked in CI, so the guidance cannot rot without breaking the build. - **Portable by default.** Guidance calls out macOS Bash 3.2 and BSD-vs-GNU - differences instead of assuming Linux + GNU coreutils — and CI runs the - suite on both Linux and macOS, so that claim is checked, not just asserted. + differences instead of assuming Linux + GNU coreutils. The local gate runs on + macOS and Linux toolchains; hosted CI stays lean by running the full Ubuntu + quality gate. - **Judgment, not just syntax.** It also covers [when *not* to use shell](reference/meta-guidance.md) — reach for Python or Go before a 300-line Bash script. -- **Agent-ready.** Ships `AGENTS.md`, `CLAUDE.md`, Copilot instructions, and an - installable Claude Code plugin so the rules travel with your tools. +- **Agent-ready.** Ships `AGENTS.md`, `CHATGPT.md`, `CLAUDE.md`, Copilot + instructions, OpenAI skill metadata, and installable Claude Code plus Codex + plugin manifests so the rules travel with your tools. ## Skills @@ -82,7 +84,7 @@ Then: ```sh task # list every entrypoint -task ci # fmt-check, lint, examples, nushell, nushell-demo, test +task ci # fmt-check, lint, examples, nushell, nushell-demo, yaml-schemas, ai-integrations, test task hooks # run every git hook across the repo (lefthook) ``` @@ -104,11 +106,13 @@ Nushell examples are `*.nu`, verified with `nu --ide-check`. ## Use it with AI coding agents -| Agent | Entry point | -|-------|-------------| -| Codex, Cursor, general | [`AGENTS.md`](AGENTS.md) | -| Claude Code | [`CLAUDE.md`](CLAUDE.md), or install the plugin (below) | +| Surface | Entry point | +|---------|-------------| +| Codex / ChatGPT Cowork | [`.codex-plugin/plugin.json`](.codex-plugin/plugin.json), [`.agents/skills/`](.agents/skills/), [`.codex/config.toml`](.codex/config.toml), [`CHATGPT.md`](CHATGPT.md), and `skills/*/agents/openai.yaml` | +| Claude Code | [`CLAUDE.md`](CLAUDE.md), [`.claude-plugin/plugin.json`](.claude-plugin/plugin.json), or install the plugin below | +| Cursor, Gemini CLI, Windsurf, Goose, other skill clients | Copy or link `skills//` into the client's skills directory | | GitHub Copilot | [`.github/copilot-instructions.md`](.github/copilot-instructions.md) | +| Generic agents and crawlers | [`llms.txt`](llms.txt), [`AGENTS.md`](AGENTS.md), and the portable `SKILL.md` folders | Install the Claude Code plugin: @@ -122,6 +126,20 @@ subagent: it reviews Bash/POSIX sh/Zsh/Nushell changes strictly against this repo's own cited rules, running `shellcheck`/`shfmt`/`nu --ide-check` itself rather than eyeballing style. +## Registry and discovery + +The repo is ready for the open Agent Skills ecosystem: + +- `skills/*/SKILL.md` follows the Agent Skills directory and frontmatter + contract. +- `.agents/skills/*` exposes those same skills to Codex repo-scope discovery. +- `.codex-plugin/plugin.json` packages the same skills as a Codex plugin. +- `.claude-plugin/plugin.json` keeps the Claude Code plugin installable. +- `registry/` contains copy-ready listing drafts for AgenticSkills and Awesome + Codex Plugins. AgenticSkills submission needs the maintainer email at submit + time; Awesome Codex Plugins currently asks for its own scanner gate, which is + intentionally not added to CI unless you opt into that listing PR. + ## Repository map | Path | What | @@ -130,8 +148,14 @@ itself rather than eyeballing style. | `reference/` | in-depth references with citations | | `examples/` | runnable good/bad pairs | | `tools/`, `tests/` | verification scripts and the bats suite | -| `.github/workflows/` | CI (ShellCheck, shfmt, bats, Nushell, lefthook) | -| `.claude-plugin/` | plugin + marketplace manifests | +| `.github/workflows/` | lean hosted CI quality gate | +| `.agents/skills/` | Codex repo-scope skill discovery symlinks | +| `.codex/config.toml` | trusted Codex defaults for this checkout | +| `.serena/project.yml`, `.serena/memories/` | portable Serena onboarding context | +| `.codex-plugin/`, `.claude-plugin/` | Codex and Claude Code plugin manifests | +| `CHATGPT.md`, `llms.txt` | ChatGPT/Codex handoff and crawler-friendly index | +| `registry/` | directory submission drafts and listing metadata | +| `schemas/` | local JSON Schemas for project-owned YAML metadata | | `Taskfile.yml` | task runner entrypoints | ## Contributing @@ -148,7 +172,8 @@ The repository layout follows established Agent-Skills projects: - [anthropics/skills](https://github.com/anthropics/skills) — the official Agent Skills repo; `SKILL.md` frontmatter and progressive disclosure. -- [Agent Skills specification](https://agentskills.io) — the open standard. +- [Agent Skills specification](https://agentskills.io/specification) — the open + standard. - [netresearch/skill-repo-skill](https://github.com/netresearch/skill-repo-skill) — skill-repository layout, plugin packaging, and validation. @@ -158,7 +183,9 @@ Related projects and curated indexes of Agent Skills: [travisvn/awesome-claude-skills](https://github.com/travisvn/awesome-claude-skills), [ComposioHQ/awesome-claude-skills](https://github.com/ComposioHQ/awesome-claude-skills), [rohitg00/awesome-claude-code-toolkit](https://github.com/rohitg00/awesome-claude-code-toolkit), -[VoltAgent/awesome-claude-code-subagents](https://github.com/VoltAgent/awesome-claude-code-subagents). +[VoltAgent/awesome-claude-code-subagents](https://github.com/VoltAgent/awesome-claude-code-subagents), +[AgenticSkills](https://agenticskills.io/), and +[Awesome Codex Plugins](https://github.com/hashgraph-online/awesome-codex-plugins). ### Shell references and inspiration diff --git a/Taskfile.yml b/Taskfile.yml index 05a4db0..e829e10 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://taskfile.dev/schema.json # Taskfile — developer and CI entrypoints. Run `task` (or `task --list`) for the # menu. See https://taskfile.dev. # @@ -50,6 +51,16 @@ tasks: cmds: - ./tests/nushell-startup-demo.sh + yaml-schemas: + desc: Check every tracked YAML-like file declares a JSON schema + cmds: + - ./tools/check-yaml-schemas.sh + + ai-integrations: + desc: Validate Codex, ChatGPT, Serena, and plugin discovery surfaces + cmds: + - ./tools/check-ai-integrations.sh + test: desc: Run the bats behavioural suite cmds: @@ -68,4 +79,6 @@ tasks: - task: examples - task: nushell - task: nushell-demo + - task: yaml-schemas + - task: ai-integrations - task: test diff --git a/lefthook.yml b/lefthook.yml index 448a0e3..22335d1 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://json.schemastore.org/lefthook.json # lefthook.yml — Git hooks via Lefthook (https://lefthook.dev). # # A single static Go binary, no Python runtime — consistent with this repo's @@ -40,6 +41,15 @@ pre-commit: glob: "*.nu" run: ./tools/check-nushell.sh + yaml-schemas: + glob: "*.{yml,yaml,cff}" + run: ./tools/check-yaml-schemas.sh + + # Codex/ChatGPT/Serena discovery surfaces. This is cheap and self-scans, so + # it runs on every pre-commit to catch extensionless repo-scope skill links. + ai-integrations: + run: ./tools/check-ai-integrations.sh + # JSON validity (replaces pre-commit's check-json), via nu — already required. json-valid: glob: "*.json" @@ -52,7 +62,7 @@ pre-commit: # Trailing whitespace and final newline (fail-only, no mutation; portable). # Markdown is excluded — it uses two trailing spaces for hard line breaks. whitespace: - glob: "*.{sh,bash,nu,json,yml,yaml}" + glob: "*.{sh,bash,nu,json,yml,yaml,cff}" run: | status=0 for f in {staged_files}; do diff --git a/llms.txt b/llms.txt new file mode 100644 index 0000000..4d81bdf --- /dev/null +++ b/llms.txt @@ -0,0 +1,40 @@ +# Shell Skill Kit + +> Test-enforced Agent Skills for safe shell: Bash, Zsh, POSIX sh, Nushell, +> shebangs, streams, and common generated-script failure modes. + +Repository: https://github.com/posidoni/shell-skill +License: MIT +Package name: shell-skill + +## Start Here + +- [README.md](README.md): public overview, install paths, and skill map. +- [AGENTS.md](AGENTS.md): canonical coding-agent instructions for this repo. +- [CHATGPT.md](CHATGPT.md): Codex and ChatGPT packaging notes. +- [CONTRIBUTING.md](CONTRIBUTING.md): example contract and verification gate. +- [.agents/README.md](.agents/README.md): Codex repo-scope skill discovery notes. +- [.codex/config.toml](.codex/config.toml): trusted Codex project defaults. +- [.serena/project.yml](.serena/project.yml): portable Serena project setup. + +## Portable Skills + +- Codex repo-scope symlinks: [.agents/skills/](.agents/skills/) +- [skills/shell-standards/SKILL.md](skills/shell-standards/SKILL.md): baseline Bash safety rules. +- [skills/shebang/SKILL.md](skills/shebang/SKILL.md): interpreter-line guidance. +- [skills/streams/SKILL.md](skills/streams/SKILL.md): stdin, stdout, stderr, and redirection. +- [skills/bash/SKILL.md](skills/bash/SKILL.md): Bash-specific safety and portability. +- [skills/posix-sh/SKILL.md](skills/posix-sh/SKILL.md): POSIX sh portability. +- [skills/zsh/SKILL.md](skills/zsh/SKILL.md): zsh correctness and performance. +- [skills/nushell/SKILL.md](skills/nushell/SKILL.md): Nushell structured scripting. + +## References + +- [reference/shell-standards.md](reference/shell-standards.md) +- [reference/bash.md](reference/bash.md) +- [reference/posix-sh.md](reference/posix-sh.md) +- [reference/zsh.md](reference/zsh.md) +- [reference/nushell.md](reference/nushell.md) +- [reference/shebang.md](reference/shebang.md) +- [reference/streams.md](reference/streams.md) +- [reference/meta-guidance.md](reference/meta-guidance.md) diff --git a/registry/agenticskills-submission.md b/registry/agenticskills-submission.md new file mode 100644 index 0000000..58c67fb --- /dev/null +++ b/registry/agenticskills-submission.md @@ -0,0 +1,26 @@ +# AgenticSkills Submission Draft + +Source: https://agenticskills.io/submit + +Skill Name: Shell Skill Kit + +GitHub Repository URL: https://github.com/posidoni/shell-skill + +Category: Code Quality & Testing + +Short Description: Test-enforced shell skills for AI agents: safe Bash, Zsh, POSIX sh, Nushell, shebangs, streams, and examples. + +Tags: shell, bash, zsh, posix-sh, nushell, shellcheck, shfmt, agent-skills, codex, claude-code, ai-coding + +Author Name: Mikhail Kuznetsov + +Website or Twitter: https://github.com/posidoni + +Email: maintainer email required at submission time + +Notes: + +- The repo uses the open Agent Skills layout: each skill is a directory with `SKILL.md`. +- The repo includes Codex plugin metadata in `.codex-plugin/plugin.json`. +- The repo includes Claude Code plugin metadata in `.claude-plugin/plugin.json`. +- The skill bodies are backed by cited references and runnable examples. diff --git a/registry/awesome-codex-plugins-listing.md b/registry/awesome-codex-plugins-listing.md new file mode 100644 index 0000000..cc4f930 --- /dev/null +++ b/registry/awesome-codex-plugins-listing.md @@ -0,0 +1,17 @@ +# Awesome Codex Plugins Listing Draft + +Target: https://github.com/hashgraph-online/awesome-codex-plugins + +Suggested category: Community Plugins / Developer Tools + +Suggested listing: + +```md +- [Shell Skill Kit](https://github.com/posidoni/shell-skill) - Test-enforced Agent Skills for safe Bash, Zsh, POSIX sh, Nushell, shebangs, streams, and generated-script review. Ships portable `SKILL.md` folders plus Codex and Claude Code plugin metadata. +``` + +Submission note: + +- The target list currently requires its scanner gate for submitted plugins. +- This repository intentionally keeps GitHub Actions lean; add that scanner only + if the maintainer chooses to submit the listing PR. diff --git a/schemas/codex-plugin.schema.json b/schemas/codex-plugin.schema.json new file mode 100644 index 0000000..349c234 --- /dev/null +++ b/schemas/codex-plugin.schema.json @@ -0,0 +1,135 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/posidoni/shell-skill/schemas/codex-plugin.schema.json", + "title": "Codex Plugin Manifest", + "description": "Project-local schema for .codex-plugin/plugin.json.", + "type": "object", + "required": ["name", "version", "description", "author", "interface"], + "additionalProperties": true, + "properties": { + "id": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-]*$" + }, + "name": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9-]*$" + }, + "version": { + "type": "string", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(?:[-+][0-9A-Za-z.-]+)?$" + }, + "description": { + "type": "string", + "minLength": 20 + }, + "author": { + "type": "object", + "required": ["name"], + "additionalProperties": true, + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "email": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + } + } + }, + "homepage": { + "type": "string", + "format": "uri" + }, + "repository": { + "type": "string", + "format": "uri" + }, + "license": { + "type": "string", + "minLength": 1 + }, + "keywords": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + }, + "skills": { + "type": "string", + "pattern": "^\\./" + }, + "mcpServers": { + "type": "string", + "pattern": "^\\./" + }, + "apps": { + "type": "string", + "pattern": "^\\./" + }, + "interface": { + "type": "object", + "required": [ + "displayName", + "shortDescription", + "longDescription", + "developerName", + "category", + "capabilities", + "defaultPrompt" + ], + "additionalProperties": true, + "properties": { + "displayName": { + "type": "string", + "minLength": 1 + }, + "shortDescription": { + "type": "string", + "minLength": 10, + "maxLength": 80 + }, + "longDescription": { + "type": "string", + "minLength": 20 + }, + "developerName": { + "type": "string", + "minLength": 1 + }, + "category": { + "type": "string", + "minLength": 1 + }, + "capabilities": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "defaultPrompt": { + "type": "array", + "minItems": 1, + "maxItems": 3, + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + } + }, + "brandColor": { + "type": "string", + "pattern": "^#[0-9a-fA-F]{6}$" + } + } + } + } +} diff --git a/schemas/openai-skill-metadata.schema.json b/schemas/openai-skill-metadata.schema.json new file mode 100644 index 0000000..c02cc65 --- /dev/null +++ b/schemas/openai-skill-metadata.schema.json @@ -0,0 +1,52 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/posidoni/shell-skill/schemas/openai-skill-metadata.schema.json", + "title": "OpenAI Skill Metadata", + "type": "object", + "additionalProperties": false, + "properties": { + "interface": { + "type": "object", + "additionalProperties": false, + "required": ["display_name", "short_description", "default_prompt"], + "properties": { + "display_name": { + "type": "string", + "minLength": 1 + }, + "short_description": { + "type": "string", + "minLength": 25, + "maxLength": 64 + }, + "icon_small": { + "type": "string", + "pattern": "^\\./" + }, + "icon_large": { + "type": "string", + "pattern": "^\\./" + }, + "brand_color": { + "type": "string", + "pattern": "^#[0-9A-Fa-f]{6}$" + }, + "default_prompt": { + "type": "string", + "minLength": 1, + "pattern": "\\$[a-z0-9][a-z0-9-]*[a-z0-9]" + } + } + }, + "policy": { + "type": "object", + "additionalProperties": false, + "properties": { + "allow_implicit_invocation": { + "type": "boolean" + } + } + } + }, + "required": ["interface"] +} diff --git a/schemas/serena-project.schema.json b/schemas/serena-project.schema.json new file mode 100644 index 0000000..0eea6e7 --- /dev/null +++ b/schemas/serena-project.schema.json @@ -0,0 +1,111 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/posidoni/shell-skill/schemas/serena-project.schema.json", + "title": "Serena Project Configuration", + "description": "Project-local schema for the tracked .serena/project.yml subset.", + "type": "object", + "required": [ + "project_name", + "languages", + "encoding", + "ignore_all_files_in_gitignore", + "ls_workspace_folders", + "ignored_paths", + "read_only" + ], + "additionalProperties": true, + "properties": { + "project_name": { + "type": "string", + "minLength": 1 + }, + "languages": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "enum": ["bash", "json", "markdown"] + } + }, + "encoding": { + "type": "string", + "const": "utf-8" + }, + "activation_command": { + "type": ["string", "null"] + }, + "activation_command_timeout": { + "type": "number", + "exclusiveMinimum": 0 + }, + "line_ending": { + "type": ["string", "null"], + "enum": ["lf", "crlf", "native", "unset", null] + }, + "language_backend": { + "type": ["string", "null"], + "enum": ["LSP", "JetBrains", null] + }, + "ignore_all_files_in_gitignore": { + "type": "boolean" + }, + "ls_specific_settings": { + "type": "object" + }, + "ls_workspace_folders": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "ls_additional_workspace_folders": { + "type": "array", + "items": { + "type": "string" + } + }, + "ignored_paths": { + "type": "array", + "items": { + "type": "string" + } + }, + "read_only": { + "type": "boolean" + }, + "excluded_tools": { + "type": "array", + "items": { + "type": "string" + } + }, + "included_optional_tools": { + "type": "array", + "items": { + "type": "string" + } + }, + "fixed_tools": { + "type": "array", + "items": { + "type": "string" + } + }, + "initial_prompt": { + "type": "string" + }, + "read_only_memory_patterns": { + "type": "array", + "items": { + "type": "string" + } + }, + "ignored_memory_patterns": { + "type": "array", + "items": { + "type": "string" + } + } + } +} diff --git a/skills/bash/agents/openai.yaml b/skills/bash/agents/openai.yaml new file mode 100644 index 0000000..072a038 --- /dev/null +++ b/skills/bash/agents/openai.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../schemas/openai-skill-metadata.schema.json +interface: + display_name: "Bash Safety" + short_description: "Robust Bash scripts and portability" + brand_color: "#2563EB" + default_prompt: "Use $bash to write or review a robust portable Bash script." +policy: + allow_implicit_invocation: true diff --git a/skills/nushell/agents/openai.yaml b/skills/nushell/agents/openai.yaml new file mode 100644 index 0000000..3aa28b7 --- /dev/null +++ b/skills/nushell/agents/openai.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../schemas/openai-skill-metadata.schema.json +interface: + display_name: "Nushell" + short_description: "Structured Nushell scripting" + brand_color: "#2563EB" + default_prompt: "Use $nushell to review a Nushell script or startup config." +policy: + allow_implicit_invocation: true diff --git a/skills/posix-sh/agents/openai.yaml b/skills/posix-sh/agents/openai.yaml new file mode 100644 index 0000000..75c5887 --- /dev/null +++ b/skills/posix-sh/agents/openai.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../schemas/openai-skill-metadata.schema.json +interface: + display_name: "POSIX sh" + short_description: "Portable POSIX sh patterns" + brand_color: "#2563EB" + default_prompt: "Use $posix-sh to make this script portable under /bin/sh." +policy: + allow_implicit_invocation: true diff --git a/skills/shebang/agents/openai.yaml b/skills/shebang/agents/openai.yaml new file mode 100644 index 0000000..41265af --- /dev/null +++ b/skills/shebang/agents/openai.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../schemas/openai-skill-metadata.schema.json +interface: + display_name: "Shebang" + short_description: "Portable interpreter lines" + brand_color: "#2563EB" + default_prompt: "Use $shebang to choose the right interpreter line for this script." +policy: + allow_implicit_invocation: true diff --git a/skills/shell-standards/agents/openai.yaml b/skills/shell-standards/agents/openai.yaml new file mode 100644 index 0000000..eb4c63d --- /dev/null +++ b/skills/shell-standards/agents/openai.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../schemas/openai-skill-metadata.schema.json +interface: + display_name: "Shell Standards" + short_description: "Baseline safe shell rules" + brand_color: "#2563EB" + default_prompt: "Use $shell-standards to review this Bash script against safe shell rules." +policy: + allow_implicit_invocation: true diff --git a/skills/streams/SKILL.md b/skills/streams/SKILL.md index c650013..61aad33 100644 --- a/skills/streams/SKILL.md +++ b/skills/streams/SKILL.md @@ -3,9 +3,9 @@ name: streams description: >- Correct use of stdin, stdout, and stderr — send diagnostics to stderr and data to stdout, signal failure via the exit status, read input with while IFS= read - -r, put 2>&1 after the file redirection, and use here-docs/here-strings. Use - when a script produces output, reads input, or redirects, or when a pipe is - polluted by error text. + -r, order stderr duplication after file redirection, and use here-docs and + here-strings. Use when a script produces output, reads input, or redirects, + or when a pipe is polluted by error text. --- # Streams diff --git a/skills/streams/agents/openai.yaml b/skills/streams/agents/openai.yaml new file mode 100644 index 0000000..9a853af --- /dev/null +++ b/skills/streams/agents/openai.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../schemas/openai-skill-metadata.schema.json +interface: + display_name: "Streams" + short_description: "Correct stdin stdout stderr use" + brand_color: "#2563EB" + default_prompt: "Use $streams to fix stdin, stdout, stderr, and redirection behavior." +policy: + allow_implicit_invocation: true diff --git a/skills/zsh/agents/openai.yaml b/skills/zsh/agents/openai.yaml new file mode 100644 index 0000000..a1ca2d5 --- /dev/null +++ b/skills/zsh/agents/openai.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../schemas/openai-skill-metadata.schema.json +interface: + display_name: "Zsh" + short_description: "Fast safe zsh scripting patterns" + brand_color: "#2563EB" + default_prompt: "Use $zsh to review this zsh function or startup file." +policy: + allow_implicit_invocation: true diff --git a/tools/check-ai-integrations.sh b/tools/check-ai-integrations.sh new file mode 100755 index 0000000..53e9c0b --- /dev/null +++ b/tools/check-ai-integrations.sh @@ -0,0 +1,150 @@ +#!/usr/bin/env bash +set -euo pipefail + +status=0 + +fail() { + printf 'ai-integrations: %s\n' "$1" >&2 + status=1 +} + +require_file() { + if [[ ! -f $1 ]]; then + fail "missing file: $1" + fi +} + +require_dir() { + if [[ ! -d $1 ]]; then + fail "missing directory: $1" + fi +} + +require_file ".codex-plugin/plugin.json" +require_file ".claude-plugin/plugin.json" +require_file ".codex/config.toml" +require_file ".serena/project.yml" +require_file "CHATGPT.md" +require_file "llms.txt" +require_file "schemas/codex-plugin.schema.json" +require_file "schemas/openai-skill-metadata.schema.json" +require_file "schemas/serena-project.schema.json" +require_dir ".agents/skills" + +if [[ -d .codex-plugin ]]; then + while IFS= read -r extra_file; do + fail ".codex-plugin must contain only plugin.json, found: $extra_file" + done < <(find .codex-plugin -mindepth 1 -maxdepth 1 ! -name plugin.json -print) +fi + +nu -c ' +let p = open ".codex-plugin/plugin.json" +for key in [name version description author interface skills] { + if (($p | get -o $key) == null) { + error make {msg: $"missing .codex-plugin/plugin.json field: ($key)"} + } +} +if $p.skills != "./skills/" { + error make {msg: ".codex-plugin/plugin.json skills must be ./skills/"} +} +let i = $p.interface +for key in [displayName shortDescription longDescription developerName category capabilities defaultPrompt] { + if (($i | get -o $key) == null) { + error make {msg: $"missing .codex-plugin/plugin.json interface field: ($key)"} + } +} +if (($i.capabilities | length) == 0) { + error make {msg: ".codex-plugin/plugin.json interface.capabilities must not be empty"} +} +if (($i.defaultPrompt | describe) != "list") { + error make {msg: ".codex-plugin/plugin.json interface.defaultPrompt must be an array of strings"} +} +if (($i.defaultPrompt | length) == 0) or (($i.defaultPrompt | length) > 3) { + error make {msg: ".codex-plugin/plugin.json interface.defaultPrompt must contain 1-3 prompts"} +} +for prompt in $i.defaultPrompt { + if (($prompt | str trim | is-empty) or (($prompt | str length) > 128)) { + error make {msg: ".codex-plugin/plugin.json interface.defaultPrompt entries must be non-empty and <= 128 characters"} + } +} +' || fail "invalid Codex plugin manifest shape" + +nu -c ' +let c = open ".codex/config.toml" +if (($c | get -o features.multi_agent) != true) { + error make {msg: ".codex/config.toml must set features.multi_agent = true"} +} +' || fail "invalid Codex project config" + +nu -c ' +let s = open ".serena/project.yml" +for key in [project_name languages encoding ignore_all_files_in_gitignore ls_workspace_folders read_only] { + if (($s | get -o $key) == null) { + error make {msg: $"missing .serena/project.yml field: ($key)"} + } +} +for lang in [bash markdown json] { + if not ($lang in $s.languages) { + error make {msg: $"missing Serena language: ($lang)"} + } +} +for lang in $s.languages { + if not ($lang in [bash markdown json]) { + error make {msg: $"unsupported noisy Serena language in this repo: ($lang)"} + } +} +if $s.project_name != "shell-skill" { + error make {msg: ".serena/project.yml project_name must be shell-skill"} +} +if $s.ignore_all_files_in_gitignore != true { + error make {msg: ".serena/project.yml must respect gitignore"} +} +' || fail "invalid Serena project config" + +while IFS= read -r skill_md; do + skill_dir=${skill_md%/SKILL.md} + skill_name=$(awk ' + $0 == "---" { fence++; next } + fence == 1 && $1 == "name:" { + sub(/^name:[[:space:]]*/, "", $0) + gsub(/^["'\'']|["'\'']$/, "", $0) + print $0 + exit + } + ' "$skill_md") + + if [[ -z $skill_name ]]; then + fail "missing name frontmatter: $skill_md" + continue + fi + + if [[ $skill_name != "${skill_dir##*/}" ]]; then + fail "$skill_md name must match directory (${skill_dir##*/})" + fi + + openai_yaml="$skill_dir/agents/openai.yaml" + require_file "$openai_yaml" + + repo_skill=".agents/skills/$skill_name" + if [[ ! -L $repo_skill ]]; then + fail "missing Codex repo-scope skill symlink: $repo_skill" + continue + fi + + if [[ $(readlink "$repo_skill") != "../../skills/$skill_name" ]]; then + fail "$repo_skill must point to ../../skills/$skill_name" + fi + + if [[ ! -d $repo_skill ]]; then + fail "$repo_skill does not resolve to a skill directory" + fi +done < <(find skills -mindepth 2 -maxdepth 2 -name SKILL.md -print | sort) + +while IFS= read -r repo_skill; do + skill_name=${repo_skill##*/} + if [[ ! -d "skills/$skill_name" ]]; then + fail "stale Codex repo-scope skill entry: $repo_skill" + fi +done < <(find .agents/skills -mindepth 1 -maxdepth 1 -print | sort) + +exit "$status" diff --git a/tools/check-yaml-schemas.sh b/tools/check-yaml-schemas.sh new file mode 100755 index 0000000..fb5323b --- /dev/null +++ b/tools/check-yaml-schemas.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +status=0 + +while IFS= read -r file; do + if [[ ! -f "$file" ]]; then + continue + fi + + first_line=$(sed -n '1p' "$file") + if [[ ! $first_line =~ ^#\ yaml-language-server:\ \$schema= ]]; then + printf 'missing first-line YAML schema comment: %s\n' "$file" >&2 + status=1 + fi +done < <(git ls-files --cached --others --exclude-standard '*.yml' '*.yaml' '*.cff') + +exit "$status" From 107504f0a69c254954851da41d1bfb81ebc13b1a Mon Sep 17 00:00:00 2001 From: posidoni <89808617+posidoni@users.noreply.github.com> Date: Sat, 18 Jul 2026 14:26:32 +0400 Subject: [PATCH 2/9] docs: sharpen shell skill kit presentation --- .fdignore | 3 + .rgignore | 3 + .serena/memories/conventions.md | 1 + .serena/memories/core.md | 2 +- AGENTS.md | 3 +- CHANGELOG.md | 9 ++ README.md | 268 +++++++++++--------------------- REGISTRY.md | 53 +++++++ assets/logo.svg | 2 +- assets/social-preview.png | Bin 32768 -> 37512 bytes assets/social-preview.svg | 35 +++++ llms.txt | 1 + 12 files changed, 202 insertions(+), 178 deletions(-) create mode 100644 REGISTRY.md create mode 100644 assets/social-preview.svg diff --git a/.fdignore b/.fdignore index 0f590b5..22f21dc 100644 --- a/.fdignore +++ b/.fdignore @@ -7,3 +7,6 @@ # Legal / changelog boilerplate is rarely a useful `fd` hit. LICENSE + +# Codex repo-scope skills are symlinks to skills/*; hide duplicate listings. +.agents/skills/ diff --git a/.rgignore b/.rgignore index ee42a26..434e2fb 100644 --- a/.rgignore +++ b/.rgignore @@ -7,5 +7,8 @@ LICENSE CHANGELOG.md +# Codex repo-scope skills are symlinks to skills/*; hide duplicate search hits. +.agents/skills/** + # Keep broad `rg` sweeps bounded (repo convention: never traverse VCS/data dirs). .git/ diff --git a/.serena/memories/conventions.md b/.serena/memories/conventions.md index 3adb239..5d87f68 100644 --- a/.serena/memories/conventions.md +++ b/.serena/memories/conventions.md @@ -7,5 +7,6 @@ - Shell examples must be safe and portable; use `mktemp` + `trap` for filesystem work; no secrets, personal data, or machine-specific paths. - Tracked YAML-like files (`*.yml`, `*.yaml`, `*.cff`) must start with `# yaml-language-server: $schema=...` and pass `task yaml-schemas`. - Keep Codex repo-scope skill discovery as symlinks in `.agents/skills/* -> ../../skills/*`; never duplicate skill bodies there. +- Keep developer search lightweight by ignoring `.agents/skills/**` in `.rgignore` and `.fdignore`; canonical skill content lives under `skills/`. - Keep Serena portable config/memories tracked, but leave `.serena/cache`, `.serena/logs`, and `.serena/project.local.yml` ignored. Serena LSPs are limited to Bash, Markdown, and JSON because current YAML/TOML wrappers pass health-check but log client-configuration errors. - Keep GitHub automation lean: one hosted Ubuntu quality gate running `task ci` and `task hooks`; do not add scanner/release/macOS workflows unless the maintainer explicitly opts in. diff --git a/.serena/memories/core.md b/.serena/memories/core.md index 1cd4de1..fc5c4a8 100644 --- a/.serena/memories/core.md +++ b/.serena/memories/core.md @@ -4,6 +4,6 @@ - Purpose: cited, test-enforced Agent Skills for safe shell generation/review across Bash, Zsh, POSIX `sh`, Nushell, shebangs, and streams. - Source map: `skills//SKILL.md` are portable skill entrypoints; `reference/*.md` holds cited depth; `examples//` holds runnable good/bad pairs; `tools/` + `tests/` enforce the contract. - Agent packaging: Claude Code manifest in `.claude-plugin/plugin.json`; Codex manifest in `.codex-plugin/plugin.json`; repo-scope Codex discovery in `.agents/skills`; trusted Codex defaults in `.codex/config.toml`; ChatGPT/Codex notes in `CHATGPT.md`; crawler index in `llms.txt`; OpenAI UI metadata in `skills/*/agents/openai.yaml`. -- Registry prep: `registry/agenticskills-submission.md` and `registry/awesome-codex-plugins-listing.md` are copy-ready drafts; live submission still needs maintainer email / opt-in scanner gate. +- Registry prep: `REGISTRY.md` explains discovery surfaces; `registry/agenticskills-submission.md` and `registry/awesome-codex-plugins-listing.md` are copy-ready drafts; live submission still needs maintainer email / opt-in scanner gate. - Integration gate: `task ai-integrations` validates Codex, ChatGPT, Serena, plugin, and repo-scope skill discovery surfaces. - Read `mem:tech_stack` for tools and pins, `mem:conventions` for editing rules, and `mem:task_completion` for the done gate. diff --git a/AGENTS.md b/AGENTS.md index 40569e0..64d767d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -66,4 +66,5 @@ Nushell examples are `*.nu` and must pass `nu --ide-check`. | `.serena/project.yml`, `.serena/memories/` | portable Serena project setup | | `.codex-plugin/`, `.claude-plugin/` | Codex and Claude Code plugin manifests | | `CHATGPT.md`, `llms.txt` | ChatGPT/Codex handoff and crawler-friendly index | -| `schemas/`, `registry/` | JSON Schemas and directory submission drafts | +| `REGISTRY.md`, `registry/` | directory compatibility notes and listing drafts | +| `schemas/` | project-owned JSON Schemas | diff --git a/CHANGELOG.md b/CHANGELOG.md index 34feffb..f924e32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ follow [Semantic Versioning](https://semver.org/). ## [Unreleased] +### Changed + +- Tightened the README into a lighter product overview and moved registry and + ecosystem details into `REGISTRY.md`. +- Hid `.agents/skills` symlink duplicates from `rg` and `fd` searches while + keeping Codex repo-scope discovery intact. +- Rebuilt the social preview around the Shell Skill Kit name and added the SVG + source asset. + ## [0.2.0] - 2026-07-18 ### Added diff --git a/README.md b/README.md index ca31039..1f5d51f 100644 --- a/README.md +++ b/README.md @@ -1,220 +1,138 @@
-shell-skill +Shell Skill Kit # Shell Skill Kit -**Test-enforced shell rules for AI coding agents**: Bash, Zsh, POSIX `sh`, and Nushell, packaged as portable Agent Skills plus Claude Code and Codex plugin metadata. +**Small, test-enforced shell rules for AI coding agents.** -[Why](#why-this-exists) · [Skills](#skills) · [Quick start](#quick-start) · [Use with agents](#use-it-with-ai-coding-agents) · [Registry](#registry-and-discovery) · [Contributing](#contributing) +Bash, Zsh, POSIX `sh`, Nushell, shebangs, and streams, packaged as portable +Agent Skills plus Claude Code and Codex metadata. + +[Skills](#skills) | [Install](#install) | [Agent Surfaces](#agent-surfaces) | [Quality Gates](#quality-gates) | [Registry](REGISTRY.md)
--- -## The problem - -AI coding assistants now write a large share of the world's shell — and a lot of -it is quietly wrong: unquoted expansions, missing `set -euo pipefail`, `[ ]` -where `[[ ]]` belongs, real errors swallowed by a stray `|| true`. It works on -the happy path and fails silently everywhere else. And the usual advice ("quote -your variables") is folklore — unenforced, uncited, easy to skip. - -**Shell Skill Kit is the antidote.** It encodes the rules as cited references, -runnable examples that CI checks, and portable -[Agent Skills](https://agentskills.io/specification) you can load into Claude, -Codex, Cursor, Copilot, or any client that understands `SKILL.md` folders. The -goal is simple: generated shell should be safe by default, not by luck. - -> [!IMPORTANT] -> This repository enforces its own advice. `*.good.sh` examples must run to -> exit 0 and pass `shellcheck` + `shfmt`; `*.bad.sh` examples must trigger the -> exact ShellCheck codes they claim. If the docs drift from reality, CI goes red. - -## Why this exists - -- **Enforced, not asserted.** The example contract is checked in CI, so the - guidance cannot rot without breaking the build. -- **Portable by default.** Guidance calls out macOS Bash 3.2 and BSD-vs-GNU - differences instead of assuming Linux + GNU coreutils. The local gate runs on - macOS and Linux toolchains; hosted CI stays lean by running the full Ubuntu - quality gate. -- **Judgment, not just syntax.** It also covers - [when *not* to use shell](reference/meta-guidance.md) — reach for Python or Go - before a 300-line Bash script. -- **Agent-ready.** Ships `AGENTS.md`, `CHATGPT.md`, `CLAUDE.md`, Copilot - instructions, OpenAI skill metadata, and installable Claude Code plus Codex - plugin manifests so the rules travel with your tools. +## What It Is -## Skills +Shell Skill Kit is a lightweight rule pack for one risky place in AI coding: +generated shell. -| Skill | Covers | Reference | Examples | -|-------|--------|-----------|----------| -| [shell-standards](skills/shell-standards/SKILL.md) | strict mode, quoting, `[[ ]]`, arrays, traps, error handling | [reference](reference/shell-standards.md) | [examples](examples/standards/) | -| [shebang](skills/shebang/SKILL.md) | `#!/usr/bin/env`, `env -S` flags, absolute paths, dialect | [reference](reference/shebang.md) | [examples](examples/shebang/) | -| [streams](skills/streams/SKILL.md) | stdin/stdout/stderr, `>&2`, `read -r`, redirection order, here-docs | [reference](reference/streams.md) | [examples](examples/streams/) | -| [bash](skills/bash/SKILL.md) | error handling, macOS/BSD portability, arrays, temp files | [reference](reference/bash.md) | [examples](examples/bash/) | -| [posix-sh](skills/posix-sh/SKILL.md) | no `local`, no arrays, `[ ]` not `[[ ]]`, `set -eu` without `pipefail` | [reference](reference/posix-sh.md) | [examples](examples/posix-sh/) | -| [zsh](skills/zsh/SKILL.md) | word-splitting, 1-indexed arrays, `emulate`, globbing | [reference](reference/zsh.md) | prose | -| [nushell](skills/nushell/SKILL.md) | structured data, config load order, parse-time `source` | [reference](reference/nushell.md) | [examples](examples/nushell/) | +It is deliberately not a framework. The repo ships four things: -> [!NOTE] -> ShellCheck and shfmt do not support zsh, so the zsh guidance is prose only — -> itself a reason to prefer Bash for portable, lintable scripts. +- short `SKILL.md` files that agents can load on demand; +- cited references for the reasoning behind each rule; +- runnable good/bad examples that CI checks; +- provider metadata for Codex, ChatGPT, Claude Code, Copilot, Serena, and other + skill-aware tools. -## Quick start +The design contract is simple: **tiny skill bodies, deep references, executable +claims**. If a shell rule cannot be cited or tested, it stays out. -Install the toolchain (macOS): +## Why It Exists -```sh -brew install shellcheck shfmt nushell bats-core go-task lefthook -``` +AI assistants often produce shell that works once and fails quietly later: +unquoted expansions, wrong dialects, swallowed errors, stdout polluted with +diagnostics, or Bash code hiding under `#!/bin/sh`. -
-Linux (pinned versions) +Shell Skill Kit makes those failure modes harder to ship. `*.good.sh` examples +must run and lint cleanly. `*.bad.sh` examples must trigger the exact ShellCheck +codes they claim. Nushell examples must parse with `nu --ide-check`. When docs +and reality drift, the build goes red. -Use the versions CI installs — see -[`tools/ci-install-linux.sh`](tools/ci-install-linux.sh), which fetches pinned -`shfmt`, `nushell`, and `task` binaries and installs `shellcheck` + `bats` from -apt. +## Skills -
+| Skill | Use it for | Reference | Examples | +|-------|------------|-----------|----------| +| [shell-standards](skills/shell-standards/SKILL.md) | strict mode, quoting, arrays, traps, `printf`, `[[ ]]` | [reference](reference/shell-standards.md) | [examples](examples/standards/) | +| [bash](skills/bash/SKILL.md) | Bash error handling, arrays, macOS/BSD portability | [reference](reference/bash.md) | [examples](examples/bash/) | +| [posix-sh](skills/posix-sh/SKILL.md) | real `/bin/sh`, dash, no arrays, no bashisms | [reference](reference/posix-sh.md) | [examples](examples/posix-sh/) | +| [zsh](skills/zsh/SKILL.md) | zsh functions, options, globbing, no-fork performance | [reference](reference/zsh.md) | prose | +| [nushell](skills/nushell/SKILL.md) | structured pipelines, parse-time config, typed commands | [reference](reference/nushell.md) | [examples](examples/nushell/) | +| [shebang](skills/shebang/SKILL.md) | `env`, `env -S`, interpreter paths, dialect choice | [reference](reference/shebang.md) | [examples](examples/shebang/) | +| [streams](skills/streams/SKILL.md) | stdin/stdout/stderr, redirection order, here-docs | [reference](reference/streams.md) | [examples](examples/streams/) | -Then: +ShellCheck and shfmt do not support zsh, so zsh guidance is prose plus +`zsh -n` syntax checking. For portable, lintable scripts, prefer Bash. -```sh -task # list every entrypoint -task ci # fmt-check, lint, examples, nushell, nushell-demo, yaml-schemas, ai-integrations, test -task hooks # run every git hook across the repo (lefthook) +## Install + +Claude Code plugin: + +```text +/plugin marketplace add posidoni/shell-skill +/plugin install shell-skill@shell-skill ``` -This project uses [Task](https://taskfile.dev), not Make — a single -cross-platform binary with plain-YAML, self-documenting tasks. See -[CONTRIBUTING.md](CONTRIBUTING.md) for the rationale. +Portable skill clients: -## The example contract +```sh +git clone https://github.com/posidoni/shell-skill +ln -s "$PWD/shell-skill/skills/bash" "$YOUR_SKILLS_DIR/bash" +ln -s "$PWD/shell-skill/skills/shell-standards" "$YOUR_SKILLS_DIR/shell-standards" +``` -Examples live in `examples//` as paired files: +Local development toolchain on macOS: -- **`NN-slug.good.sh`** — the correct pattern; self-contained, no arguments, - exits 0, clean under `shellcheck --severity=warning` and `shfmt`. -- **`NN-slug.bad.sh`** — a safe-to-run anti-pattern carrying one directive: - `# expect-shellcheck: SC####` (codes ShellCheck must report) or - `# expect-shellcheck: none` (a style-guide-only pitfall). +```sh +brew install shellcheck shfmt nushell bats-core go-task lefthook +task ci +task hooks +``` -Nushell examples are `*.nu`, verified with `nu --ide-check`. +Linux CI installs pinned versions through +[tools/ci-install-linux.sh](tools/ci-install-linux.sh). -## Use it with AI coding agents +## Agent Surfaces | Surface | Entry point | |---------|-------------| -| Codex / ChatGPT Cowork | [`.codex-plugin/plugin.json`](.codex-plugin/plugin.json), [`.agents/skills/`](.agents/skills/), [`.codex/config.toml`](.codex/config.toml), [`CHATGPT.md`](CHATGPT.md), and `skills/*/agents/openai.yaml` | -| Claude Code | [`CLAUDE.md`](CLAUDE.md), [`.claude-plugin/plugin.json`](.claude-plugin/plugin.json), or install the plugin below | -| Cursor, Gemini CLI, Windsurf, Goose, other skill clients | Copy or link `skills//` into the client's skills directory | -| GitHub Copilot | [`.github/copilot-instructions.md`](.github/copilot-instructions.md) | -| Generic agents and crawlers | [`llms.txt`](llms.txt), [`AGENTS.md`](AGENTS.md), and the portable `SKILL.md` folders | +| Codex / ChatGPT Cowork | [.codex-plugin/plugin.json](.codex-plugin/plugin.json), [.agents/skills/](.agents/skills/), [.codex/config.toml](.codex/config.toml), [CHATGPT.md](CHATGPT.md) | +| Claude Code | [CLAUDE.md](CLAUDE.md), [.claude-plugin/plugin.json](.claude-plugin/plugin.json), [agents/shell-reviewer.md](agents/shell-reviewer.md) | +| GitHub Copilot | [.github/copilot-instructions.md](.github/copilot-instructions.md) | +| Serena | [.serena/project.yml](.serena/project.yml), [.serena/memories/](.serena/memories/) | +| Generic agents and crawlers | [AGENTS.md](AGENTS.md), [llms.txt](llms.txt), `skills/*/SKILL.md` | -Install the Claude Code plugin: +See [REGISTRY.md](REGISTRY.md) for directory listings, ecosystem notes, and +submission drafts. +## Quality Gates + +```sh +task --list # discover entrypoints +task ci # full local/CI gate +task hooks # lefthook pre-commit mirror across the repo +task ai-integrations # Codex, ChatGPT, Serena, plugin, and skill metadata ``` -/plugin marketplace add posidoni/shell-skill -/plugin install shell-skill@shell-skill -``` -The plugin also ships a [`shell-reviewer`](agents/shell-reviewer.md) -subagent: it reviews Bash/POSIX sh/Zsh/Nushell changes strictly against -this repo's own cited rules, running `shellcheck`/`shfmt`/`nu --ide-check` -itself rather than eyeballing style. - -## Registry and discovery - -The repo is ready for the open Agent Skills ecosystem: - -- `skills/*/SKILL.md` follows the Agent Skills directory and frontmatter - contract. -- `.agents/skills/*` exposes those same skills to Codex repo-scope discovery. -- `.codex-plugin/plugin.json` packages the same skills as a Codex plugin. -- `.claude-plugin/plugin.json` keeps the Claude Code plugin installable. -- `registry/` contains copy-ready listing drafts for AgenticSkills and Awesome - Codex Plugins. AgenticSkills submission needs the maintainer email at submit - time; Awesome Codex Plugins currently asks for its own scanner gate, which is - intentionally not added to CI unless you opt into that listing PR. - -## Repository map - -| Path | What | -|------|------| -| `skills/` | Agent Skills (`SKILL.md` per domain) | -| `reference/` | in-depth references with citations | -| `examples/` | runnable good/bad pairs | -| `tools/`, `tests/` | verification scripts and the bats suite | -| `.github/workflows/` | lean hosted CI quality gate | -| `.agents/skills/` | Codex repo-scope skill discovery symlinks | -| `.codex/config.toml` | trusted Codex defaults for this checkout | -| `.serena/project.yml`, `.serena/memories/` | portable Serena onboarding context | -| `.codex-plugin/`, `.claude-plugin/` | Codex and Claude Code plugin manifests | -| `CHATGPT.md`, `llms.txt` | ChatGPT/Codex handoff and crawler-friendly index | -| `registry/` | directory submission drafts and listing metadata | -| `schemas/` | local JSON Schemas for project-owned YAML metadata | -| `Taskfile.yml` | task runner entrypoints | +`task ci` runs formatting, shell linting, example checks, Nushell parsing, YAML +schema-modeline checks, AI integration checks, and the bats suite. + +## Repository Map + +| Path | Purpose | +|------|---------| +| `skills/` | portable Agent Skills | +| `reference/` | cited rule explanations | +| `examples/` | runnable good/bad contracts | +| `tools/`, `tests/` | verification scripts and bats tests | +| `.agents/`, `.codex-plugin/`, `.claude-plugin/` | AI provider packaging | +| `.serena/` | portable Serena project setup | +| `schemas/` | project-owned JSON Schemas | +| `registry/`, `REGISTRY.md` | listing drafts and discovery notes | ## Contributing -See [CONTRIBUTING.md](CONTRIBUTING.md) for the toolchain, the example contract, -and the PR checklist. Please also read the [Code of Conduct](CODE_OF_CONDUCT.md). -Security reports: [SECURITY.md](SECURITY.md). Curious how this was actually -built — the research process, the enforcement discipline? See -[ENGINEERING.md](ENGINEERING.md). - -## References - -The repository layout follows established Agent-Skills projects: - -- [anthropics/skills](https://github.com/anthropics/skills) — the official Agent - Skills repo; `SKILL.md` frontmatter and progressive disclosure. -- [Agent Skills specification](https://agentskills.io/specification) — the open - standard. -- [netresearch/skill-repo-skill](https://github.com/netresearch/skill-repo-skill) - — skill-repository layout, plugin packaging, and validation. - -Related projects and curated indexes of Agent Skills: -[addyosmani/agent-skills](https://github.com/addyosmani/agent-skills), -[hesreallyhim/awesome-claude-code](https://github.com/hesreallyhim/awesome-claude-code), -[travisvn/awesome-claude-skills](https://github.com/travisvn/awesome-claude-skills), -[ComposioHQ/awesome-claude-skills](https://github.com/ComposioHQ/awesome-claude-skills), -[rohitg00/awesome-claude-code-toolkit](https://github.com/rohitg00/awesome-claude-code-toolkit), -[VoltAgent/awesome-claude-code-subagents](https://github.com/VoltAgent/awesome-claude-code-subagents), -[AgenticSkills](https://agenticskills.io/), and -[Awesome Codex Plugins](https://github.com/hashgraph-online/awesome-codex-plugins). - -### Shell references and inspiration - -The guidance draws on — and is indebted to — these sources: - -- [Google Shell Style Guide](https://google.github.io/styleguide/shellguide.html) - and the [ShellCheck wiki](https://www.shellcheck.net/wiki/) — the backbone of - the rules and codes. -- [Greg's Wiki — BashFAQ & BashPitfalls](https://mywiki.wooledge.org/) — the - definitive catalogue of shell gotchas. -- [dylanaraps/pure-bash-bible](https://github.com/dylanaraps/pure-bash-bible) — - pure-bash alternatives to external commands. -- [anordal/shellharden](https://github.com/anordal/shellharden) — a safety - auto-corrector and its precise "what you don't need to quote" rules. -- [koalaman/shellcheck](https://github.com/koalaman/shellcheck) — one page per - diagnostic; the model for machine-checkable rules. -- [romkatv/zsh-bench](https://github.com/romkatv/zsh-bench) & - [Powerlevel10k](https://github.com/romkatv/powerlevel10k) — the zsh - no-subprocess performance discipline. -- [The Nushell Book](https://www.nushell.sh/book/) — the Nushell chapters. -- Shebang mechanics: [`execve(2)`](https://man7.org/linux/man-pages/man2/execve.2.html) - and the merged Linux doc fix by [@alurm](https://github.com/alurm). +Read [CONTRIBUTING.md](CONTRIBUTING.md) for the example contract and PR checklist. +Read [AGENTS.md](AGENTS.md) before agent-assisted edits. Security reports go +through [SECURITY.md](SECURITY.md). ## License -[MIT](LICENSE) © 2026 Mikhail Kuznetsov ([@posidoni](https://github.com/posidoni)) - ---- +[MIT](LICENSE) (c) 2026 Mikhail Kuznetsov ([@posidoni](https://github.com/posidoni))
-Safe shell, for humans and the agents that write it. +Safe shell for humans and the agents that write it.
diff --git a/REGISTRY.md b/REGISTRY.md new file mode 100644 index 0000000..eb75ad2 --- /dev/null +++ b/REGISTRY.md @@ -0,0 +1,53 @@ +# Registry And Discovery + +Shell Skill Kit is prepared for skill clients, plugin clients, and crawler-style +discovery without requiring a heavyweight runtime. + +## Published Surfaces + +| Surface | Files | +|---------|-------| +| Portable Agent Skills | `skills/*/SKILL.md` | +| Codex repo-scope skills | `.agents/skills/*` symlinks | +| Codex / ChatGPT plugin | `.codex-plugin/plugin.json` | +| Claude Code plugin | `.claude-plugin/plugin.json` | +| ChatGPT / Codex handoff | `CHATGPT.md`, `llms.txt`, `skills/*/agents/openai.yaml` | +| Serena project context | `.serena/project.yml`, `.serena/memories/*.md` | +| GitHub Copilot | `.github/copilot-instructions.md` | + +## Listing Drafts + +- [registry/agenticskills-submission.md](registry/agenticskills-submission.md) + contains the copy-ready AgenticSkills submission fields. +- [registry/awesome-codex-plugins-listing.md](registry/awesome-codex-plugins-listing.md) + contains the Awesome Codex Plugins listing snippet and scanner note. + +Live submission still needs the maintainer's explicit action. Keep hosted CI +lean unless a target registry requires a scanner gate for a listing PR. + +## Compatibility Notes + +- Keep the public product name **Shell Skill Kit**. +- Keep the stable technical id **shell-skill** for repository, plugin, and + package references. +- Keep `SKILL.md` frontmatter minimal and portable; provider-specific UI metadata + belongs in companion files such as `agents/openai.yaml`. +- Keep `.codex-plugin/` to `plugin.json` only. The actual skills stay in the + top-level `skills/` directory and are referenced from the manifest. +- Keep `.agents/skills/*` as symlinks to `../../skills/*`; do not duplicate skill + bodies for Codex repo-scope discovery. +- Keep Serena language servers limited to Bash, Markdown, and JSON until the YAML + and TOML wrappers stop logging noisy client-configuration errors. YAML, TOML, + and Nushell are covered by repository gates instead. + +## Ecosystem References + +- [Agent Skills specification](https://agentskills.io/specification) +- [AgenticSkills](https://agenticskills.io/) +- [Awesome Codex Plugins](https://github.com/hashgraph-online/awesome-codex-plugins) +- [anthropics/skills](https://github.com/anthropics/skills) +- [addyosmani/agent-skills](https://github.com/addyosmani/agent-skills) +- [hesreallyhim/awesome-claude-code](https://github.com/hesreallyhim/awesome-claude-code) +- [travisvn/awesome-claude-skills](https://github.com/travisvn/awesome-claude-skills) +- [ComposioHQ/awesome-claude-skills](https://github.com/ComposioHQ/awesome-claude-skills) +- [VoltAgent/awesome-claude-code-subagents](https://github.com/VoltAgent/awesome-claude-code-subagents) diff --git a/assets/logo.svg b/assets/logo.svg index 862e472..6f203de 100644 --- a/assets/logo.svg +++ b/assets/logo.svg @@ -1,5 +1,5 @@ - shell-skill + Shell Skill Kit diff --git a/assets/social-preview.png b/assets/social-preview.png index 378136c296b1adfd76bfebacb997e28dfb0c21ac..f952b288ed1ce3accbd38f9677de289a5cd615b5 100644 GIT binary patch literal 37512 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 O$;+y|Dthtm%l`lcstEP~ diff --git a/assets/social-preview.svg b/assets/social-preview.svg new file mode 100644 index 0000000..59785f2 --- /dev/null +++ b/assets/social-preview.svg @@ -0,0 +1,35 @@ + + Shell Skill Kit social preview + Brand preview for Shell Skill Kit, a test-enforced shell rule pack for AI coding agents. + + + + + + + + + + + + + + + + + + + + + + Shell Skill Kit + test-enforced shell rules for AI coding agents + + Bash + Zsh + POSIX sh + Nushell + ShellCheck + Codex + + diff --git a/llms.txt b/llms.txt index 4d81bdf..dd4f8d5 100644 --- a/llms.txt +++ b/llms.txt @@ -13,6 +13,7 @@ Package name: shell-skill - [AGENTS.md](AGENTS.md): canonical coding-agent instructions for this repo. - [CHATGPT.md](CHATGPT.md): Codex and ChatGPT packaging notes. - [CONTRIBUTING.md](CONTRIBUTING.md): example contract and verification gate. +- [REGISTRY.md](REGISTRY.md): provider compatibility and directory listings. - [.agents/README.md](.agents/README.md): Codex repo-scope skill discovery notes. - [.codex/config.toml](.codex/config.toml): trusted Codex project defaults. - [.serena/project.yml](.serena/project.yml): portable Serena project setup. From f3b1902563bd842936fa5f2f665cf3a7618db443 Mon Sep 17 00:00:00 2001 From: posidoni <89808617+posidoni@users.noreply.github.com> Date: Sat, 18 Jul 2026 14:32:47 +0400 Subject: [PATCH 3/9] chore: remove release clutter --- .serena/memories/core.md | 2 +- .serena/memories/suggested_commands.md | 2 +- AGENTS.md | 5 +- CHANGELOG.md | 2 + CONTRIBUTING.md | 4 +- ENGINEERING.md | 71 ----------------------- README.md | 6 +- REGISTRY.md | 22 +++++-- Taskfile.yml | 2 +- registry/agenticskills-submission.md | 26 --------- registry/awesome-codex-plugins-listing.md | 17 ------ 11 files changed, 31 insertions(+), 128 deletions(-) delete mode 100644 ENGINEERING.md delete mode 100644 registry/agenticskills-submission.md delete mode 100644 registry/awesome-codex-plugins-listing.md diff --git a/.serena/memories/core.md b/.serena/memories/core.md index fc5c4a8..95f3110 100644 --- a/.serena/memories/core.md +++ b/.serena/memories/core.md @@ -4,6 +4,6 @@ - Purpose: cited, test-enforced Agent Skills for safe shell generation/review across Bash, Zsh, POSIX `sh`, Nushell, shebangs, and streams. - Source map: `skills//SKILL.md` are portable skill entrypoints; `reference/*.md` holds cited depth; `examples//` holds runnable good/bad pairs; `tools/` + `tests/` enforce the contract. - Agent packaging: Claude Code manifest in `.claude-plugin/plugin.json`; Codex manifest in `.codex-plugin/plugin.json`; repo-scope Codex discovery in `.agents/skills`; trusted Codex defaults in `.codex/config.toml`; ChatGPT/Codex notes in `CHATGPT.md`; crawler index in `llms.txt`; OpenAI UI metadata in `skills/*/agents/openai.yaml`. -- Registry prep: `REGISTRY.md` explains discovery surfaces; `registry/agenticskills-submission.md` and `registry/awesome-codex-plugins-listing.md` are copy-ready drafts; live submission still needs maintainer email / opt-in scanner gate. +- Registry prep: `REGISTRY.md` explains discovery surfaces and keeps copy-ready listing text; live submission still needs maintainer email / opt-in scanner gate. - Integration gate: `task ai-integrations` validates Codex, ChatGPT, Serena, plugin, and repo-scope skill discovery surfaces. - Read `mem:tech_stack` for tools and pins, `mem:conventions` for editing rules, and `mem:task_completion` for the done gate. diff --git a/.serena/memories/suggested_commands.md b/.serena/memories/suggested_commands.md index f06ced5..76e71e9 100644 --- a/.serena/memories/suggested_commands.md +++ b/.serena/memories/suggested_commands.md @@ -2,7 +2,7 @@ - Discover commands: `task --list`. - Format shell scripts: `task fmt`. -- Full local/CI quality gate: `task ci`. +- Core quality gate: `task ci`. - Pre-commit mirror across repo: `task hooks`. - YAML schema modeline gate: `task yaml-schemas`. - AI integration metadata gate: `task ai-integrations`. diff --git a/AGENTS.md b/AGENTS.md index 64d767d..d411484 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,8 +6,9 @@ Cursor, and any other tool that reads an `AGENTS.md`. Humans should start with ## What this repository is -`shell-skill` teaches safe, correct shell (Bash, Zsh, POSIX `sh`) and Nushell +Shell Skill Kit teaches safe, correct shell (Bash, Zsh, POSIX `sh`) and Nushell through cited rules, runnable good/bad examples, and loadable agent skills. +The stable technical id remains `shell-skill`. ## Golden rules @@ -66,5 +67,5 @@ Nushell examples are `*.nu` and must pass `nu --ide-check`. | `.serena/project.yml`, `.serena/memories/` | portable Serena project setup | | `.codex-plugin/`, `.claude-plugin/` | Codex and Claude Code plugin manifests | | `CHATGPT.md`, `llms.txt` | ChatGPT/Codex handoff and crawler-friendly index | -| `REGISTRY.md`, `registry/` | directory compatibility notes and listing drafts | +| `REGISTRY.md` | directory compatibility notes and listing drafts | | `schemas/` | project-owned JSON Schemas | diff --git a/CHANGELOG.md b/CHANGELOG.md index f924e32..ffeff05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ follow [Semantic Versioning](https://semver.org/). keeping Codex repo-scope discovery intact. - Rebuilt the social preview around the Shell Skill Kit name and added the SVG source asset. +- Removed the process diary and folded duplicate registry drafts into + `REGISTRY.md` to keep the public tree sharp. ## [0.2.0] - 2026-07-18 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6153e8c..7f9041b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing -Thanks for your interest in improving **shell-skill**. This repository teaches +Thanks for your interest in improving **Shell Skill Kit**. This repository teaches safe, correct shell through rules, runnable examples, and AI-agent skills. Every contribution is expected to hold that same bar: correct, safe, portable, and verified by the toolchain before it lands. @@ -49,7 +49,7 @@ entrypoint. The important ones: ```sh task fmt # format all shell scripts in place (shfmt -w) -task ci # everything CI runs: fmt-check, lint, examples, nushell, nushell-demo, yaml-schemas, ai-integrations, test +task ci # core quality gate: fmt-check, lint, examples, nushell, nushell-demo, yaml-schemas, ai-integrations, test task hooks # run every git hook across the repo (lefthook) task yaml-schemas # verify tracked YAML-like files declare a JSON schema task ai-integrations # verify Codex/ChatGPT/Serena/plugin discovery surfaces diff --git a/ENGINEERING.md b/ENGINEERING.md deleted file mode 100644 index 2e4eecd..0000000 --- a/ENGINEERING.md +++ /dev/null @@ -1,71 +0,0 @@ -# Engineering notes - -How this repository was actually built — for anyone curious about the process, -not just the output. - -## Enforcement-first, not asserted - -The organizing idea is that shell advice is usually folklore: unenforced, easy -to skip, quietly drifting from reality. Every rule here ships as a runnable -example CI checks on every push: - -- `*.good.sh` must run to exit 0 with no arguments and pass - `shellcheck --severity=warning` and `shfmt`. -- `*.bad.sh` must be safe to run and must trigger the exact ShellCheck code it - declares — [`tools/check-bad-examples.sh`](tools/check-bad-examples.sh) turns - "does the linter actually catch this?" into a tested claim instead of an - assertion. -- `*.nu` must pass `nu --ide-check` cleanly. - -If a rule and the code disagree, the build goes red. See -[CONTRIBUTING.md](CONTRIBUTING.md#the-example-contract) for the full contract. - -## Built with multi-agent research, not memory - -Several domains needed depth beyond what a single pass could responsibly -assert — the correctness bar here is "cite it or don't write it." Rather than -writing from recall, this repo's content was produced by dispatching parallel -research agents against primary sources, each required to cite a URL and -paraphrase rather than copy: - -- The Nushell parse-time/runtime model and its config load order came from a - fan-out over the official [Nushell Book](https://www.nushell.sh/book/) and - release notes. -- The zsh performance section — including *why* `$TTY` is correct and - `$(tty)` is not (fork cost, and `tty(1)` inspecting fd 0 rather than the - shell's controlling terminal) — came from a targeted pass over - [romkatv](https://github.com/romkatv)'s `zsh-bench` and `Powerlevel10k`. -- The shebang mechanics (the kernel's single-argument rule behind `env -S`, - the `BINPRM_BUF_SIZE` truncation limit) came from `execve(2)` and the GNU - `env` manual, plus a merged Linux kernel documentation fix by - [@alurm](https://github.com/alurm). -- The `printf`-over-`echo` and stream-handling rules came from a research pass - that also caught the repo's *own* tooling using `echo` — which was then - rewritten to `printf` throughout, so the rule and the practice agree. - -Every one of these went through an adversarial review pass (technical -accuracy, docs quality, publish-safety) before being committed — one finding -from that pass corrected an overclaim in the standards reference about which -ShellCheck severity actually enforces which rule. - -## Iterative, checkpointed, never committed red - -The repo was developed incrementally: each change is scoped to one concern, -verified locally (`task ci` and `task hooks` both green) before it is -committed, and pushed only once proven. Nothing here was committed on faith -that CI would catch it later — CI is the backstop, not the first check. - -## Transparent about how it was made - -Every commit keeps its `Co-Authored-By: Claude` trailer, by choice, rather -than being scrubbed for appearance — an accurate record beats a flattering -one. The scope, structure, and every publish decision were the maintainer's -calls throughout, including a hard boundary the agent respected without -exception: it could prepare, verify, and stage everything, but the decision to -make the repository public was never its call to make. - -## Result - -Six enforced skill domains, a CI-checked example contract, cross-platform -verification (Linux and macOS), and a written history where the claims and the -mechanism that checks them live in the same repository. diff --git a/README.md b/README.md index 1f5d51f..c4abba5 100644 --- a/README.md +++ b/README.md @@ -96,13 +96,13 @@ Linux CI installs pinned versions through | Generic agents and crawlers | [AGENTS.md](AGENTS.md), [llms.txt](llms.txt), `skills/*/SKILL.md` | See [REGISTRY.md](REGISTRY.md) for directory listings, ecosystem notes, and -submission drafts. +copy-ready submission text. ## Quality Gates ```sh task --list # discover entrypoints -task ci # full local/CI gate +task ci # core quality gate task hooks # lefthook pre-commit mirror across the repo task ai-integrations # Codex, ChatGPT, Serena, plugin, and skill metadata ``` @@ -121,7 +121,7 @@ schema-modeline checks, AI integration checks, and the bats suite. | `.agents/`, `.codex-plugin/`, `.claude-plugin/` | AI provider packaging | | `.serena/` | portable Serena project setup | | `schemas/` | project-owned JSON Schemas | -| `registry/`, `REGISTRY.md` | listing drafts and discovery notes | +| `REGISTRY.md` | listing drafts and discovery notes | ## Contributing diff --git a/REGISTRY.md b/REGISTRY.md index eb75ad2..777854a 100644 --- a/REGISTRY.md +++ b/REGISTRY.md @@ -17,10 +17,24 @@ discovery without requiring a heavyweight runtime. ## Listing Drafts -- [registry/agenticskills-submission.md](registry/agenticskills-submission.md) - contains the copy-ready AgenticSkills submission fields. -- [registry/awesome-codex-plugins-listing.md](registry/awesome-codex-plugins-listing.md) - contains the Awesome Codex Plugins listing snippet and scanner note. +AgenticSkills: + +```text +Skill Name: Shell Skill Kit +GitHub Repository URL: https://github.com/posidoni/shell-skill +Category: Code Quality & Testing +Short Description: Test-enforced shell rules for AI agents: Bash, Zsh, POSIX sh, Nushell, shebangs, streams, and examples. +Tags: shell, bash, zsh, posix-sh, nushell, shellcheck, shfmt, agent-skills, codex, claude-code, ai-coding +Author Name: Mikhail Kuznetsov +Website or Twitter: https://github.com/posidoni +Email: maintainer email required at submission time +``` + +Awesome Codex Plugins: + +```md +- [Shell Skill Kit](https://github.com/posidoni/shell-skill) - Test-enforced Agent Skills for safe Bash, Zsh, POSIX sh, Nushell, shebangs, streams, and generated-script review. Ships portable `SKILL.md` folders plus Codex and Claude Code plugin metadata. +``` Live submission still needs the maintainer's explicit action. Keep hosted CI lean unless a target registry requires a scanner gate for a listing PR. diff --git a/Taskfile.yml b/Taskfile.yml index e829e10..b716946 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -72,7 +72,7 @@ tasks: - lefthook run pre-commit --all-files ci: - desc: Run everything CI runs + desc: Run the core quality gate cmds: - task: fmt-check - task: lint diff --git a/registry/agenticskills-submission.md b/registry/agenticskills-submission.md deleted file mode 100644 index 58c67fb..0000000 --- a/registry/agenticskills-submission.md +++ /dev/null @@ -1,26 +0,0 @@ -# AgenticSkills Submission Draft - -Source: https://agenticskills.io/submit - -Skill Name: Shell Skill Kit - -GitHub Repository URL: https://github.com/posidoni/shell-skill - -Category: Code Quality & Testing - -Short Description: Test-enforced shell skills for AI agents: safe Bash, Zsh, POSIX sh, Nushell, shebangs, streams, and examples. - -Tags: shell, bash, zsh, posix-sh, nushell, shellcheck, shfmt, agent-skills, codex, claude-code, ai-coding - -Author Name: Mikhail Kuznetsov - -Website or Twitter: https://github.com/posidoni - -Email: maintainer email required at submission time - -Notes: - -- The repo uses the open Agent Skills layout: each skill is a directory with `SKILL.md`. -- The repo includes Codex plugin metadata in `.codex-plugin/plugin.json`. -- The repo includes Claude Code plugin metadata in `.claude-plugin/plugin.json`. -- The skill bodies are backed by cited references and runnable examples. diff --git a/registry/awesome-codex-plugins-listing.md b/registry/awesome-codex-plugins-listing.md deleted file mode 100644 index cc4f930..0000000 --- a/registry/awesome-codex-plugins-listing.md +++ /dev/null @@ -1,17 +0,0 @@ -# Awesome Codex Plugins Listing Draft - -Target: https://github.com/hashgraph-online/awesome-codex-plugins - -Suggested category: Community Plugins / Developer Tools - -Suggested listing: - -```md -- [Shell Skill Kit](https://github.com/posidoni/shell-skill) - Test-enforced Agent Skills for safe Bash, Zsh, POSIX sh, Nushell, shebangs, streams, and generated-script review. Ships portable `SKILL.md` folders plus Codex and Claude Code plugin metadata. -``` - -Submission note: - -- The target list currently requires its scanner gate for submitted plugins. -- This repository intentionally keeps GitHub Actions lean; add that scanner only - if the maintainer chooses to submit the listing PR. From edcdf9d9642810e8a89f38f15f4216d9320a5f25 Mon Sep 17 00:00:00 2001 From: posidoni <89808617+posidoni@users.noreply.github.com> Date: Sun, 19 Jul 2026 17:29:04 +0400 Subject: [PATCH 4/9] feat!: collapse seven skills into one router skill Seven sibling skills competed for the same request and none carried a trigger-first description, so the kit went unused while agents wrote unsafe shell. One 'shell' skill now routes into reference/ on demand. Adds reference/pipelines.md covering the awk/sed replacement table, the non-portable sed -i, and interpreter selection (macOS /bin/bash is 3.2). BREAKING: shell-skill:bash and the other six are now shell-skill:shell. --- .agents/skills/bash | 1 - .agents/skills/nushell | 1 - .agents/skills/posix-sh | 1 - .agents/skills/shebang | 1 - .agents/skills/shell | 1 + .agents/skills/shell-standards | 1 - .agents/skills/streams | 1 - .agents/skills/zsh | 1 - .claude-plugin/marketplace.json | 2 +- .claude-plugin/plugin.json | 2 +- .codex-plugin/plugin.json | 2 +- CHANGELOG.md | 30 ++++++ README.md | 28 +++-- llms.txt | 8 +- reference/pipelines.md | 123 ++++++++++++++++++++++ skills/bash/SKILL.md | 36 ------- skills/bash/agents/openai.yaml | 8 -- skills/nushell/SKILL.md | 40 ------- skills/nushell/agents/openai.yaml | 8 -- skills/posix-sh/SKILL.md | 43 -------- skills/posix-sh/agents/openai.yaml | 8 -- skills/shebang/SKILL.md | 40 ------- skills/shebang/agents/openai.yaml | 8 -- skills/shell-standards/SKILL.md | 49 --------- skills/shell-standards/agents/openai.yaml | 8 -- skills/shell/SKILL.md | 96 +++++++++++++++++ skills/shell/agents/openai.yaml | 8 ++ skills/streams/SKILL.md | 34 ------ skills/streams/agents/openai.yaml | 8 -- skills/zsh/SKILL.md | 43 -------- skills/zsh/agents/openai.yaml | 8 -- 31 files changed, 279 insertions(+), 369 deletions(-) delete mode 120000 .agents/skills/bash delete mode 120000 .agents/skills/nushell delete mode 120000 .agents/skills/posix-sh delete mode 120000 .agents/skills/shebang create mode 120000 .agents/skills/shell delete mode 120000 .agents/skills/shell-standards delete mode 120000 .agents/skills/streams delete mode 120000 .agents/skills/zsh create mode 100644 reference/pipelines.md delete mode 100644 skills/bash/SKILL.md delete mode 100644 skills/bash/agents/openai.yaml delete mode 100644 skills/nushell/SKILL.md delete mode 100644 skills/nushell/agents/openai.yaml delete mode 100644 skills/posix-sh/SKILL.md delete mode 100644 skills/posix-sh/agents/openai.yaml delete mode 100644 skills/shebang/SKILL.md delete mode 100644 skills/shebang/agents/openai.yaml delete mode 100644 skills/shell-standards/SKILL.md delete mode 100644 skills/shell-standards/agents/openai.yaml create mode 100644 skills/shell/SKILL.md create mode 100644 skills/shell/agents/openai.yaml delete mode 100644 skills/streams/SKILL.md delete mode 100644 skills/streams/agents/openai.yaml delete mode 100644 skills/zsh/SKILL.md delete mode 100644 skills/zsh/agents/openai.yaml diff --git a/.agents/skills/bash b/.agents/skills/bash deleted file mode 120000 index 8959685..0000000 --- a/.agents/skills/bash +++ /dev/null @@ -1 +0,0 @@ -../../skills/bash \ No newline at end of file diff --git a/.agents/skills/nushell b/.agents/skills/nushell deleted file mode 120000 index 65b9f11..0000000 --- a/.agents/skills/nushell +++ /dev/null @@ -1 +0,0 @@ -../../skills/nushell \ No newline at end of file diff --git a/.agents/skills/posix-sh b/.agents/skills/posix-sh deleted file mode 120000 index fa2ec24..0000000 --- a/.agents/skills/posix-sh +++ /dev/null @@ -1 +0,0 @@ -../../skills/posix-sh \ No newline at end of file diff --git a/.agents/skills/shebang b/.agents/skills/shebang deleted file mode 120000 index 67fc0ce..0000000 --- a/.agents/skills/shebang +++ /dev/null @@ -1 +0,0 @@ -../../skills/shebang \ No newline at end of file diff --git a/.agents/skills/shell b/.agents/skills/shell new file mode 120000 index 0000000..3244a71 --- /dev/null +++ b/.agents/skills/shell @@ -0,0 +1 @@ +../../skills/shell \ No newline at end of file diff --git a/.agents/skills/shell-standards b/.agents/skills/shell-standards deleted file mode 120000 index 9accb86..0000000 --- a/.agents/skills/shell-standards +++ /dev/null @@ -1 +0,0 @@ -../../skills/shell-standards \ No newline at end of file diff --git a/.agents/skills/streams b/.agents/skills/streams deleted file mode 120000 index 003d646..0000000 --- a/.agents/skills/streams +++ /dev/null @@ -1 +0,0 @@ -../../skills/streams \ No newline at end of file diff --git a/.agents/skills/zsh b/.agents/skills/zsh deleted file mode 120000 index 5838056..0000000 --- a/.agents/skills/zsh +++ /dev/null @@ -1 +0,0 @@ -../../skills/zsh \ No newline at end of file diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 0ab0844..259f560 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -10,7 +10,7 @@ "source": "./", "description": "Shell Skill Kit: safe shell rules for AI coding agents.", "category": "development", - "version": "0.2.0" + "version": "0.3.0" } ] } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 0002f09..906088d 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "shell-skill", - "version": "0.2.0", + "version": "0.3.0", "description": "Shell Skill Kit: test-enforced skills for safe Bash, Zsh, POSIX sh, and Nushell.", "author": { "name": "Mikhail Kuznetsov", diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 9b02f5a..2cbec33 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "shell-skill", - "version": "0.2.0", + "version": "0.3.0", "description": "Shell Skill Kit: test-enforced Agent Skills for safe Bash, Zsh, POSIX sh, and Nushell.", "author": { "name": "Mikhail Kuznetsov", diff --git a/CHANGELOG.md b/CHANGELOG.md index ffeff05..6347e0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,3 +69,33 @@ Initial public release. [Unreleased]: https://github.com/posidoni/shell-skill/compare/v0.2.0...HEAD [0.2.0]: https://github.com/posidoni/shell-skill/compare/v0.1.0...v0.2.0 [0.1.0]: https://github.com/posidoni/shell-skill/releases/tag/v0.1.0 + +## 0.3.0 + +### Changed — breaking + +- **Seven skills collapsed into one.** `bash`, `zsh`, `posix-sh`, `nushell`, + `shebang`, `streams` and `shell-standards` are no longer separate skills; there is + now a single `shell` skill that routes into `reference/` on demand. Anything + referencing `shell-skill:bash` (or the other six) must now use `shell-skill:shell`. + + Why: seven sibling skills competed for the same request, and an agent had to choose + a dialect *before* knowing which shell it was about to write — so in practice it + chose none. The `reference/` files, which held the real content, are unchanged. + +- **Descriptions are trigger-first.** Each old description opened with what the skill + *contained* ("Bash-specific practices beyond the shared standards — error + handling..."), and buried the activation cue at the end or omitted it. Skill + selection weights the opening, so the kit sat unused while agents wrote unsafe + shell. The new description leads with when to load it. + +### Added + +- `reference/pipelines.md` — the missing guidance on **not** writing shell: a + replacement table (`jq`/`yq`/`sd`/`fd`/`rg`/`nu`/Python over `awk`+`sed`), why + `sed -i` has no portable invocation across BSD and GNU, when `awk` is still + correct, and worked rewrites of a fragile pipeline into Nushell and Python. +- Interpreter-selection guidance in the skill body: macOS `/bin/bash` is 3.2 and + lacks `mapfile`, associative arrays and `${var^^}`, so `#!/usr/bin/env bash` alone + is not a portability strategy. + diff --git a/README.md b/README.md index c4abba5..f874f6c 100644 --- a/README.md +++ b/README.md @@ -44,15 +44,22 @@ and reality drift, the build goes red. ## Skills -| Skill | Use it for | Reference | Examples | -|-------|------------|-----------|----------| -| [shell-standards](skills/shell-standards/SKILL.md) | strict mode, quoting, arrays, traps, `printf`, `[[ ]]` | [reference](reference/shell-standards.md) | [examples](examples/standards/) | -| [bash](skills/bash/SKILL.md) | Bash error handling, arrays, macOS/BSD portability | [reference](reference/bash.md) | [examples](examples/bash/) | -| [posix-sh](skills/posix-sh/SKILL.md) | real `/bin/sh`, dash, no arrays, no bashisms | [reference](reference/posix-sh.md) | [examples](examples/posix-sh/) | -| [zsh](skills/zsh/SKILL.md) | zsh functions, options, globbing, no-fork performance | [reference](reference/zsh.md) | prose | -| [nushell](skills/nushell/SKILL.md) | structured pipelines, parse-time config, typed commands | [reference](reference/nushell.md) | [examples](examples/nushell/) | -| [shebang](skills/shebang/SKILL.md) | `env`, `env -S`, interpreter paths, dialect choice | [reference](reference/shebang.md) | [examples](examples/shebang/) | -| [streams](skills/streams/SKILL.md) | stdin/stdout/stderr, redirection order, here-docs | [reference](reference/streams.md) | [examples](examples/streams/) | +| Skill | Use it for | +|-------|------------| +| [shell](skills/shell/SKILL.md) | **The single entry point.** Loads before you write any shell — picks the right tool first (often not shell), then applies the safety rules. | + +It routes into these on demand, so only what you need enters context: + +| Reference | Covers | Examples | +|-----------|--------|----------| +| [pipelines](reference/pipelines.md) | replacing `awk`/`sed`/`cut`, BSD-vs-GNU `sed -i`, worked rewrites | prose | +| [shell-standards](reference/shell-standards.md) | strict mode, quoting, arrays, traps, `printf`, `[[ ]]` | [examples](examples/standards/) | +| [bash](reference/bash.md) | Bash error handling, arrays, macOS/BSD portability, bash-3.2 traps | [examples](examples/bash/) | +| [posix-sh](reference/posix-sh.md) | real `/bin/sh`, dash, no arrays, no bashisms | [examples](examples/posix-sh/) | +| [zsh](reference/zsh.md) | zsh functions, options, globbing, no-fork performance | prose | +| [nushell](reference/nushell.md) | structured pipelines, parse-time config, typed commands | [examples](examples/nushell/) | +| [shebang](reference/shebang.md) | `env`, `env -S`, interpreter paths, dialect choice | [examples](examples/shebang/) | +| [streams](reference/streams.md) | stdin/stdout/stderr, redirection order, exit codes | [examples](examples/streams/) | ShellCheck and shfmt do not support zsh, so zsh guidance is prose plus `zsh -n` syntax checking. For portable, lintable scripts, prefer Bash. @@ -70,8 +77,7 @@ Portable skill clients: ```sh git clone https://github.com/posidoni/shell-skill -ln -s "$PWD/shell-skill/skills/bash" "$YOUR_SKILLS_DIR/bash" -ln -s "$PWD/shell-skill/skills/shell-standards" "$YOUR_SKILLS_DIR/shell-standards" +ln -s "$PWD/shell-skill/skills/shell" "$YOUR_SKILLS_DIR/shell" ``` Local development toolchain on macOS: diff --git a/llms.txt b/llms.txt index dd4f8d5..2bbfc1a 100644 --- a/llms.txt +++ b/llms.txt @@ -21,13 +21,7 @@ Package name: shell-skill ## Portable Skills - Codex repo-scope symlinks: [.agents/skills/](.agents/skills/) -- [skills/shell-standards/SKILL.md](skills/shell-standards/SKILL.md): baseline Bash safety rules. -- [skills/shebang/SKILL.md](skills/shebang/SKILL.md): interpreter-line guidance. -- [skills/streams/SKILL.md](skills/streams/SKILL.md): stdin, stdout, stderr, and redirection. -- [skills/bash/SKILL.md](skills/bash/SKILL.md): Bash-specific safety and portability. -- [skills/posix-sh/SKILL.md](skills/posix-sh/SKILL.md): POSIX sh portability. -- [skills/zsh/SKILL.md](skills/zsh/SKILL.md): zsh correctness and performance. -- [skills/nushell/SKILL.md](skills/nushell/SKILL.md): Nushell structured scripting. +- [skills/shell/SKILL.md](skills/shell/SKILL.md): the single entry point — tool selection (jq/yq/sd/nu/Python over awk+sed), interpreter choice, the Bash non-negotiables, then routes into reference/ per dialect. ## References diff --git a/reference/pipelines.md b/reference/pipelines.md new file mode 100644 index 0000000..6cfe943 --- /dev/null +++ b/reference/pipelines.md @@ -0,0 +1,123 @@ +# Pipelines and text processing + +The most common shell mistake is not a quoting bug. It is reaching for +`awk`/`sed`/`cut` when a dedicated tool or a ten-line script would be shorter, +portable, and readable. + +## Replacement table + +| Task | Reach for | Instead of | +| --- | --- | --- | +| Read a field from JSON | `jq -r .field` | `grep`/`sed`/`cut` on JSON | +| Reshape JSON | `jq` | `awk` | +| Read or edit YAML / TOML / XML | `yq` | any regex | +| Substitute in a file | `sd 'from' 'to' file` | `sed -i 's/from/to/'` | +| Find files | `fd pattern` | `find . -name '*pattern*'` | +| Search contents | `rg pattern` | `grep -r pattern .` | +| Files → sizes → sorted | `nu` | `du` + `sort` + `awk` | +| Case / encoding / hashing of a string | `sttr` | `tr` / `openssl` one-liners | +| Column from a *fixed* delimiter | `cut -d, -f2` | `awk -F, '{print $2}'` | +| Anything with logic, state, or arithmetic | Python / `bun` .ts | a pipeline | + +## Why `sed -i` specifically + +`sed -i` is the single least portable common invocation: + +```bash +sed -i 's/a/b/' f # GNU: edits in place +sed -i 's/a/b/' f # BSD/macOS: error — -i needs an argument +sed -i '' 's/a/b/' f # BSD/macOS: correct +sed -i '' 's/a/b/' f # GNU: creates a file literally named '' +``` + +There is no invocation that works on both. Scripts that "work on my machine" and +corrupt files in CI usually contain exactly this line. `sd` takes real regex, needs no +delimiter escaping, and behaves identically everywhere: + +```bash +sd 'from' 'to' file.txt +sd -p 'from' 'to' file.txt # preview +``` + +## When awk is still right + +`awk` is fine for exactly one thing: selecting or summing a column of +whitespace-delimited output. + +```bash +awk '{print $2}' # fine +awk '{s+=$1} END {print s}' # fine +``` + +The moment it grows a `BEGIN` block, a second pattern, an array, or a regex with +capture groups, it has become a program written in a language nobody on the team +reads. Move it to Python or a `bun` script. + +## Structured data deserves a structured tool + +Parsing `du`, `ps`, or `ls` output with `awk` re-derives fields the OS already +returned as data. Nushell keeps them typed: + +```nu +ls **/*.log | where size > 10mb | sort-by size --reverse | first 10 +ps | where cpu > 10 | select pid name cpu +``` + +The Python equivalent, when logic is involved: + +```python +from pathlib import Path +big = sorted( + (p for p in Path(".").rglob("*.log") if p.stat().st_size > 10 * 1024**2), + key=lambda p: p.stat().st_size, reverse=True, +) +``` + +Both beat `du -sk * | sort -rn | head | awk '{print $2}'`, which breaks on the first +filename containing a space. + +## Worked rewrite + +Fragile — nested substitutions, unquoted expansion, `echo` as a header, breaks on +spaces in paths: + +```bash +echo "=== big dirs ===" +du -sh $(find . -type d -name node_modules -prune -print) | sort -rh | head +echo "total: $(du -sh $(find . -type d -name node_modules -prune -print) | tail -1)" +``` + +Two scans, two nested `$( )`, word-split file list, and `sort -rh` is GNU-only. + +Nushell — one pass, typed, no parsing: + +```nu +glob **/node_modules --no-file +| each {|p| {size: (du $p | get 0.apparent), path: $p} } +| sort-by size --reverse +| first 10 +``` + +Python when the result feeds further logic: + +```python +import os +from pathlib import Path + +def tree_size(root: Path) -> int: + return sum(f.stat().st_size for f in root.rglob("*") if f.is_file()) + +trees = [Path(r) / d for r, dirs, _ in os.walk(".") + for d in dirs if d == "node_modules"] +for t in sorted(trees, key=tree_size, reverse=True)[:10]: + print(f"{tree_size(t) / 1024**2:8.1f} MB {t}") +``` + +## If it must be a pipeline + +- One stage is fine. Two is usually fine. Three is a script. +- `set -o pipefail`, or a failure mid-pipe is invisible. +- Quote every expansion; a filename with a space is not an edge case. +- `find -print0 | xargs -0`, never bare `find | xargs`. +- Prefer `while IFS= read -r line` over `for line in $(cmd)`. +- Check `${PIPESTATUS[@]}` when you need to know *which* stage failed. diff --git a/skills/bash/SKILL.md b/skills/bash/SKILL.md deleted file mode 100644 index f719ec1..0000000 --- a/skills/bash/SKILL.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -name: bash -description: >- - Bash-specific practices beyond the shared standards — error handling (pipefail, - trap ERR), macOS/BSD-vs-GNU portability, arrays and mapfile, and safe temp - files. Use when writing or reviewing Bash scripts that must be robust and - portable, or when a script behaves differently on macOS than on Linux. ---- - -# Bash - -Builds on [`skills/shell-standards`](../shell-standards/). Depth and citations in -[`reference/bash.md`](../../reference/bash.md); runnable pairs in -[`examples/bash/`](../../examples/bash/). - -## Key points - -| Topic | Do | Avoid | -|-------|----|-------| -| Arrays | `mapfile -t arr < file` | `arr=($(cat file))` (`SC2207`) | -| Array expansion | `"${arr[@]}"` | bare `$arr` = first element only (`SC2128`) | -| Forwarding args | `"$@"` | unquoted `$@` (`SC2068`) | -| Errors | `trap ... ERR`, explicit checks | `\|\| true` masking | -| Portability | assert `BASH_VERSINFO`; temp-file `sed` | assuming GNU coreutils / Bash 4 | - -## Portability - -macOS ships Bash 3.2 and BSD userland. `mapfile`, `readarray`, and -`declare -A` are Bash 4+; `sed -i` and `readlink -f` differ from GNU. If you rely -on Bash 4 features, assert the version early and exit with a clear message. See -`reference/bash.md` for the full compatibility table. - -## Verify - -`task ci` runs `shellcheck --severity=warning` and the bats suite over these -examples. The SC codes in the table map one-to-one to the anti-patterns. diff --git a/skills/bash/agents/openai.yaml b/skills/bash/agents/openai.yaml deleted file mode 100644 index 072a038..0000000 --- a/skills/bash/agents/openai.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# yaml-language-server: $schema=../../../schemas/openai-skill-metadata.schema.json -interface: - display_name: "Bash Safety" - short_description: "Robust Bash scripts and portability" - brand_color: "#2563EB" - default_prompt: "Use $bash to write or review a robust portable Bash script." -policy: - allow_implicit_invocation: true diff --git a/skills/nushell/SKILL.md b/skills/nushell/SKILL.md deleted file mode 100644 index b395ff0..0000000 --- a/skills/nushell/SKILL.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -name: nushell -description: >- - Nushell practices — the parse-time-vs-runtime model, structured data over text, - immutability (let/mut/const), typed custom commands and def --env, expression - control flow, config/env and modules, error handling, external commands with ^ - and complete, and version-pinning. Use when writing Nushell scripts or config, - or when Nushell fails to start or a script breaks after a version upgrade. ---- - -# Nushell - -Nushell passes typed, structured values (records, tables, lists), not text. -Depth and citations in [`reference/nushell.md`](../../reference/nushell.md); -parse-clean fragments in [`examples/nushell/`](../../examples/nushell/); a runtime -demo of the startup pitfall in -[`tests/nushell-startup-demo.sh`](../../tests/nushell-startup-demo.sh). - -## Rules at a glance - -| Rule | Why | -|------|-----| -| Parse the whole file, then run it | no `eval`; runtime cannot change what was parsed | -| `source`/`use`/`overlay use` need a **parse-time `const`** path | resolved before any `let`/`$env` exists | -| A runtime `if (path exists)` can't guard a `source` | path is resolved at parse time regardless | -| Structured data, not text: `get`/`where`/`select` | pipelines carry typed values, no word-splitting | -| Write with `save`, not `>` | `>` is the greater-than operator | -| Prefer `let`; `mut` only to reassign; `const` for parse time | immutability enables parallel/streaming | -| Type command params; declare `in -> out` signatures | checked at parse time, self-documenting | -| `def --env` when a command must change env or `cd` | `$env` is block-scoped otherwise | -| `if`/`match` are expressions; iterate with `each`/`where` | return values; far faster than `for`/`while` | -| `^cmd` for externals + `\| complete` for exit code | quote args yourself; branch on real status | -| `try`/`catch`, `error make`, optional `get -o`/`?` + `default` | recover instead of aborting | -| Pin/declare the target Nushell version | 0.x ships breaking changes every ~6 weeks | - -## Verify - -`task nushell` runs `nu --ide-check` on every `*.nu` and fails on any parse or -type error. A failing script cannot be committed, so demonstrate *bad* runtime -behaviour in prose or a hermetic bash demo, not in a `*.nu` file. diff --git a/skills/nushell/agents/openai.yaml b/skills/nushell/agents/openai.yaml deleted file mode 100644 index 3aa28b7..0000000 --- a/skills/nushell/agents/openai.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# yaml-language-server: $schema=../../../schemas/openai-skill-metadata.schema.json -interface: - display_name: "Nushell" - short_description: "Structured Nushell scripting" - brand_color: "#2563EB" - default_prompt: "Use $nushell to review a Nushell script or startup config." -policy: - allow_implicit_invocation: true diff --git a/skills/posix-sh/SKILL.md b/skills/posix-sh/SKILL.md deleted file mode 100644 index 60a3b77..0000000 --- a/skills/posix-sh/SKILL.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -name: posix-sh -description: >- - POSIX sh portability — no local, no arrays, [ ] not [[ ]], set -eu without - pipefail, and other bashisms that break under a real /bin/sh (dash on - Debian/Ubuntu, most container base images, curl | sh installers). Use when - a script's shebang is #!/bin/sh, when targeting a minimal or non-bash - runtime, or when ShellCheck reports an SC3xxx code. ---- - -# POSIX sh - -Builds on [`skills/shell-standards`](../shell-standards/). Depth and -citations in [`reference/posix-sh.md`](../../reference/posix-sh.md); runnable -pairs in [`examples/posix-sh/`](../../examples/posix-sh/). - -## Key points - -| Topic | Do | Avoid | -|-------|----|-------| -| Strict mode | `set -eu` | `set -o pipefail` (not POSIX) | -| Function-local state | capture via `$( )`, which already subshells | `local` (`SC3043`) | -| Lists | `set -- ...` / `"$@"` | `arr=(...)` (`SC3030`/`SC3054`) | -| Conditionals | `[ "$x" = y ]` | `[[ $x == y ]]` (`SC3010`) | -| Sourcing | `. ./lib.sh` | `source ./lib.sh` (`SC3046`) | -| Functions | `name() { ...; }` | `function name { ...; }` (`SC3045`) | - -## Why this matters - -`/bin/sh` is `dash` on Debian/Ubuntu, not `bash` — and most container base -images, install scripts (`curl | sh`), and anything with a literal -`#!/bin/sh` shebang run under a real POSIX shell with none of Bash's -extensions. Code that only ran under `bash` in development can fail — or -silently misbehave — the first time it runs as `/bin/sh`. - -## Verify - -`shellcheck` infers the POSIX dialect automatically from a `#!/bin/sh` -shebang (`SC3xxx` codes only fire there). `task ci` runs both ShellCheck and -`shfmt` over [`examples/posix-sh/`](../../examples/posix-sh/); note that -`*.bad.sh` files there are excluded from the shfmt gate because some -anti-patterns (arrays) are invalid POSIX syntax, not just bad style — shfmt -cannot parse them in POSIX mode at all. diff --git a/skills/posix-sh/agents/openai.yaml b/skills/posix-sh/agents/openai.yaml deleted file mode 100644 index 75c5887..0000000 --- a/skills/posix-sh/agents/openai.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# yaml-language-server: $schema=../../../schemas/openai-skill-metadata.schema.json -interface: - display_name: "POSIX sh" - short_description: "Portable POSIX sh patterns" - brand_color: "#2563EB" - default_prompt: "Use $posix-sh to make this script portable under /bin/sh." -policy: - allow_implicit_invocation: true diff --git a/skills/shebang/SKILL.md b/skills/shebang/SKILL.md deleted file mode 100644 index 48b805b..0000000 --- a/skills/shebang/SKILL.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -name: shebang -description: >- - How to write correct, portable shebang (#!) lines — prefer /usr/bin/env, - pass interpreter flags with env -S, keep the path absolute and the line short, - and match the dialect. Use when creating any executable script, choosing - between /usr/bin/env bash and /bin/bash, or diagnosing a "bad interpreter" - error. ---- - -# Shebang - -The `#!` line picks a script's interpreter. Depth, mechanism, and citations in -[`reference/shebang.md`](../../reference/shebang.md); runnable pairs in -[`examples/shebang/`](../../examples/shebang/). - -## Rules at a glance - -| Rule | Good | Bad | Caught by | -|------|------|-----|-----------| -| Prefer `env` for portable version | `#!/usr/bin/env bash` | `#!/bin/bash` | judgment | -| Pass flags with `env -S` | `#!/usr/bin/env -S bash -euo pipefail` | `#!/usr/bin/env bash -euo pipefail` | `SC2096` | -| Absolute interpreter path | `#!/usr/bin/env python3` | `#!bin/python3` | `SC2239` | -| `#!` first, LF endings, no BOM | `#!…` on line 1 | `#…`, CRLF, blank line | `SC1113`/`SC1017`/`SC1128` | -| Executable scripts need a shebang | present | missing | `SC2148` | -| Keep the line < 127 bytes | short | long `env -S` chains | kernel (silent truncation) | -| Pin absolute path when privileged | `#!/bin/sh` | `#!/usr/bin/env` (PATH-hijackable) | judgment | -| Match the dialect you write | `#!/usr/bin/env bash` + bashisms | `#!/bin/sh` + `[[ ]]` | runtime (dash) | - -## Why the kernel forces these - -The interpreter path is used verbatim (no `PATH` search, so it must be absolute), -everything after it is a **single** argument (hence `env -S` for flags), and the -line is length-limited and **silently truncated** past the kernel's buffer. See -the reference for `execve(2)` details. - -## Do not - -Polyglot/self-re-exec headers, `${ORIGIN}/`-relative shebangs, arbitrary programs -as interpreters, and relying on setuid — all fragile or non-standard. diff --git a/skills/shebang/agents/openai.yaml b/skills/shebang/agents/openai.yaml deleted file mode 100644 index 41265af..0000000 --- a/skills/shebang/agents/openai.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# yaml-language-server: $schema=../../../schemas/openai-skill-metadata.schema.json -interface: - display_name: "Shebang" - short_description: "Portable interpreter lines" - brand_color: "#2563EB" - default_prompt: "Use $shebang to choose the right interpreter line for this script." -policy: - allow_implicit_invocation: true diff --git a/skills/shell-standards/SKILL.md b/skills/shell-standards/SKILL.md deleted file mode 100644 index 5b995f1..0000000 --- a/skills/shell-standards/SKILL.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -name: shell-standards -description: >- - Baseline rules for safe, correct Bash — strict mode, quoting, [[ ]] over [ ], - $(...) over backticks, declare/assign separation, arrays, return-vs-exit, and - trap-based cleanup. Use when writing, reviewing, or debugging any Bash script, - or when deciding how to handle quoting, word-splitting, exit codes, or cleanup. ---- - -# Shell standards - -The non-negotiable baseline for writing Bash that does not silently corrupt data -or hide failures. Every rule is explained in depth, with citations, in -[`reference/shell-standards.md`](../../reference/shell-standards.md); the rules -that carry a ShellCheck code also have a runnable good/bad pair in -[`examples/standards/`](../../examples/standards/). - -## Rules at a glance - -| # | Rule | Why | Enforced by | -|---|------|-----|-------------| -| 1 | `set -euo pipefail` at the top | Fail fast; unset vars and broken pipes become errors | style guide | -| 2 | Quote every expansion: `"$var"`, `"$(cmd)"` | Prevents word-splitting and globbing | `SC2086` | -| 3 | `read -r`, never bare `read` | Bare `read` mangles backslashes | `SC2162` | -| 4 | Declare `local`, then assign | `local x="$(cmd)"` hides the command's exit status | `SC2155` | -| 5 | Arrays for lists; expand `"${arr[@]}"` | A string can't hold an element with spaces | `SC2206` | -| 6 | `[[ ... ]]`, not `[ ... ]` | `[[ ]]` doesn't split or glob its operands | `SC2292` | -| 7 | `return` from helpers, `exit` only in `main` | `exit` kills the caller's shell when sourced | style guide | -| 8 | `trap '...' EXIT` for cleanup | Releases temp files on every exit path | style guide | -| 9 | `printf`, not `echo` | Portable escapes; keep data out of the format | `SC2028`/`SC2059` | -| 10 | Brace expansions: `${var}` | Disambiguates `${v}_x`, arrays, defaults | `SC2250` | -| 11 | `readonly` constants; `command -v` not `which` | Immutability; portable existence check | style guide | -| 12 | Never parse `ls`; use a glob | Breaks on odd filenames | `SC2045` | - -## How to use - -- **Writing a script?** Start from rule 1, quote everything (rule 2), and reach - for arrays (rule 5) the moment you have a list of arguments. -- **Reviewing a script?** Run `shellcheck` at its **default** severity (this - repo's `.shellcheckrc` sets `enable=all`); the SC codes above map directly to - these rules. CI lints at `--severity=warning`, which catches the - higher-severity codes but filters out `SC2086`/`SC2162` (info) and `SC2292` - (style) — so review locally at the default level to see them all. -- **Formatting?** `shfmt` (configured in `.editorconfig`) enforces layout — it - even rewrites legacy backticks to `$(...)` for you, which is why there is no - runnable backticks example here. - -See also: [`skills/bash`](../bash/), [`skills/zsh`](../zsh/), and -[`skills/nushell`](../nushell/) for shell-specific guidance. diff --git a/skills/shell-standards/agents/openai.yaml b/skills/shell-standards/agents/openai.yaml deleted file mode 100644 index eb4c63d..0000000 --- a/skills/shell-standards/agents/openai.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# yaml-language-server: $schema=../../../schemas/openai-skill-metadata.schema.json -interface: - display_name: "Shell Standards" - short_description: "Baseline safe shell rules" - brand_color: "#2563EB" - default_prompt: "Use $shell-standards to review this Bash script against safe shell rules." -policy: - allow_implicit_invocation: true diff --git a/skills/shell/SKILL.md b/skills/shell/SKILL.md new file mode 100644 index 0000000..9e02d7d --- /dev/null +++ b/skills/shell/SKILL.md @@ -0,0 +1,96 @@ +--- +name: shell +description: >- + Use BEFORE writing or editing any shell command, script, pipeline, function, + Makefile recipe, Dockerfile RUN, CI run: block, cron entry, or .env file — in Bash, + Zsh, POSIX sh, or Nushell. Also use when a command grows past one stage, when + reaching for awk/sed/cut, when choosing a shebang, when handling stdout vs stderr + or exit codes, and when reviewing or debugging someone else's shell. Routes to the + right tool — often not shell at all — then applies the safety rules. +--- + +# Shell + +**Most "shell problems" are solved by not writing shell.** This skill decides that +first, then enforces the rules for the parts that genuinely are shell. + +## 1. Pick the tool before writing a line + +| The task | Use | Not | +| --- | --- | --- | +| JSON in, JSON out | `jq` | `grep`/`sed` on JSON | +| YAML / TOML / XML | `yq` | any regex | +| Substitute text in files | `sd` | `sed -i` (delimiter escaping, BSD-vs-GNU `-i`) | +| Find files | `fd` | `find` with `-exec` chains | +| Search contents | `rg` | `grep -r` | +| Query the OS for **structured** facts (sizes, processes, files) | `nu` | `du`/`ps` + `awk` | +| String case/encoding transforms | `sttr` | `tr`/`awk` | +| SQLite | `sqlite3` or a `bun` script | shelling out per row | +| Anything with a data structure, two stages of logic, or arithmetic | **Python or a `bun` .ts file** | a pipeline | + +**Hard stop:** if a pipeline needs three or more stages, or any `awk` beyond +`{print $1}`, stop and write a script file. A three-stage `awk`/`sed`/`cut` chain is +unreadable, unportable (BSD vs GNU), and silently wrong on unusual input. + +See [`reference/pipelines.md`](../../reference/pipelines.md) for the replacement +table, BSD-vs-GNU traps, and worked rewrites. + +## 2. Pick the interpreter deliberately + +Never write bare `bash` and hope. On macOS `/bin/bash` is **3.2** (2007, frozen over +GPLv3) — no `mapfile`, no associative arrays, no `${var^^}`. Homebrew's bash 5.x lives +at `/opt/homebrew/bin/bash`. + +- Portable script → `#!/usr/bin/env bash` **and** stay inside 3.2 features, or assert + the version at the top. +- Need bash 4+/5 features → require it explicitly, do not assume `env` finds it. +- Interactive-shell config → that is Zsh on macOS, not bash. +- Structured data → Nushell. + +Details and the `env -S` flag trick: [`reference/shebang.md`](../../reference/shebang.md). + +## 3. The non-negotiables (Bash) + +| # | Rule | Enforced by | +|---|------|-------------| +| 1 | `set -euo pipefail` first line of every script | style | +| 2 | Quote every expansion: `"$var"`, `"$(cmd)"` | `SC2086` | +| 3 | `read -r`, never bare `read` | `SC2162` | +| 4 | Declare `local`, then assign — `local x="$(cmd)"` hides the exit status | `SC2155` | +| 5 | Arrays for lists; expand `"${arr[@]}"` | `SC2206` | +| 6 | `[[ ... ]]`, not `[ ... ]` | `SC2292` | +| 7 | `return` in helpers; `exit` only in `main` | style | +| 8 | `trap '...' EXIT` for cleanup | style | +| 9 | `printf`, never `echo` — builtins disagree on `-n`, `-e`, backslashes | `SC2028`/`SC2059` | +| 10 | `${var}` braces | `SC2250` | +| 11 | `readonly` constants; `command -v`, not `which` | style | +| 12 | Never parse `ls`; use a glob | `SC2045` | +| 13 | No nested `$( $( ) )` — assign an intermediate variable | readability | + +Full rationale and citations: [`reference/shell-standards.md`](../../reference/shell-standards.md). + +## 4. Then the shell-specific reference + +Load only the one you are actually writing: + +- **Bash** — `pipefail` semantics, `trap ERR`, BSD-vs-GNU coreutils, bash-3.2 traps → + [`reference/bash.md`](../../reference/bash.md) +- **Zsh** — `emulate -L zsh`, no automatic word-splitting, glob qualifiers, startup + files → [`reference/zsh.md`](../../reference/zsh.md) +- **POSIX sh** — no `local`, no arrays, `[ ]` only, `set -eu` without `pipefail` → + [`reference/posix-sh.md`](../../reference/posix-sh.md) +- **Nushell** — parse-time vs runtime, `const` for `source`, structured pipelines, + `save` not `>`, `^cmd | complete` → [`reference/nushell.md`](../../reference/nushell.md) +- **Streams** — stdout is data, stderr is diagnostics, exit codes, signals → + [`reference/streams.md`](../../reference/streams.md) + +## 5. Verify before claiming it works + +```bash +shellcheck script.sh # default severity — CI's --severity=warning hides SC2086 +shfmt -d script.sh # layout, and rewrites backticks +nu --ide-check script.nu # parse + type errors for Nushell +``` + +`task lint` runs these across the repo. A script that has not been shellcheck'd at +default severity has not been checked. diff --git a/skills/shell/agents/openai.yaml b/skills/shell/agents/openai.yaml new file mode 100644 index 0000000..91204d3 --- /dev/null +++ b/skills/shell/agents/openai.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=../../../schemas/openai-skill-metadata.schema.json +interface: + display_name: "Shell" + short_description: "Pick the right tool, then write safe shell" + brand_color: "#2563EB" + default_prompt: "Use $shell before writing any shell command, script, or pipeline." +policy: + allow_implicit_invocation: true diff --git a/skills/streams/SKILL.md b/skills/streams/SKILL.md deleted file mode 100644 index 61aad33..0000000 --- a/skills/streams/SKILL.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -name: streams -description: >- - Correct use of stdin, stdout, and stderr — send diagnostics to stderr and data - to stdout, signal failure via the exit status, read input with while IFS= read - -r, order stderr duplication after file redirection, and use here-docs and - here-strings. Use when a script produces output, reads input, or redirects, - or when a pipe is polluted by error text. ---- - -# Streams - -fd 0 is stdin, fd 1 is stdout (data), fd 2 is stderr (diagnostics). Depth and -citations in [`reference/streams.md`](../../reference/streams.md); runnable pairs -in [`examples/streams/`](../../examples/streams/). - -## Rules at a glance - -| Rule | Why | Code | -|------|-----|------| -| Diagnostics and prompts to stderr (`>&2`); data to stdout | keeps stdout pipeable and parseable | review-only | -| Signal failure with a non-zero exit, not just a message | callers test the exit status | — | -| Read lines with `while IFS= read -r line` | preserves whitespace and backslashes | — | -| Don't loop over `$(cat file)` | it word-splits and globs | `SC2013` | -| `cmd >file 2>&1`, not `cmd 2>&1 >file` | `2>&1` copies stdout's target at that moment | `SC2069` | -| Quote the delimiter (`<<'EOF'`) to stop expansion | literal here-doc | — | -| Never read and write the same file in one pipeline | the target is truncated first | — | -| `$(...)` strips trailing newlines | guard with a sentinel when they matter | — | - -## Note - -ShellCheck catches `SC2013` and `SC2069`, but **not** a diagnostic sent to stdout -instead of stderr — that one is enforced by review. A green lint does not mean -the streams are right. diff --git a/skills/streams/agents/openai.yaml b/skills/streams/agents/openai.yaml deleted file mode 100644 index 9a853af..0000000 --- a/skills/streams/agents/openai.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# yaml-language-server: $schema=../../../schemas/openai-skill-metadata.schema.json -interface: - display_name: "Streams" - short_description: "Correct stdin stdout stderr use" - brand_color: "#2563EB" - default_prompt: "Use $streams to fix stdin, stdout, stderr, and redirection behavior." -policy: - allow_implicit_invocation: true diff --git a/skills/zsh/SKILL.md b/skills/zsh/SKILL.md deleted file mode 100644 index 136ad65..0000000 --- a/skills/zsh/SKILL.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -name: zsh -description: >- - Zsh scripting and performance practices — emulate -L zsh, safety options, no - automatic word-splitting, 1-indexed arrays, subprocess-free idioms ($TTY not - $(tty), ${f:h}/${f:t} not dirname/basename), zsh/parameter existence checks, - and glob qualifiers. Use when writing zsh functions, scripts, or a fast - interactive config, or when a zsh script behaves differently from bash. ---- - -# Zsh - -Depth and citations in [`reference/zsh.md`](../../reference/zsh.md). Two framing -facts: ShellCheck and shfmt do not support zsh (syntax-check with `zsh -n`), and -zsh's edge is doing work **without forking a subprocess**. - -## Robust scripting - -| Rule | Why | -|------|-----| -| `emulate -L zsh` in functions, `emulate -LR zsh` in scripts | reset options; a caller's `setopt` can't change your behaviour | -| `setopt err_return` (not `errexit` in sourced code), `no_unset`, `pipefail`, `warn_create_global` | fail fast without killing the shell; catch leaked globals | -| No auto word-splitting; split with `${=v}` / `${(s:X:)v}` / `${(f)v}` | unquoted `$var` stays one word (unlike Bash) | -| Arrays are 1-indexed: `$arr[1]`, `$#arr`, `arr+=(x)`, `"${(@)arr}"` | `${arr[0]}` is the wrong element | -| Quote for re-parse with `${(q)v}`; indirect with `${(P)name}` | safe `eval`/`ssh`; no `${!name}` | -| `zparseopts -D -E -F` | robust option parsing | - -## Performance (romkatv): never fork in a hot path - -| Rule | Why | -|------|-----| -| `$TTY`, never `$(tty)` | fork cost, and `$(tty)` reads fd 0 — wrong under redirected stdin / instant prompt; `$TTY` is the shell's controlling terminal | -| `${f:h}` / `${f:t}` / `${f:r}` / `${f:e}` / `${f:A}` not `dirname`/`basename`/`realpath` | in-process string ops, no fork | -| `$EPOCHREALTIME` / `$EPOCHSECONDS` not `$(date)` | `zmodload zsh/datetime` | -| `${${(%):-%x}:A:h}` for a script's own dir; `${(%):-%N}` / `$funcstack` | no `readlink` fork | -| `(( $+commands[x] ))` via `zsh/parameter` | existence check with no `command -v` fork | -| `[[ ]]` / `(( ))` not `[ ]`/`test`/`expr`; `$( Date: Sun, 19 Jul 2026 17:30:48 +0400 Subject: [PATCH 5/9] docs: situational triggers in skill description; nu -n invocation guidance Description now fires on the act (about to type awk, parsing du/ps output, nesting $(...)) rather than on an explicit request for shell help. Adds nu invocation forms to reference/nushell.md: agent and CI one-liners need -n/--no-config-file, or they inherit the user's config.nu and env.nu and stop being deterministic. --- reference/nushell.md | 26 ++++++++++++++++++++++++++ skills/shell/SKILL.md | 17 +++++++++++------ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/reference/nushell.md b/reference/nushell.md index d5c749e..396bc4a 100644 --- a/reference/nushell.md +++ b/reference/nushell.md @@ -236,3 +236,29 @@ the script — ideal for CI and pre-commit. This repo's `task nushell` See [`skills/nushell`](../skills/nushell/) for the agent-facing summary and [`examples/nushell/`](../examples/nushell/) for parse-clean fragments. + +## Invoking `nu` from an agent or a script + +Use `-n` (`--no-config-file`). Without it, `nu -c` loads the user's `config.nu` and +`env.nu`, so the command inherits their aliases, `$env`, and any parse-time `source` +— which makes an agent-run command non-deterministic and hostage to a config it did +not write. That is the same parse-time failure documented above, arriving through the +back door. + +| Form | Use for | +| --- | --- | +| `nu -n -c '...'` | **agent/CI one-liners** — no config, no env, deterministic | +| `nu -n script.nu` | running a script file without user config | +| `nu --stdin -c '...'` | piping data in; read it via `$in` | +| `nu --no-newline -c '...'` | output destined for command substitution | +| `#!/usr/bin/env nu` | a standalone executable script | +| `nu -l` / `nu -i` | login / interactive shell — a human's shell, not a script's | + +Startup cost is small when the config is lean (~35ms vs ~25ms on a tuned machine), so +determinism, not speed, is the reason. + +Check a script before shipping it: + +```sh +nu --ide-check 0 script.nu # parse + type errors, no execution +``` diff --git a/skills/shell/SKILL.md b/skills/shell/SKILL.md index 9e02d7d..f93fe2a 100644 --- a/skills/shell/SKILL.md +++ b/skills/shell/SKILL.md @@ -1,12 +1,17 @@ --- name: shell description: >- - Use BEFORE writing or editing any shell command, script, pipeline, function, - Makefile recipe, Dockerfile RUN, CI run: block, cron entry, or .env file — in Bash, - Zsh, POSIX sh, or Nushell. Also use when a command grows past one stage, when - reaching for awk/sed/cut, when choosing a shebang, when handling stdout vs stderr - or exit codes, and when reviewing or debugging someone else's shell. Routes to the - right tool — often not shell at all — then applies the safety rules. + Load before running or writing ANY shell — including throwaway one-liners you + intend to run yourself, not just scripts you save. Trigger on the act, not the + request: you are about to type awk, sed, cut, or a second pipe; you are parsing + du/ps/ls/find output; a command needs a loop, a conditional, or arithmetic; you are + writing $(...) inside another $(...); you need a file list, sizes, or process info; + you are choosing a shebang or an interpreter; a command exceeds one line or will + outlive this session; output must be machine-read, or must separate data from + diagnostics; you are editing a Makefile recipe, Dockerfile RUN, CI run: block, cron + entry, or .env; or you are reviewing or debugging shell someone else wrote. Answers + "should this be shell at all?" first — usually jq, yq, sd, nu, Python or bun — then + applies the Bash/Zsh/POSIX/Nushell safety rules to whatever survives that question. --- # Shell From 6adf629075febc8ae891b377aab88494fc51cc7e Mon Sep 17 00:00:00 2001 From: posidoni <89808617+posidoni@users.noreply.github.com> Date: Sun, 19 Jul 2026 17:32:47 +0400 Subject: [PATCH 6/9] docs: check tools instead of recalling flags; cached CLI cheatsheets Adds the escalation ladder (--help -> man -> context7 -> web) to the skill, and reference/cli-cheatsheets.md with defaults that silently do the wrong thing -- notably fd/rg honouring .gitignore, where -H fixes only half the problem and -I is the other half. Verified against installed versions. --- README.md | 1 + reference/cli-cheatsheets.md | 146 +++++++++++++++++++++++++++++++++++ skills/shell/SKILL.md | 22 ++++++ 3 files changed, 169 insertions(+) create mode 100644 reference/cli-cheatsheets.md diff --git a/README.md b/README.md index f874f6c..3a6c5fc 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ It routes into these on demand, so only what you need enters context: | Reference | Covers | Examples | |-----------|--------|----------| +| [cli-cheatsheets](reference/cli-cheatsheets.md) | how to check a tool (`--help` → `man` → context7), plus cached gotchas for fd/rg/sd/nu/jq/yq | prose | | [pipelines](reference/pipelines.md) | replacing `awk`/`sed`/`cut`, BSD-vs-GNU `sed -i`, worked rewrites | prose | | [shell-standards](reference/shell-standards.md) | strict mode, quoting, arrays, traps, `printf`, `[[ ]]` | [examples](examples/standards/) | | [bash](reference/bash.md) | Bash error handling, arrays, macOS/BSD portability, bash-3.2 traps | [examples](examples/bash/) | diff --git a/reference/cli-cheatsheets.md b/reference/cli-cheatsheets.md new file mode 100644 index 0000000..e427019 --- /dev/null +++ b/reference/cli-cheatsheets.md @@ -0,0 +1,146 @@ +# CLI cheatsheets + +Cached essentials for the tools this kit sends you to. **Verified against installed +versions, not recalled** — the versions are recorded so a future reader knows when to +re-check. + +The point of this file is not to replace `--help`. It is to record the handful of +**defaults that silently do the wrong thing**, which `--help` states plainly but +nobody reads until after they have been bitten. + +Verified 2026-07-19: `sd 1.0.0` · `rg 15.2.0` · `fd 10.4.2` · `nu 0.114.1` · +`jq 1.7.1` · `yq 4.53.3`. + +## How to check anything here + +```sh + --help # first, always — matches the installed version +man -w # is there a man page at all? + --version # before trusting any remembered flag +``` + +`nu` and `sttr` ship **no** man page. `sd`, `rg`, `fd`, `jq`, `yq` do. + +--- + +## fd — find files + +```sh +fd pattern # regex by default +fd -g '*.log' # glob mode +fd -e db # by extension +fd -t f / -t d / -t l # files / dirs / symlinks +fd -x cmd {} # execute per result (parallel) +fd -X cmd # execute once with all results +``` + +**The gotcha that costs you an hour:** `fd` skips hidden files **and** honours +`.gitignore`, `.ignore`, `.fdignore` and the global ignore file. `-H` only fixes the +first half. + +```sh +fd -H -e db # 0 results — files were gitignored +fd -H -I -e db # 3 results ← -I / --no-ignore is the other half +``` + +A `.db` or `.env` you are hunting is *usually* gitignored, which is precisely why you +are hunting it. Reach for `-HI` when searching for state and data files. + +## rg — search contents + +```sh +rg pattern # recursive, respects .gitignore, skips hidden +rg -HI pattern # ...unless you say otherwise (same trap as fd) +rg -l pattern # filenames only +rg -o pattern # only the matched part +rg -U 'a\n.*b' # -U/--multiline: patterns may cross lines +rg -t py pattern # restrict by file type +rg --json pattern # structured output, parseable +rg -A3 -B1 pattern # context after / before +rg -c pattern # count per file +``` + +Same ignore semantics as `fd`. `rg --json` beats parsing `rg` text output. + +## sd — substitute (use instead of sed) + +```sh +sd 'find' 'replace' file.txt # in place, by default +sd -p 'find' 'replace' file.txt # -p/--preview: show, do not write +sd -F 'literal' 'replace' file # -F: no regex, treat as fixed string +sd -n 2 'find' 'replace' file # limit replacements per file +cat f | sd 'a' 'b' # reads STDIN when no file given +``` + +Captures are `$1`, `$2` — not `\1`. **`sd` writes in place by default**, so use `-p` +first on anything you cannot regenerate. + +Why `sd` and not `sed`: there is no `sed -i` invocation portable across BSD and GNU. +`sed -i 's/a/b/' f` works on GNU and errors on macOS; `sed -i '' 's/a/b/' f` works on +macOS and creates a file named `''` on GNU. `sd` behaves identically everywhere. + +## nu — structured shell + +```sh +nu -n -c '...' # agent/CI: -n = --no-config-file, deterministic +nu --stdin -c 'print $in' +nu --no-newline -c '...' # for command substitution +nu --ide-check 0 script.nu # parse + type check, no execution +``` + +Without `-n`, `nu -c` loads the user's `config.nu`/`env.nu` and inherits their +aliases, `$env`, and any parse-time `source`. See +[`nushell.md`](nushell.md) for the language itself. + +Useful shapes: + +```nu +ls **/*.log | where size > 10mb | sort-by size --reverse | first 10 +glob **/node_modules --no-file | each {|p| {path: $p, size: (du $p | get 0.apparent)} } +open data.json | get items | where active | select name id +ps | where cpu > 10 | select pid name cpu +^git status --short | complete | get stdout # ^ = external, complete = exit code +``` + +`$nu` has `home-path`? **No** — that was a wrong guess that cost two failed attempts. +Check `$nu | columns` before using any `$nu.*` field. + +## jq — JSON + +```sh +jq -r .field # -r: raw, no surrounding quotes +jq -c . # compact, one line — good for piping +jq -e '.x' # exit non-zero if null/false — usable in `if` +jq -s '.' # slurp multiple inputs into one array +jq --arg k "$v" '.[$k]' # pass a shell value in SAFELY, never interpolate +jq 'to_entries[] | "\(.key)=\(.value)"' +``` + +Never build a `jq` program by string-interpolating shell variables; use `--arg` / +`--argjson`. + +## yq — YAML / TOML / XML + +Mike Farah's Go `yq` (v4), not the Python wrapper — the syntaxes differ. + +```sh +yq '.field' file.yaml +yq -i '.version = "0.3.0"' file.yaml # -i: in place +yq -o json '.' file.yaml # convert +yq -p toml '.tool' file.toml # -p: input format +yq ea '. as $i ireduce ({}; . * $i)' *.yaml # merge multiple docs +``` + +## sttr — string transforms + +No `--help` and no man page in this install; run `sttr` with no arguments for its +interactive picker, or `sttr