From e68c8a0b327395f8d58eb9ab09332eff6f9cc071 Mon Sep 17 00:00:00 2001 From: Sal Date: Sat, 15 Aug 2026 12:37:56 +0100 Subject: [PATCH 1/2] feat(zsh): govern plugin standard reviews --- .../zsh-plugin-standard-reviewer.agent.md | 29 +++- .github/instruction-surfaces.json | 58 ++++++- .../documentation.instructions.md | 9 ++ ...sh-plugin-standard-aliases.instructions.md | 17 ++ .../zsh-plugin-standard.instructions.md | 58 +++++++ .github/skills/create-readme/SKILL.md | 4 +- .github/skills/new-zsh-plugin/SKILL.md | 17 +- .../templates/plugin.plugin.zsh | 22 +-- AGENTS.md | 23 +-- PATTERNS.md | 61 +++++--- runbooks/instruction-update.md | 19 +++ runbooks/new-repository.md | 11 +- runbooks/recurring-operations.md | 96 +++++++++++- scripts/test_validate_agent_policy.py | 147 +++++++++++++++++- templates/readme/zsh-plugin.md | 11 +- 15 files changed, 512 insertions(+), 70 deletions(-) create mode 100644 .github/instructions/zsh-plugin-standard-aliases.instructions.md create mode 100644 .github/instructions/zsh-plugin-standard.instructions.md diff --git a/.github/agents/zsh-plugin-standard-reviewer.agent.md b/.github/agents/zsh-plugin-standard-reviewer.agent.md index 7071ef010..5cc51d901 100644 --- a/.github/agents/zsh-plugin-standard-reviewer.agent.md +++ b/.github/agents/zsh-plugin-standard-reviewer.agent.md @@ -4,7 +4,11 @@ description: Use to audit a Zsh plugin file (or a whole plugin directory) agains model: sonnet --- -You audit Zsh plugins against the [Z-Shell Plugin Standard](https://wiki.zshell.dev/community/zsh_plugin_standard) and this workspace's `AGENTS.md` conventions. You are **read-only**: you find and report violations with file:line references and exact fixes. You do not edit files. +You audit Zsh plugins against the +[Zsh Plugin Standard](https://wiki.zshell.dev/community/zsh_plugin_standard) +and this workspace's `AGENTS.md` conventions. Official Zsh documentation is +authoritative for shell semantics. You are **read-only**: you find and report +violations with file:line references and exact fixes. You do not edit files. ## What to check @@ -24,16 +28,23 @@ Run through this checklist for the plugin entry file and supporting files. Repor 0="${${(M)0:#/*}:-$PWD/$0}" ``` -3. **Plugins hash** — `typeset -gA Plugins` then `Plugins[KEY]="${0:h}"` with a sensible upper-case KEY. +3. **Portable state discipline** — plugin-owned functions, parameters, aliases, + hooks, widgets, and other mutable state are namespaced; option changes are + scoped or restored. -4. **PMSPEC fpath guard** — any `fpath+=(...)` for the plugin's own `functions/` dir must be guarded by `if [[ $PMSPEC != *f* ]]; then ... fi`. +4. **Optional manager profile** — when the plugin uses manager capabilities, + verify that the integration is capability-guarded and follows that manager's + current documented API. Do not require or recommend a shared `Plugins` + registry, and do not fail portable compliance because an optional profile is + absent. 5. **Unload function** — `_plugin_unload` exists and: - - removes its own `functions/` entry from `fpath` + - removes a `functions/` entry from `fpath` only when the plugin added it - unsets every global variable the plugin created - removes aliases / hooks / options it set, restoring prior state - `unfunction`s the plugin's own functions - - unsets its `Plugins[KEY]` entry + - reverses manager-profile state only when the plugin created that state, + using the manager's documented lifecycle API when one exists - self-destructs (`unfunction _plugin_unload`) 6. **Handler functions** (files under `functions/`) — start with strict emulation: @@ -47,9 +58,15 @@ Run through this checklist for the plugin entry file and supporting files. Repor 8. **Syntax** — run `zsh -n ` on each Zsh file and report any failures. +9. **Load safety** — load performs no network activity, side effects are + explicit, and unload reverses plugin-owned changes. + ## How to work - Use Glob/Grep to locate the entry file and supporting files; Read them fully. - Run `zsh -n` via Bash for syntax verification. - Cross-reference an existing compliant plugin (e.g. `z-shell/zsh-eza:zsh-eza.plugin.zsh`) when a pattern is ambiguous. -- Output a single compact report: a checklist table, then a numbered list of concrete fixes ordered by severity (standard-breaking first, style last). +- Classify findings as official-Zsh semantics, portable-standard requirements, + optional manager-profile defects, or repository conventions. +- Output a single compact report: a checklist table, then a numbered list of + concrete fixes ordered by severity (standard-breaking first, style last). diff --git a/.github/instruction-surfaces.json b/.github/instruction-surfaces.json index 6779af3ff..ccc949fef 100644 --- a/.github/instruction-surfaces.json +++ b/.github/instruction-surfaces.json @@ -57,7 +57,14 @@ "kind": "shared-policy", "authority": "canonical-detail", "consumers": ["codex", "claude-code", "copilot", "gemini-cli", "human"], - "tasks": ["implementation", "review"], + "tasks": [ + "implementation", + "review", + "zsh-plugin-creation", + "zsh-plugin-review", + "zsh-plugin-code-change", + "zsh-plugin-template" + ], "file_patterns": ["**"], "required": true, "review_owner": "z-shell maintainers", @@ -147,6 +154,39 @@ "review_owner": "z-shell maintainers", "canonical_for": ["shell"] }, + { + "id": "instruction-zsh-plugin-standard", + "path": ".github/instructions/zsh-plugin-standard.instructions.md", + "kind": "scoped-guidance", + "authority": "canonical-detail", + "consumers": ["codex", "claude-code", "copilot", "gemini-cli", "human"], + "tasks": [ + "zsh-plugin-creation", + "zsh-plugin-review", + "zsh-plugin-code-change", + "zsh-plugin-template", + "zsh-plugin-documentation", + "zsh-plugin-scaffolding" + ], + "file_patterns": ["**"], + "required": true, + "review_owner": "z-shell maintainers", + "canonical_for": ["zsh-plugin-standard-application"] + }, + { + "id": "instruction-zsh-plugin-standard-aliases", + "path": ".github/instructions/zsh-plugin-standard-aliases.instructions.md", + "kind": "scoped-guidance", + "authority": "canonical-detail", + "consumers": ["codex", "claude-code", "copilot", "gemini-cli", "human"], + "tasks": ["code-review", "readme-authoring", "zsh-plugin-scaffolding"], + "file_patterns": [ + "**/*.plugin.zsh,**/init.zsh,templates/readme/zsh-plugin.md,.github/skills/new-zsh-plugin/**,.github/agents/zsh-plugin-standard-reviewer.agent.md" + ], + "required": true, + "review_owner": "z-shell maintainers", + "canonical_for": [] + }, { "id": "instruction-testing", "path": ".github/instructions/testing.instructions.md", @@ -237,7 +277,7 @@ "kind": "agent", "authority": "advisory", "consumers": ["copilot"], - "tasks": ["zsh-plugin-review"], + "tasks": ["zsh-plugin-review", "code-review"], "file_patterns": ["**"], "required": false, "review_owner": "z-shell maintainers", @@ -261,7 +301,7 @@ "kind": "skill", "authority": "advisory", "consumers": ["copilot"], - "tasks": ["readme-authoring"], + "tasks": ["readme-authoring", "zsh-plugin-documentation"], "file_patterns": ["**"], "required": false, "review_owner": "z-shell maintainers", @@ -333,7 +373,7 @@ "kind": "skill", "authority": "advisory", "consumers": ["copilot"], - "tasks": ["zsh-plugin-scaffolding"], + "tasks": ["zsh-plugin-scaffolding", "zsh-plugin-creation"], "file_patterns": ["**"], "required": false, "review_owner": "z-shell maintainers", @@ -417,7 +457,7 @@ "kind": "runbook", "authority": "canonical-detail", "consumers": ["codex", "claude-code", "copilot", "gemini-cli", "human"], - "tasks": ["instruction-change"], + "tasks": ["instruction-change", "zsh-plugin-standard-governance"], "file_patterns": ["**"], "required": true, "review_owner": "z-shell maintainers", @@ -516,12 +556,16 @@ "tasks": [ "recurring-operations", "scheduled-workflow-audit", - "automation-review" + "automation-review", + "zsh-plugin-standard-review" ], "file_patterns": ["**"], "required": true, "review_owner": "z-shell maintainers", - "canonical_for": ["recurring-operations"] + "canonical_for": [ + "recurring-operations", + "zsh-plugin-standard-review" + ] }, { "id": "runbook-release", diff --git a/.github/instructions/documentation.instructions.md b/.github/instructions/documentation.instructions.md index 3f4594b9e..236c3109d 100644 --- a/.github/instructions/documentation.instructions.md +++ b/.github/instructions/documentation.instructions.md @@ -54,6 +54,14 @@ identical prose or artwork. Zi remains the first installation path. Include a screenshot or short demo only when it materially explains behavior, and keep long-form ecosystem guidance in the wiki. +The +[Zsh Plugin Standard](https://wiki.zshell.dev/community/zsh_plugin_standard) +is the canonical public plugin-authoring standard and should be linked from +plugin documentation where authoring or lifecycle behavior is discussed. +Summarize only repository-specific behavior. Official Zsh documentation remains +authoritative for shell semantics, and manager-specific profiles must be +identified as optional integrations rather than portable requirements. + ## LLM/agent files Keep shared organization guidance in z-shell/.github. Keep child-repository AGENTS.md or .github/instructions files only for concise repository-specific behavior, and link to public canonical guidance rather than duplicating it. @@ -61,5 +69,6 @@ Keep shared organization guidance in z-shell/.github. Keep child-repository AGEN ## See also - `decisions/0006-wiki-content-root-boundaries.md` +- [Zsh Plugin Standard](https://wiki.zshell.dev/community/zsh_plugin_standard) - `z-shell/wiki:.github/copilot-instructions.md` (wiki-local authoring rules) - `z-shell/wiki:.github/instructions/docs-authoring.instructions.md` diff --git a/.github/instructions/zsh-plugin-standard-aliases.instructions.md b/.github/instructions/zsh-plugin-standard-aliases.instructions.md new file mode 100644 index 000000000..8bb511928 --- /dev/null +++ b/.github/instructions/zsh-plugin-standard-aliases.instructions.md @@ -0,0 +1,17 @@ +--- +description: "Route generic review and README tasks on plugin-shaped files to the canonical Zsh plugin guidance" +applyTo: "**/*.plugin.zsh,**/init.zsh,templates/readme/zsh-plugin.md,.github/skills/new-zsh-plugin/**,.github/agents/zsh-plugin-standard-reviewer.agent.md" +--- + +# Zsh Plugin Standard Task Aliases + +For plugin-shaped code, plugin scaffolding, and the plugin README template, +apply the mandatory +[Zsh Plugin Standard instructions](zsh-plugin-standard.instructions.md). +The canonical public standard remains the +[Zsh Plugin Standard](https://wiki.zshell.dev/community/zsh_plugin_standard), +and official Zsh documentation remains authoritative for shell semantics. + +Generic `code-review` and `readme-authoring` tasks do not imply that every Zsh +file or README is a plugin. Apply this route only to the plugin-specific paths +declared in this instruction's `applyTo` scope. diff --git a/.github/instructions/zsh-plugin-standard.instructions.md b/.github/instructions/zsh-plugin-standard.instructions.md new file mode 100644 index 000000000..b9f3109cf --- /dev/null +++ b/.github/instructions/zsh-plugin-standard.instructions.md @@ -0,0 +1,58 @@ +--- +description: "Canonical Z-Shell requirements for authoring, changing, documenting, and reviewing Zsh plugins" +applyTo: "**" +--- + +# Zsh Plugin Standard Instructions + +The canonical public plugin-authoring standard is the +[Zsh Plugin Standard](https://wiki.zshell.dev/community/zsh_plugin_standard). +Read the current page before creating, changing, documenting, or reviewing a +Zsh plugin. Link to it instead of copying its full rules into repositories, +templates, reviews, or agent guidance. + +Official Zsh documentation remains authoritative for shell syntax, expansion, +options, builtins, and other language semantics. If the standard, a plugin +manager, or an example conflicts with the Zsh manual for the supported Zsh +version, follow the manual and report the documentation drift. + +## Organization requirements + +- Write Zsh-first code; do not substitute Bash syntax or portability advice for + documented Zsh behavior. +- Namespace plugin-owned functions, parameters, aliases, hooks, widgets, and + other mutable shell state. +- Scope option changes with `emulate -L zsh` or save and restore the prior + option state when a change must outlive one function call. +- Make load-time side effects explicit, minimal, and documented. +- Provide lifecycle cleanup that reverses plugin-owned side effects, including + hooks, functions, parameters, aliases, widgets, path entries, and temporary + resources. +- Do not perform network activity during plugin load. Network access must be an + explicit user action. +- Validate syntax with native Zsh and exercise load and unload behavior in a + clean Zsh process. + +## Portable requirements and manager profiles + +Keep portable plugin requirements separate from optional plugin-manager +profiles. Manager APIs such as Zi metadata, `PMSPEC`, or a manager-maintained +plugin registry may improve integration, but they are not portable Zsh +requirements. Use them only behind an intentional profile or capability guard, +and never present one manager's API as shell semantics. + +Zi is the Z-Shell reference manager for examples and testing under +`decisions/0002-zi-as-canonical-plugin-manager.md`. This affects defaults, not +the definition of portable plugin behavior. + +## Review output + +Identify whether each finding is: + +1. an official-Zsh semantic error; +2. a portable Plugin Standard violation; +3. an optional manager-profile defect; or +4. a repository-specific convention. + +Do not fail portable compliance solely because an optional manager integration +is absent. diff --git a/.github/skills/create-readme/SKILL.md b/.github/skills/create-readme/SKILL.md index 4e929d66f..3fb46fc0a 100644 --- a/.github/skills/create-readme/SKILL.md +++ b/.github/skills/create-readme/SKILL.md @@ -13,11 +13,13 @@ Create an accurate, concise, and visually intentional repository landing page. model, and linked organization policy before drafting. 2. Classify the repository. For a Zsh plugin, use [`templates/readme/zsh-plugin.md`](../../../templates/readme/zsh-plugin.md) - as the canonical structure. + as the canonical structure and read the + [Zsh Plugin Standard](https://wiki.zshell.dev/community/zsh_plugin_standard). 3. Verify every feature, setting, default, alias, lifecycle claim, command, and branch statement against the current implementation. 4. Lead Zsh-plugin installation guidance with Zi. Keep other manager examples concise and include only intentionally supported or verified paths. + Distinguish optional manager profiles from portable plugin requirements. 5. Keep long-form ecosystem guidance in the wiki and link to it. 6. Preserve meaningful visual identity: a clear header, a restrained maintained badge set, accessible alt text, and an optional behavior-focused screenshot diff --git a/.github/skills/new-zsh-plugin/SKILL.md b/.github/skills/new-zsh-plugin/SKILL.md index ca17f9932..ac8d97796 100644 --- a/.github/skills/new-zsh-plugin/SKILL.md +++ b/.github/skills/new-zsh-plugin/SKILL.md @@ -1,6 +1,6 @@ --- name: new-zsh-plugin -description: Scaffold a new Z-Shell-Standard-compliant Zsh plugin. Use when the user asks to create a new Zsh plugin, start a plugin from scratch, or add a plugin skeleton. Generates a compliant entry file (modeline, ZERO handling, Plugins hash, fpath guard, unload function) plus functions/ and docs/ layout. +description: Scaffold a new Z-Shell-Standard-compliant Zsh plugin. Use when the user asks to create a new Zsh plugin, start a plugin from scratch, or add a plugin skeleton. Generates a compliant entry file (modeline, ZERO handling, capability-aware fpath setup, unload function) plus functions/ and docs/ layout. disable-model-invocation: true --- @@ -8,13 +8,16 @@ disable-model-invocation: true Scaffold a plugin that conforms to the [Z-Shell Plugin Standard](https://wiki.zshell.dev/community/zsh_plugin_standard), `z-shell/.github/AGENTS.md`, and the owning repository's local `AGENTS.md` when present. +Official Zsh documentation is authoritative for shell semantics. The scaffold +honors the optional `PMSPEC` capability signal without requiring a shared +manager registry. + ## Steps 1. **Gather inputs** (ask only if not supplied): - An explicit target repository root. The caller must supply it; do not infer or default to a multi-repository checkout path. - Plugin name in kebab-case, e.g. `zsh-foo` → entry file `zsh-foo.plugin.zsh`. - - Derive `PLUGIN_KEY` = upper-snake of the name without a `zsh-` prefix, e.g. `zsh-foo` → `ZSH_FOO`. 2. **Create the layout**: @@ -26,7 +29,10 @@ Scaffold a plugin that conforms to the [Z-Shell Plugin Standard](https://wiki.zs docs/ ``` -3. **Write the entry file** from `templates/plugin.plugin.zsh`, replacing `__NAME__` (kebab name), `__KEY__` (PLUGIN_KEY), and `__FPATH_VAR__` (`_FPATH`). Keep the modeline as the first two lines verbatim. +3. **Write the entry file** from `templates/plugin.plugin.zsh`, replacing + `__NAME__` (kebab name) and `__FPATH_VAR__` (a namespaced upper-snake + parameter such as `ZSH_FOO_FPATH`). Keep the modeline as the first two lines + verbatim. 4. **Verify**: run `zsh -n .plugin.zsh`. It must pass before reporting done. Source it in a subshell to confirm the unload function is defined: @@ -45,4 +51,9 @@ Scaffold a plugin that conforms to the [Z-Shell Plugin Standard](https://wiki.zs builtin setopt extended_glob warn_create_global typeset_silent no_short_loops rc_quotes no_auto_pushd ``` - The unload function must reverse **every** side effect and self-destruct. +- Namespace plugin-owned state, scope option changes, and perform no network + activity during load. +- Keep portable behavior usable without optional manager capabilities. Track + whether the plugin changed shared state so unload preserves manager- or + user-owned state. - No build system — verify by sourcing in a Zsh session, not by running a build. diff --git a/.github/skills/new-zsh-plugin/templates/plugin.plugin.zsh b/.github/skills/new-zsh-plugin/templates/plugin.plugin.zsh index 85a3a33ee..8260cbef5 100644 --- a/.github/skills/new-zsh-plugin/templates/plugin.plugin.zsh +++ b/.github/skills/new-zsh-plugin/templates/plugin.plugin.zsh @@ -1,19 +1,18 @@ # -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- # vim: ft=zsh sw=2 ts=2 et # -# Zsh Plugin Standard +# Zsh Plugin Standard portable path handling # https://wiki.zshell.dev/community/zsh_plugin_standard#zero-handling 0="${ZERO:-${${0:#$ZSH_ARGZERO}:-${(%):-%N}}}" 0="${${(M)0:#/*}:-$PWD/$0}" -# https://wiki.zshell.dev/community/zsh_plugin_standard#standard-plugins-hash -typeset -gA Plugins -Plugins[__KEY__]="${0:h}" - -# https://wiki.zshell.dev/community/zsh_plugin_standard#funtions-directory +# Optional manager capability: PMSPEC=f means the manager owns fpath setup. +# https://wiki.zshell.dev/community/zsh_plugin_standard#functions-directory typeset -g __FPATH_VAR__="${0:h}/functions" -if [[ $PMSPEC != *f* ]]; then +typeset -gi __FPATH_VAR___ADDED=${__FPATH_VAR___ADDED:-0} +if [[ ${PMSPEC-} != *f* ]] && (( ! ${fpath[(Ie)${__FPATH_VAR__}]} )); then fpath+=( "${__FPATH_VAR__}" ) + __FPATH_VAR___ADDED=1 fi # --- Plugin body ------------------------------------------------------------- @@ -23,13 +22,16 @@ fi # https://wiki.zshell.dev/community/zsh_plugin_standard#unload-function __NAME___plugin_unload() { - # Remove our functions/ dir from fpath - fpath=("${fpath[@]:#${__FPATH_VAR__}}") + local fpath_index + if (( __FPATH_VAR___ADDED )); then + fpath_index=${fpath[(Ie)${__FPATH_VAR__}]} + (( fpath_index )) && fpath[$fpath_index]=() + fi # TODO: unset variables, remove aliases, remove hooks, unfunction helpers, # and restore any options/state this plugin changed. - unset __FPATH_VAR__ 'Plugins[__KEY__]' + unset __FPATH_VAR__ __FPATH_VAR___ADDED unfunction __NAME___plugin_unload } diff --git a/AGENTS.md b/AGENTS.md index 921432381..4b6a7437b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,16 +33,17 @@ The broad shapes are: Use the right home for each kind of knowledge: -| Kind of information | Source of truth | -| ------------------------------------- | -------------------------------------------------------------------- | -| Active work, blockers, next steps | GitHub issues, pull requests, and Linear | -| Organization policy | AGENTS.md in this repository | -| Instruction routing and impact review | .github/instruction-surfaces.json and runbooks/instruction-update.md | -| Durable architectural decisions | `decisions/` in this repo | -| Cross-repo operational procedures | `runbooks/` in this repo | -| Reusable implementation idioms | `PATTERNS.md` in this repo | -| Long-form user and maintainer docs | `wiki/` where practical | -| Local LLM memory | Optional cache only, never the only record | +| Kind of information | Source of truth | +| ------------------------------------- | ---------------------------------------------------------------------------- | +| Active work, blockers, next steps | GitHub issues, pull requests, and Linear | +| Organization policy | AGENTS.md in this repository | +| Instruction routing and impact review | .github/instruction-surfaces.json and runbooks/instruction-update.md | +| Durable architectural decisions | `decisions/` in this repo | +| Cross-repo operational procedures | `runbooks/` in this repo | +| Reusable implementation idioms | `PATTERNS.md` in this repo | +| Public Zsh plugin-authoring standard | [Zsh Plugin Standard](https://wiki.zshell.dev/community/zsh_plugin_standard) | +| Long-form user and maintainer docs | `wiki/` where practical | +| Local LLM memory | Optional cache only, never the only record | For handoffs, follow `.github/AGENT_MEMORY.md`. @@ -59,6 +60,7 @@ When working in z-shell repositories, optimize for: - **Language:** Zsh-first. Bash-only constructs are bugs in Zsh code unless the file is explicitly POSIX `sh`. - **Naming:** plugins use `zsh-`, annexes use `z-a-`, modules keep short descriptive names. +- **Plugin authoring:** read the canonical [Zsh Plugin Standard](https://wiki.zshell.dev/community/zsh_plugin_standard) for plugin creation, code changes, reviews, templates, and documentation. Official Zsh documentation remains authoritative for shell semantics; manager-specific profiles are optional. - **Canonical plugin manager:** `zi`. See `decisions/0002-zi-as-canonical-plugin-manager.md`. - **Commits and PR titles:** Conventional Commits. See `decisions/0003-conventional-commits.md`. - **Commit trailers:** Never include a `Co-authored-by` trailer. This organization-wide ban is author-enforced until [z-shell/.github#464](https://github.com/z-shell/.github/issues/464) lands. @@ -153,6 +155,7 @@ Do not silently work around drift. Open or update an issue in `z-shell/.github`, - `.github/AGENT_MEMORY.md` - `.github/instruction-surfaces.json` - `PATTERNS.md` +- [Zsh Plugin Standard](https://wiki.zshell.dev/community/zsh_plugin_standard) - `decisions/` - `runbooks/org-review.md` - `runbooks/adr.md` diff --git a/PATTERNS.md b/PATTERNS.md index 3a6d8d2e3..5796de189 100644 --- a/PATTERNS.md +++ b/PATTERNS.md @@ -8,6 +8,26 @@ Admission rule: - prefer linking to the wiki or plugin standard when a deeper explanation already exists - supersede patterns by updating this file, not by relying on private memory +## Zsh plugin standard boundary + +The canonical public authoring contract is the +[Zsh Plugin Standard](https://wiki.zshell.dev/community/zsh_plugin_standard). +Official Zsh documentation remains authoritative for shell semantics. The +patterns below are implementation examples, not a replacement for either +source. + +Across Z-Shell plugins, preserve these organization-level expectations: + +- use Zsh-first syntax and namespace plugin-owned shell state +- scope option changes and make load-time side effects explicit +- reverse plugin-owned side effects during unload +- keep network activity out of the load path +- separate portable plugin behavior from optional manager APIs + +Zi is the reference manager for Z-Shell examples. `PMSPEC` and similar +integration APIs form an optional manager profile; do not describe their +absence as a portable-standard failure. + ## Plugin entry-point skeleton Observed in: @@ -32,24 +52,19 @@ Pattern: Reference: -## Register the repository directory in `Plugins` +## Namespace and track plugin-owned state -Observed in: - -- `z-shell/zsh-eza:zsh-eza.plugin.zsh` -- `z-shell/zsh-fancy-completions:zsh-fancy-completions.plugin.zsh` -- `z-shell/z-a-meta-plugins:z-a-meta-plugins.plugin.zsh` - -Pattern: +Use plugin-specific parameter names for paths and ownership markers. Preserve +an existing ownership marker when an entry file is sourced repeatedly so the +eventual unload still reverses the original plugin-owned change. ```zsh -typeset -gA Plugins -Plugins[PLUGIN_KEY]="${0:h}" +typeset -g ZSH_EXAMPLE_FPATH="${0:h}/functions" +typeset -gi ZSH_EXAMPLE_FPATH_ADDED=${ZSH_EXAMPLE_FPATH_ADDED:-0} ``` -Use a stable, repo-specific key and treat the registered directory as the root for cleanup and sibling-path resolution. - -Reference: +Do not register plugin state in a shared global registry. Clear namespaced +parameters during unload after reversing only the state the plugin introduced. ## Guard `fpath` additions @@ -61,25 +76,25 @@ Observed in: Pattern: -- add `functions/` only when the plugin manager or current shell setup has not already done so -- the common Zi-aware form is: - ```zsh -if [[ $PMSPEC != *f* ]]; then - fpath+=( "${0:h}/functions" ) +if [[ ${PMSPEC-} != *f* ]] && + (( ! ${fpath[(Ie)${ZSH_EXAMPLE_FPATH}]} )); then + fpath+=( "$ZSH_EXAMPLE_FPATH" ) + ZSH_EXAMPLE_FPATH_ADDED=1 fi ``` -- an explicit membership guard is also acceptable when the entry point must tolerate non-Zi loader paths: +The capability and membership checks preserve manager- or user-owned entries. +Unload only when the ownership marker says the plugin added the path: ```zsh -if [[ ${fpath[(r)${0:h}/functions]} != "${0:h}/functions" ]]; then - fpath+=( "${0:h}/functions" ) +if (( ZSH_EXAMPLE_FPATH_ADDED )); then + local fpath_index=${fpath[(Ie)${ZSH_EXAMPLE_FPATH}]} + (( fpath_index )) && fpath[$fpath_index]=() fi +unset ZSH_EXAMPLE_FPATH ZSH_EXAMPLE_FPATH_ADDED ``` -Prefer the simpler Zi-aware form when the repository is clearly targeting Zi-managed loading. - ## Mandatory SHA-pinning for GitHub Actions Observed in: diff --git a/runbooks/instruction-update.md b/runbooks/instruction-update.md index 9e5b48c1d..c75261a31 100644 --- a/runbooks/instruction-update.md +++ b/runbooks/instruction-update.md @@ -29,6 +29,14 @@ pull-request body: 6. Can each supported runtime still receive the mandatory rule without relying on an optional hook or skill? 7. Do generated output and size limits still pass? +When the Zsh Plugin Standard or a governance reference to it changes, question +5 must also cover: + +- the private control workspace's manifest routes and generated instruction + composite; and +- the coordinated wiki change that must own the twice-yearly review automation, + structured issue output, and deterministic page checks. + Record an explicit answer for every question. A link to this runbook without the answers is not an impact review. @@ -45,6 +53,10 @@ answers is not an impact review. 8. Run the public and private validators that apply to the repositories changed. 9. Perform each manual runtime discovery check or mark it unverified. 10. Review the complete diff for duplicate or contradictory ownership. +11. For Plugin Standard changes, confirm the private control-workspace route and + coordinated wiki automation change remain aligned. Land the wiki automation + before or with policy that depends on an active review cycle; otherwise + record it as unresolved. Do not add a duplicate schedule here. ## Wiki (`z-shell/wiki`) checklist @@ -53,12 +65,19 @@ answers is not an impact review. - [ ] Update `.github/instructions/docs-authoring.instructions.md` (content-root selection, frontmatter, naming). - [ ] Update `.github/instructions/agent-docusaurus-writer.instructions.md` (root selection, invocation). - [ ] Run `pnpm validate:frontmatter` and `pnpm build:en`. +- [ ] When the Plugin Standard changes, add or update the coordinated + twice-yearly workflow so it opens the structured review issue and runs + deterministic link, example, and consistency checks. Treat unlanded + automation as a dependency, not current behavior. ## Org (`z-shell/.github`) checklist - [ ] Decision-level change? Draft an ADR — see `runbooks/adr.md` (status starts `PROPOSED`). - [ ] Update affected runbooks and `.github/instructions/`. - [ ] New tooling/plugin? Update `.github/instructions/mcp-plugins.instructions.md`. +- [ ] For Plugin Standard governance, update its routed scoped guidance, + templates, patterns, and recurring-review procedure without duplicating + the public standard. ## Other repositories diff --git a/runbooks/new-repository.md b/runbooks/new-repository.md index 212bbcc98..b0dca27f6 100644 --- a/runbooks/new-repository.md +++ b/runbooks/new-repository.md @@ -62,9 +62,14 @@ lib/ # only when sourced helpers are needed docs/ # short repository-local usage only ``` -Follow the entry-point, `ZERO`, `Plugins`, guarded `fpath`, and unload patterns -in `PATTERNS.md` and the Z-Shell Plugin Standard. The unload function must -reverse plugin-owned side effects. +Follow the entry-point, `ZERO`, namespaced state, ownership-tracked `fpath`, and +unload patterns in `PATTERNS.md` and the +[Zsh Plugin Standard](https://wiki.zshell.dev/community/zsh_plugin_standard). +Official Zsh documentation remains authoritative for shell semantics. Treat +`PMSPEC` and similar manager capabilities as optional profiles rather than +portable requirements. Namespace plugin-owned state, scope option changes, keep +network activity out of the load path, and reverse only plugin-owned side +effects during unload. ### Annex diff --git a/runbooks/recurring-operations.md b/runbooks/recurring-operations.md index 23e144a7c..371e2fe21 100644 --- a/runbooks/recurring-operations.md +++ b/runbooks/recurring-operations.md @@ -9,10 +9,12 @@ Recurring work should run only as often, and with only as much authority, as its value requires. This runbook separates deterministic automation from maintainer judgment and provides one evidence record for each classification. -**Hard rule:** every review described here produces a draft only. Do not add -labels, post comments, close or lock issues or pull requests, dispatch -workflows, change settings, or modify repositories unless a maintainer approves -that action as a separate, scoped step. +**Hard rule:** except for the explicitly authorized +[Zsh Plugin Standard review issue](#review-the-zsh-plugin-standard-twice-yearly), +every review described here produces a draft only. Do not add labels, post +comments, close or lock issues or pull requests, dispatch workflows, change +settings, or modify repositories unless a maintainer approves that action as a +separate, scoped step. This runbook does not create a Codex schedule or any other scheduler. Evaluate the scheduler's supported capabilities and authority before proposing one. @@ -208,6 +210,92 @@ Before proposing a new tracker item: 3. attach new evidence to the existing owner when one exists; 4. record cross-links and keep status, owner, and next step consistent. +## Review the Zsh Plugin Standard twice yearly + +The canonical public standard is the +[Zsh Plugin Standard](https://wiki.zshell.dev/community/zsh_plugin_standard). +Review it in two planned windows each year. A coordinated `z-shell/wiki` change +must own the schedule, structured review issue, page source, and deterministic +checks. Land that wiki automation before or with any policy change that depends +on an active review cycle. If this runbook lands first, record the wiki work as +an unresolved dependency and do not claim the cycle is active. Do not add a +second scheduled workflow to this repository. + +**Authorized exception:** the maintainer approved the wiki-owned workflow to run +unattended twice yearly, execute deterministic read-only checks, and create +exactly one structured review issue with `issues: write`. The issue contains the +draft evidence and classifications for maintainer judgment. This authorization +does not extend to editing the standard, adding labels, posting follow-up +comments, closing or locking items, dispatching other workflows, or changing +repository settings. + +The authorized workflow must be idempotent per UTC half-year review window. Use +a stable window key and exact issue title, paginate the complete open and closed +issue set before creation, exclude pull requests, and no-op when any issue +exactly matches that title. Configure concurrency so overlapping scheduled or +manual runs for the same workflow cannot both reach issue creation. Only POST +the issue after the full duplicate lookup finds no match. + +### Collect evidence + +Use a bounded evidence window and record: + +- the current released official Zsh documentation and release notes; +- changes to the canonical wiki page and its open issues or pull requests; +- confirmed behavior and adoption across maintained Z-Shell plugins; +- plugin load, unload, security, compatibility, and performance incidents; +- current documentation and releases for Zi plus at least two other actively + maintained plugin managers; and +- links, code examples, anchors, and deterministic-check results from the + coordinated wiki automation once it has landed. + +Official Zsh documentation is authoritative for shell semantics. Ecosystem +practice can justify a portable plugin convention, but cannot redefine Zsh. +Manager APIs belong to optional profiles and must not become portable +requirements merely because one sampled manager supports them. + +### Classify each reviewed item + +Record both dimensions: + +```text +Authority: official-zsh | portable-ecosystem | optional-manager-profile +Status: retain | clarify | revise | deprecate | remove +Adoption: established | emerging | legacy | unsupported +Evidence: +Affected examples and links: +Cross-repository impact: +Follow-up owner: +``` + +Use `established` only when multiple maintained implementations support the +practice. Mark conflicting, obsolete, or unverified behavior explicitly rather +than averaging incompatible manager behavior into a false consensus. + +### Complete the review + +The structured issue must include: + +1. link and anchor verification; +2. native-Zsh validation of runnable examples; +3. internal consistency across terminology, requirements, examples, and + portable-versus-profile labels; +4. security review of trust boundaries, input handling, network behavior, + temporary resources, and cleanup; +5. performance review of plugin load paths, repeated work, external processes, + and completion initialization; +6. manager sampling results without treating manager APIs as shell semantics; +7. cross-repository impact on this repository's policy, scoped instructions, + patterns, templates, agents, and skills; +8. impact on affected plugin repositories, wiki navigation and automation, and + the private control workspace's routes and generated composite; and +9. an owner and tracked follow-up for every proposed change. + +Deterministic checks provide evidence; maintainers decide normative changes. +Keep the first pass draft-only. A review is complete only when the issue records +the classification, unresolved evidence, cross-repository dependencies, and +whether the next twice-yearly review remains warranted. + ## Use draft-only prompt templates ### Scheduled-workflow health review diff --git a/scripts/test_validate_agent_policy.py b/scripts/test_validate_agent_policy.py index ef3a08149..bd608f4e3 100644 --- a/scripts/test_validate_agent_policy.py +++ b/scripts/test_validate_agent_policy.py @@ -1477,6 +1477,77 @@ def test_cli_escapes_control_characters_in_discovered_paths(self) -> None: class PublicRepositoryTests(unittest.TestCase): + def test_new_zsh_plugin_scaffold_owns_only_its_fpath_change(self) -> None: + template = ( + PUBLIC_ROOT + / ".github/skills/new-zsh-plugin/templates/plugin.plugin.zsh" + ).read_text() + rendered = template.replace("__NAME__", "zsh-example").replace( + "__FPATH_VAR__", "ZSH_EXAMPLE_FPATH" + ) + self.assertNotIn("Plugins[", rendered) + self.assertNotIn("Plugins[", (PUBLIC_ROOT / "PATTERNS.md").read_text()) + + with tempfile.TemporaryDirectory() as directory: + plugin_path = Path(directory) / "zsh-example.plugin.zsh" + (plugin_path.parent / "functions").mkdir() + plugin_path.write_text(rendered) + + cases = { + "manager-owned": """ + plugin_path=$1 + functions_dir=${plugin_path:h}/functions + fpath+=( "$functions_dir" ) + typeset -g PMSPEC=f + source "$plugin_path" + (( ZSH_EXAMPLE_FPATH_ADDED == 0 )) + zsh-example_plugin_unload + (( ${fpath[(Ie)$functions_dir]} )) + (( ! ${+parameters[ZSH_EXAMPLE_FPATH]} )) + (( ! ${+parameters[ZSH_EXAMPLE_FPATH_ADDED]} )) + """, + "plugin-owned": """ + plugin_path=$1 + functions_dir=${plugin_path:h}/functions + unset PMSPEC + (( ! ${fpath[(Ie)$functions_dir]} )) + source "$plugin_path" + (( ZSH_EXAMPLE_FPATH_ADDED == 1 )) + (( ${fpath[(Ie)$functions_dir]} )) + zsh-example_plugin_unload + (( ! ${fpath[(Ie)$functions_dir]} )) + (( ! ${+parameters[ZSH_EXAMPLE_FPATH]} )) + (( ! ${+parameters[ZSH_EXAMPLE_FPATH_ADDED]} )) + """, + "repeated-source": """ + plugin_path=$1 + functions_dir=${plugin_path:h}/functions + unset PMSPEC + source "$plugin_path" + (( ZSH_EXAMPLE_FPATH_ADDED == 1 )) + source "$plugin_path" + (( ZSH_EXAMPLE_FPATH_ADDED == 1 )) + (( ${fpath[(Ie)$functions_dir]} )) + zsh-example_plugin_unload + (( ! ${fpath[(Ie)$functions_dir]} )) + (( ! ${+parameters[ZSH_EXAMPLE_FPATH]} )) + (( ! ${+parameters[ZSH_EXAMPLE_FPATH_ADDED]} )) + """, + } + for name, script in cases.items(): + with self.subTest(name=name): + completed = subprocess.run( + ["zsh", "-fc", script, "_", str(plugin_path)], + check=False, + capture_output=True, + text=True, + ) + self.assertEqual( + completed.returncode, + 0, + completed.stdout + completed.stderr, + ) + def test_public_manifest_routes_recurring_operations_runbook(self) -> None: manifest = json.loads( (PUBLIC_ROOT / ".github/instruction-surfaces.json").read_text() @@ -1484,7 +1555,7 @@ def test_public_manifest_routes_recurring_operations_runbook(self) -> None: recurring_operations_surfaces = [ surface for surface in manifest["surfaces"] - if surface.get("canonical_for") == ["recurring-operations"] + if "recurring-operations" in surface.get("canonical_for", []) ] self.assertEqual( @@ -1500,15 +1571,87 @@ def test_public_manifest_routes_recurring_operations_runbook(self) -> None: "recurring-operations", "scheduled-workflow-audit", "automation-review", + "zsh-plugin-standard-review", ], "file_patterns": ["**"], "required": True, "review_owner": "z-shell maintainers", - "canonical_for": ["recurring-operations"], + "canonical_for": [ + "recurring-operations", + "zsh-plugin-standard-review", + ], } ], ) + def test_public_manifest_routes_zsh_plugin_standard(self) -> None: + manifest = json.loads( + (PUBLIC_ROOT / ".github/instruction-surfaces.json").read_text() + ) + surfaces = {surface["id"]: surface for surface in manifest["surfaces"]} + + self.assertEqual( + surfaces["instruction-zsh-plugin-standard"], + { + "id": "instruction-zsh-plugin-standard", + "path": ".github/instructions/zsh-plugin-standard.instructions.md", + "kind": "scoped-guidance", + "authority": "canonical-detail", + "consumers": [ + "codex", + "claude-code", + "copilot", + "gemini-cli", + "human", + ], + "tasks": [ + "zsh-plugin-creation", + "zsh-plugin-review", + "zsh-plugin-code-change", + "zsh-plugin-template", + "zsh-plugin-documentation", + "zsh-plugin-scaffolding", + ], + "file_patterns": ["**"], + "required": True, + "review_owner": "z-shell maintainers", + "canonical_for": ["zsh-plugin-standard-application"], + }, + ) + self.assertEqual( + surfaces["instruction-zsh-plugin-standard-aliases"], + { + "id": "instruction-zsh-plugin-standard-aliases", + "path": ( + ".github/instructions/" + "zsh-plugin-standard-aliases.instructions.md" + ), + "kind": "scoped-guidance", + "authority": "canonical-detail", + "consumers": [ + "codex", + "claude-code", + "copilot", + "gemini-cli", + "human", + ], + "tasks": [ + "code-review", + "readme-authoring", + "zsh-plugin-scaffolding", + ], + "file_patterns": [ + "**/*.plugin.zsh,**/init.zsh," + "templates/readme/zsh-plugin.md," + ".github/skills/new-zsh-plugin/**," + ".github/agents/zsh-plugin-standard-reviewer.agent.md" + ], + "required": True, + "review_owner": "z-shell maintainers", + "canonical_for": [], + }, + ) + def test_public_repository_documents_instruction_governance(self) -> None: adr = ( PUBLIC_ROOT / "decisions/0014-portable-agent-instruction-architecture.md" diff --git a/templates/readme/zsh-plugin.md b/templates/readme/zsh-plugin.md index 388694afd..936ac95d9 100644 --- a/templates/readme/zsh-plugin.md +++ b/templates/readme/zsh-plugin.md @@ -71,7 +71,8 @@ plugin capability, not merely an alternative spelling of the basic load. --> ### Other plugin managers +section compact and do not compare competing projects' feature sets. Identify +manager-specific APIs as optional profiles rather than portable requirements.> ## Configuration @@ -90,8 +91,10 @@ option mappings.> ## Lifecycle and side effects - +- - - +- ## Verification @@ -106,6 +109,8 @@ From the repository root: ## Documentation and support - [Z-Shell wiki](https://wiki.zshell.dev/) +- [Zsh Plugin Standard](https://wiki.zshell.dev/community/zsh_plugin_standard) +- [Zsh documentation](https://zsh.sourceforge.io/Doc/) - [Report an issue](https://github.com/z-shell//issues) ## Release model @@ -126,8 +131,12 @@ This project is distributed under the terms in [LICENSE](LICENSE). - [ ] The purpose and feature claims match current implementation behavior. - [ ] Zi is the first installation path. - [ ] Other manager examples are intentionally supported or verified. +- [ ] Manager-specific profiles are distinguished from portable requirements. - [ ] Public settings, aliases, functions, hooks, and defaults are complete. - [ ] Load failures and unload behavior are documented. +- [ ] Plugin-owned state is namespaced, option changes are scoped, and unload + reverses every owned side effect. +- [ ] Plugin load performs no network activity. - [ ] The verification command runs from a clean checkout. - [ ] Long-form guidance links to the wiki instead of being duplicated. - [ ] Badges are maintained signals rather than decoration. From 3c9bf27fb5b1b12205535ef48808c759971d6ac0 Mon Sep 17 00:00:00 2001 From: Sal Date: Sat, 15 Aug 2026 12:44:02 +0100 Subject: [PATCH 2/2] fix(ci): provision zsh for policy tests --- .github/workflows/agent-instructions.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/agent-instructions.yml b/.github/workflows/agent-instructions.yml index fd36d366c..5adc3a447 100644 --- a/.github/workflows/agent-instructions.yml +++ b/.github/workflows/agent-instructions.yml @@ -64,6 +64,9 @@ jobs: with: python-version: "3.10" + - name: Install Zsh + run: sudo apt-get update && sudo apt-get install --yes zsh + - name: Run agent policy unit tests run: python3 -m unittest scripts/test_validate_agent_policy.py -v