Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 23 additions & 19 deletions .github/agents/zsh-plugin-standard-reviewer.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,33 +30,37 @@ Check the plugin entry file and its supporting files:
2. **Entry-path resolution**: verify that the `ZERO`-aware source-path
expression is evaluated at the call site and passed into localized work
without assigning special parameter `0` or using function-local `${0:h}`.
3. **Plugin registration**: if the plugin uses the optional `Plugins` profile,
verify a unique key and the snapshot needed to restore it. Do not require
manager-specific registration for a portable plugin. Document every
intentional global effect. Cite `zsh/plugin/document-global-state`.
3. **Plugin registration**: if the plugin uses a shared `Plugins` parameter,
report it as non-portable migration debt. Portable code neither requires nor
mutates a shared manager or plugin registry. Do not require manager-specific
registration for a portable plugin.
4. **Autoload path**: verify that a controlled `functions/` directory is added
only when the loader has not already handled it and the exact entry is
absent. Cite `zsh/security/trust-paths`.
5. **Unload lifecycle**: when unload is part of the plugin contract, verify that
it reverses every owned side effect, restores any `Plugins` key it owns to
its pre-load state, and self-destructs. When cleanup identifies an appended
`fpath` entry as the last exact match, require an invariant against inserting
or reordering an indistinguishable equal entry after it. Cite
`zsh/plugin/restore-state`.
6. **Passive loading**: verify that plugin and completion load paths perform no
5. **Namespace and configuration**: verify one documented portable ASCII
identifier, project-prefixed persistent names, one namespaced `zstyle`
configuration context, and no scattered public configuration parameters.
6. **Unload lifecycle**: verify an idempotent, partial-load-safe unload function
that reverses every owned side effect and self-destructs. It restores a prior
pre-load state only when the installed value is unchanged and preserves
newer user state.
7. **Passive loading**: verify that plugin and completion load paths perform no
implicit network activity. Cite `zsh/security/no-passive-network`.
7. **Autoloaded functions**: evaluate function initialization under the
8. **Autoloaded functions**: evaluate function initialization under the
canonical `autoload-function` rules. Do not impose a universal option
bundle.
8. **Native syntax**: when a Zsh file is intended to parse independently, run:
9. **Runtime proof**: require a clean-process lifecycle test for the declared
load surface, repeated source, partial failure, hostile state, and post-load
user changes. Static analysis does not prove runtime restoration.
10. **Native syntax**: when a Zsh file is intended to parse independently, run:

```sh
zsh -f -n <file>
```
```sh
zsh -f -n <file>
```

This is native syntax validation only. It is not behavioral validation and
does not prove every system startup source was skipped. Distinguish
native-invalid Zsh from gaps in supplemental tools.
This is native syntax validation only. It is not behavioral validation and
does not prove every system startup source was skipped. Distinguish
native-invalid Zsh from gaps in supplemental tools.

Do not add ShellCheck or `shfmt` as Zsh validators.

Expand Down
18 changes: 18 additions & 0 deletions .github/instruction-surfaces.json
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,24 @@
"review_owner": "z-shell maintainers",
"canonical_for": ["branching-model"]
},
{
"id": "decision-0020",
"path": "decisions/0020-adopt-zsh-plugin-standard-2.md",
"kind": "decision",
"authority": "canonical-detail",
"consumers": ["codex", "claude-code", "copilot", "gemini-cli", "human"],
"tasks": [
"architecture-decision",
"zsh-plugin-creation",
"zsh-plugin-review",
"zsh-plugin-code-change",
"zsh-plugin-template"
],
"file_patterns": ["**"],
"required": true,
"review_owner": "z-shell maintainers",
"canonical_for": ["zsh-plugin-standard-adoption"]
},
{
"id": "zsh-standard-policy",
"path": "lib/zsh-standard-policy.json",
Expand Down
2 changes: 1 addition & 1 deletion .github/instructions/code-review-generic.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Structure code review feedback with concrete evidence and actionable fixes:

````markdown
- **Severity**: [CRITICAL | IMPORTANT | SUGGESTION]
- **Rule / Category**: [e.g., zsh/plugin/restore-state or security/untrusted-eval]
- **Rule / Category**: [e.g., zsh/plugin/exact-lifecycle or security/untrusted-eval]
- **Location**: `path/to/file:line`
- **Impact**: Explanation of the concrete failure mode or risk.
- **Correction**:
Expand Down
20 changes: 16 additions & 4 deletions .github/instructions/testing.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ definitions come from `decisions/0007-release-publication-flow.md`.
`zpmod`. Never cut a `vX.Y.Z` tag from a red commit.
- **Class 3, git-consumed:** **validation-only as the required organization
gate.** Run existing repository-owned tests and add regression coverage when
behavior changes, but do not impose a release suite or coverage gate. The
baseline remains syntax, compilation, and clean loading.
behavior changes, but do not impose a release suite or coverage gate. A
maintained plugin also proves its Standard 2 load surface and exact lifecycle
contract in a clean process.
- **Class 4 — meta:** baseline plus workflow/markdown linting.

## Coverage
Expand All @@ -57,8 +58,15 @@ Do not add an org-wide coverage number.
integration or system tests are also valid when they exercise boundaries that
do not fit a unit test.
- Test plugins by sourcing them in a clean Zsh session; there is no build step.
- When unload is part of the subject's contract, assert that its unload function
reverses the owned side effects.
- Prime lifecycle observers before the baseline. Compare functions, parameters
and attributes, aliases, options, traps, modules, hooks, widgets, bindings,
styles, `path`, and `fpath` without printing captured values.
- Assert the documented load allowlist, harmless repeated source, cleanup after
partial failure, hostile caller options, non-interactive behavior, and exact
unload restoration.
- Use ownership-aware cleanup assertions: restore the pre-load value only when
the user did not change the installed value, otherwise preserve the user's
newer state.

## Required checks

Expand All @@ -70,6 +78,10 @@ suite against the exact commit before a release tag is published.
For `zi`, ordinary pull requests validate against `next`; the promotion pull
request into `main` runs the full stable-branch check set on its exact head SHA.

Organization templates must pin zsh-lint and ZUnit to exact commits belonging
to published releases. Do not use mutable branches, tags, or unreleased pull
request commits as a required organization gate.

## See also

- `decisions/0009-testing-ci-strategy.md`
Expand Down
33 changes: 24 additions & 9 deletions .github/instructions/zsh-plugin-standard.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,43 @@ version, follow the manual and report the documentation drift.

## Organization requirements

- Treat version 2 as one clean portable contract. Do not preserve an older
namespace, shared registry, configuration parameter, or directory convention
merely as a compatibility path in a refactored plugin.
- Choose and document one portable ASCII project identifier. Derive every
persistent public or private shell-visible name from it, using a leading
underscore for private state and callbacks.
- Use one project-owned `zstyle` context for ordinary public configuration.
Keep project parameters private and do not expose scattered configuration
globals or environment variables as a parallel interface.
- Portable code must neither require nor mutate a shared manager or plugin
registry parameter. Manager integration belongs to an optional, independently
tested profile.
- 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.
- When the plugin declares an unload contract, provide lifecycle cleanup that
reverses plugin-owned side effects, including hooks, functions, parameters,
aliases, widgets, path entries, and temporary resources.
- Provide idempotent lifecycle cleanup that tolerates partial initialization
and reverses plugin-owned side effects, including hooks, functions,
parameters, aliases, widgets, path entries, and temporary resources. Restore
prior state only while the installed value remains unchanged; preserve newer
user state.
- Do not perform network activity during plugin load. Network access must be an
explicit user action.
- Validate syntax with native Zsh. When unload is part of the contract, exercise
load and unload behavior in a clean Zsh process.
- Validate syntax with native Zsh. Separately exercise the declared load
surface, repeated source, partial failure, hostile caller state, and exact
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.
profiles. Manager APIs such as Zi metadata or `PMSPEC` may improve integration,
but they are not portable Zsh requirements. Use them only behind an intentional
profile or capability guard, never mutate a manager-owned registry from the
portable entrypoint, 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
Expand Down
58 changes: 51 additions & 7 deletions .github/instructions/zsh-scripting.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -806,19 +806,60 @@ Plugin and completion load paths perform no implicit network activity.

## Plugin lifecycle and documentation

### `zsh/plugin/document-global-state`
### `zsh/plugin/stable-namespace`

- Level: `required`
- Profiles: `sourced-library`
- Minimum Zsh: `null`
- Basis: `organization-policy`
- Evidence: `parameters`, `functions`
- Enforcement: `human-review`
- Enforcement: `lint`, `human-review`

Choose one portable ASCII project identifier. Derive every persistent public
and private shell-visible name from it, with a leading underscore for private
state and callbacks. Do not retain punctuation-only semantic roles or a second
legacy namespace in refactored plugins.

### `zsh/plugin/coherent-configuration`

- Level: `required`
- Profiles: `sourced-library`
- Minimum Zsh: `null`
- Basis: `organization-policy`
- Evidence: `parameters`
- Enforcement: `lint`, `human-review`

Use one namespaced `zstyle` context for ordinary public configuration. Keep
project parameters private and do not expose scattered global parameters or
environment variables as a parallel configuration interface.

### `zsh/plugin/no-shared-registry`

- Level: `required`
- Profiles: `sourced-library`
- Minimum Zsh: `null`
- Basis: `organization-policy`
- Evidence: `parameters`
- Enforcement: `lint`, `runtime-test`, `human-review`

Portable plugin code neither requires nor mutates a shared manager or plugin
registry parameter. Manager-owned registries and capabilities belong to
optional, independently tested profiles.

### `zsh/plugin/document-load-surface`

- Level: `required`
- Profiles: `sourced-library`
- Minimum Zsh: `null`
- Basis: `organization-policy`
- Evidence: `parameters`, `functions`
- Enforcement: `runtime-test`, `human-review`

Document every intentional global parameter, hook, widget, alias, function,
option, path, descriptor, and directory effect.
Document and test every intentional persistent function, parameter, hook,
widget, alias, style, option, path, descriptor, module, and directory effect.
Setup-only helpers do not remain after loading.

### `zsh/plugin/restore-state`
### `zsh/plugin/exact-lifecycle`

- Level: `required`
- Profiles: `sourced-library`
Expand All @@ -827,8 +868,11 @@ option, path, descriptor, and directory effect.
- Evidence: `functions`, `parameters`, `options`
- Enforcement: `runtime-test`, `human-review`

When unload is part of the contract, reverse every owned side effect and remove
the unload function.
Provide an idempotent, partial-load-safe unload function that reverses every
owned side effect and removes itself. Restore prior state only while the value
installed by the plugin remains unchanged; preserve newer user state. Prove the
contract in a clean process after observer priming, including repeated source,
hostile caller state, partial failure, and post-load user changes.

### `zsh/documentation/comment-invariants`

Expand Down
47 changes: 27 additions & 20 deletions .github/skills/new-zsh-plugin/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,28 @@ semantics.
or default to a multi-repository checkout path.
- Plugin name in kebab-case, for example `zsh-foo` with entry file
`zsh-foo.plugin.zsh`.
- One portable ASCII project identifier, for example `zsh_foo`. This owns
every persistent public and private shell name and the
`:zsh_foo:config` style context.

3. **Create the layout**:
3. **Create the layout**. Create only the authoritative entrypoint initially.
Add each optional directory only when its execution role is required:

```
<target-repository-root>/
<target-repository-root>/
<name>.plugin.zsh
functions/
lib/
docs/
lib/ # optional private eager sources
functions/ # optional autoload functions
completions/ # optional native completion functions
bin/ # optional user-invoked executables
```

4. **Write the entry file** from `templates/plugin.plugin.zsh`, replacing
`__NAME__` (kebab name) and `__FPATH_VAR__` (an upper-snake project-owned
parameter such as `ZSH_FOO_FPATH`). Keep the modelines as the first two lines
verbatim. The first source owns the `fpath` decision; repeated sources must
not reset it. Add manager-specific registration only when the user requests
and identifies that optional profile.
`__IDENTIFIER__` with the ASCII project identifier. Keep the modelines as the
first two lines verbatim. Do not create shared `Plugins` state, scattered
public configuration parameters, or a second legacy namespace. Add
manager-specific behavior only when the user requests and identifies that
optional profile, and keep it outside the portable contract.

5. **Write autoload function bodies**: begin each generated function body with
`builtin emulate -L zsh`. Select only the correctness-affecting options that
Expand All @@ -52,13 +57,12 @@ semantics.
6. **Verify syntax and lifecycle**:
- Run `zsh -f -n <name>.plugin.zsh` for native syntax validation under
`zsh/validation/native-authority`.
- In an isolated shell with temporary `HOME` and `ZDOTDIR`, source the entry
file, verify its declared load effects, invoke `<name>_plugin_unload`, and
assert post-unload restoration of `fpath`, scaffold parameters, functions,
hooks, aliases, options, and every other declared side effect.
- The scaffold removes the last exact `fpath` match that it appended. Do not
insert or reorder an indistinguishable equal entry after that append
before unloading; Zsh arrays do not retain occurrence identity.
- In an isolated shell with temporary `HOME` and `ZDOTDIR`, prime the ZUnit
lifecycle observer, snapshot the baseline, source the entry file, and
assert the exact documented load allowlist.
- Test repeated source, partial initialization failure, hostile caller
options, non-interactive loading, and post-load user changes. Invoke
`<identifier>_plugin_unload` and assert ownership-aware restoration.
- Remove the temporary directory. `zsh -f` suppresses normal RCS processing,
but a system `zshenv` may still execute.

Expand All @@ -69,10 +73,13 @@ semantics.

- Caller-state preservation: `zsh/sourced/preserve-caller-state`.
- Autoload body initialization: `zsh/autoload/initialize`.
- Documented plugin effects: `zsh/plugin/document-global-state`.
- Owned-effect cleanup: `zsh/plugin/restore-state`.
- Stable namespace: `zsh/plugin/stable-namespace`.
- Coherent configuration: `zsh/plugin/coherent-configuration`.
- Documented plugin effects: `zsh/plugin/document-load-surface`.
- Owned-effect cleanup: `zsh/plugin/exact-lifecycle`.
- Controlled autoload paths: `zsh/security/trust-paths`.

Keep the rule rationale in the canonical instruction. The scaffold must reverse
every owned side effect and self-destruct; syntax success alone is not a
behavioral result.
behavioral result. Pin zsh-lint and ZUnit only to commits from published
releases when wiring required CI.
Loading
Loading