From 432b4f72a7382cd611756506c4f3926623a033e4 Mon Sep 17 00:00:00 2001 From: Salvydas Lukosius Date: Fri, 29 May 2026 04:35:50 +0100 Subject: [PATCH 01/12] docs(adr): ratify ADRs 0001-0003/0006 and document decision authority Flip the four PROPOSED foundational ADRs (meta-repo pattern, zi as canonical plugin manager, Conventional Commits, wiki content-root boundaries) to ACCEPTED, since all are de-facto enforced across the workspace. Record ss-o as decider and add a Decision Authority section to the ADR runbook clarifying who may accept an ADR. --- decisions/0001-meta-repo-and-agents-md.md | 6 +++--- decisions/0002-zi-as-canonical-plugin-manager.md | 6 +++--- decisions/0003-conventional-commits.md | 6 +++--- decisions/0006-wiki-content-root-boundaries.md | 4 ++-- runbooks/adr.md | 13 +++++++++++++ 5 files changed, 24 insertions(+), 11 deletions(-) diff --git a/decisions/0001-meta-repo-and-agents-md.md b/decisions/0001-meta-repo-and-agents-md.md index ed18bb266..134ee633f 100644 --- a/decisions/0001-meta-repo-and-agents-md.md +++ b/decisions/0001-meta-repo-and-agents-md.md @@ -1,8 +1,8 @@ # 1. Adopt a meta-repo pattern centered on `AGENTS.md` -- **Status:** PROPOSED -- **Date:** 2026-05-19 -- **Deciders:** TBD +- **Status:** ACCEPTED +- **Date:** 2026-05-29 +- **Deciders:** ss-o - **Supersedes:** None - **Superseded by:** None diff --git a/decisions/0002-zi-as-canonical-plugin-manager.md b/decisions/0002-zi-as-canonical-plugin-manager.md index 6ddb7ec00..1b1897e48 100644 --- a/decisions/0002-zi-as-canonical-plugin-manager.md +++ b/decisions/0002-zi-as-canonical-plugin-manager.md @@ -1,8 +1,8 @@ # 2. `zi` is the canonical plugin manager for the z-shell ecosystem -- **Status:** PROPOSED -- **Date:** 2026-05-19 -- **Deciders:** TBD +- **Status:** ACCEPTED +- **Date:** 2026-05-29 +- **Deciders:** ss-o - **Supersedes:** None - **Superseded by:** None diff --git a/decisions/0003-conventional-commits.md b/decisions/0003-conventional-commits.md index df76d9d2b..44e573569 100644 --- a/decisions/0003-conventional-commits.md +++ b/decisions/0003-conventional-commits.md @@ -1,8 +1,8 @@ # 3. Adopt Conventional Commits across z-shell repositories -- **Status:** PROPOSED -- **Date:** 2026-05-19 -- **Deciders:** TBD +- **Status:** ACCEPTED +- **Date:** 2026-05-29 +- **Deciders:** ss-o - **Supersedes:** None - **Superseded by:** None diff --git a/decisions/0006-wiki-content-root-boundaries.md b/decisions/0006-wiki-content-root-boundaries.md index a039aef21..68fa75508 100644 --- a/decisions/0006-wiki-content-root-boundaries.md +++ b/decisions/0006-wiki-content-root-boundaries.md @@ -1,7 +1,7 @@ # 6. Wiki Content-Root Boundaries -- **Status:** PROPOSED -- **Date:** 2026-05-22 +- **Status:** ACCEPTED +- **Date:** 2026-05-29 - **Deciders:** ss-o, Claude Code - **Supersedes:** None - **Superseded by:** None diff --git a/runbooks/adr.md b/runbooks/adr.md index 4348aaf9a..f5f948710 100644 --- a/runbooks/adr.md +++ b/runbooks/adr.md @@ -4,6 +4,19 @@ Use this workflow when a discussion, issue, or pull request makes a non-obvious **Hard rule:** draft the ADR, but do not silently mark it accepted. New ADRs start as `PROPOSED` until maintainers confirm them. +## Decision authority + +Only an org maintainer may flip an ADR from `PROPOSED` to `ACCEPTED`. Agents and +contributors draft and propose; they never self-accept. + +- The maintainer who accepts an ADR records their handle in the `Deciders` field. + Never leave `Deciders: TBD` on an `ACCEPTED` ADR. +- Acceptance happens on `main` (via merged PR), not on a feature branch. +- Superseding an existing ADR follows the same authority: link the old and new ADRs + through `Supersedes` / `Superseded by` and leave the superseded ADR in place. + +The current accepting maintainer is **ss-o**. + ## When to use this Draft an ADR when: From c0439acd8099e540f4968380d370810b49c66328 Mon Sep 17 00:00:00 2001 From: Salvydas Lukosius Date: Fri, 29 May 2026 04:35:59 +0100 Subject: [PATCH 02/12] docs(governance): add branching, testing/CI, and security-response ADRs plus runbooks and instructions Add ADR-0008 (branching model derived from ADR-0007 repo classes, resolving the repos.yml branch-model drift at its source), ADR-0009 (testing/CI scope by repo class), and ADR-0010 (security incident response: SLA, severity targets, escalation, post-incident review). Add paired runbooks (security-incident-response, onboarding, deprecation) and scoped instructions (testing, documentation) operationalizing the new decisions. --- .../documentation.instructions.md | 58 ++++++++++ .github/instructions/testing.instructions.md | 65 ++++++++++++ decisions/0008-branching-model.md | 82 ++++++++++++++ decisions/0009-testing-ci-strategy.md | 91 ++++++++++++++++ decisions/0010-security-incident-response.md | 100 ++++++++++++++++++ runbooks/deprecation.md | 69 ++++++++++++ runbooks/onboarding.md | 63 +++++++++++ runbooks/security-incident-response.md | 81 ++++++++++++++ 8 files changed, 609 insertions(+) create mode 100644 .github/instructions/documentation.instructions.md create mode 100644 .github/instructions/testing.instructions.md create mode 100644 decisions/0008-branching-model.md create mode 100644 decisions/0009-testing-ci-strategy.md create mode 100644 decisions/0010-security-incident-response.md create mode 100644 runbooks/deprecation.md create mode 100644 runbooks/onboarding.md create mode 100644 runbooks/security-incident-response.md diff --git a/.github/instructions/documentation.instructions.md b/.github/instructions/documentation.instructions.md new file mode 100644 index 000000000..d5089fe32 --- /dev/null +++ b/.github/instructions/documentation.instructions.md @@ -0,0 +1,58 @@ +--- +description: "Where documentation lives across z-shell repos and the wiki content-root boundaries" +applyTo: "**" +--- + +# Documentation Instructions + +Where a piece of documentation belongs, and the wiki's content-root rules. This +operationalizes `decisions/0006-wiki-content-root-boundaries.md` and the +org-wide documentation policy. + +## Default home: the wiki + +Durable, long-form documentation belongs in `z-shell/wiki` whenever practical. +Keep individual repos lean by linking to wiki pages instead of duplicating +guidance. Repo-local docs are justified only when tightly coupled to that repo's +source, release process, or contributor workflow. + +When a repo-local copy is unavoidable, prefer a generated or synchronized file +sourced from the wiki so maintenance stays centralized and drift is minimized. + +## Wiki content-root boundaries (ADR-0006) + +The wiki has three independent content roots. Place content by audience and +purpose, not by topic: + +- **`docs/`** — Zi **end-user** documentation: getting started, usage, guides for + people *using* the tools. +- **`community/`** — community-facing material: standards, contribution norms, + ecosystem-wide conventions. +- **`ecosystem/`** — ecosystem catalog: plugins, annexes, and related projects. + +Maintainer/operational guides are **not** end-user docs — do not place them under +`docs/` just because they concern the tools. + +### Hard rules + +- Do not create a duplicate of the same page across two content roots. A page has + one canonical home; link to it from elsewhere. +- When "moving" a page between roots, reconcile content rather than copying + verbatim — the ADR-0006 failure was a literal move that would have shipped + stale secret-key naming. Verify the moved copy matches current code/config. +- Never commit secret values or stale secret-key names in docs; reference the + current canonical names only. + +## LLM/agent files + +Per the workspace LLM file-placement policy, keep generic agent instructions +centralized in the meta-workspace root or `z-shell/.github`. Child repos keep +`AGENTS.md` / `.github/instructions/` only for repo-specific workflows that +cannot be expressed centrally, and those should link back rather than repeat +shared policy. + +## See also + +- `decisions/0006-wiki-content-root-boundaries.md` +- `repos/docs/wiki/.github/copilot-instructions.md` (wiki-local authoring rules) +- `repos/docs/wiki/.github/instructions/docs-authoring.instructions.md` diff --git a/.github/instructions/testing.instructions.md b/.github/instructions/testing.instructions.md new file mode 100644 index 000000000..69ddf4274 --- /dev/null +++ b/.github/instructions/testing.instructions.md @@ -0,0 +1,65 @@ +--- +description: "Testing and CI expectations for z-shell repositories, by repository class" +applyTo: "**" +--- + +# Testing Instructions + +How much to test, and what CI to require, depends on the repository's class. +This operationalizes `decisions/0009-testing-ci-strategy.md`; the class +definitions come from `decisions/0007-release-publication-flow.md`. + +## Identify the class first + +| Class | Repos | What it is | +| ----- | --------------------------------------- | -------------------------------- | +| 1 | `wiki`, `src`, `zd` | Continuously deployed artifact | +| 2 | `zunit`, `zsh-lint`, packaged `zsh` | Versioned tool/package | +| 3 | `zi`, most plugins/annexes | Git-consumed source | +| 4 | `.github` | Meta/infrastructure | + +## Baseline (every repo) + +- Workflows follow org conventions: SHA-pinned actions, top-level + least-privilege `permissions:`, `concurrency:` on push/PR, no-emoji workflow/ + job `name:` (ADR-0005), kebab-case filenames. +- Zsh sources pass `zsh -n` and `zcompile`. +- Conventional Commits (ADR-0003) and the disallowed-trailer check are enforced. + +## By class + +- **Class 1 — deployed:** the build must pass on the development branch before + deploy. Wiki: ESLint + Stylelint + production build. `zd`: Docker build matrix. + `src`: installer/loader validation. Add CodeQL where a supported language exists. +- **Class 2 — versioned tools:** a **full functional suite is required and gates + release tags**. ZUnit for Zsh tools; `go test` for the `zsh-lint` Go CLI. Never + cut a `vX.Y.Z` tag from a red commit. +- **Class 3 — git-consumed:** **validation-only.** Baseline checks plus ZUnit + where the plugin ships tests. No release automation, no coverage gate. The bar + is "loads and parses cleanly." +- **Class 4 — meta:** baseline plus workflow/markdown linting. + +## Coverage + +Coverage is **observed, not gated**, unless a class-2 tool sets its own threshold. +Do not add an org-wide coverage number. + +## Writing Zsh tests + +- Use ZUnit; keep one behavior per test. +- Test by sourcing the plugin in a clean Zsh session — there is no build step. +- For annex/handler functions, assert side effects are reversed by the unload + function (`_plugin_unload`). + +## Required checks + +Mark the class-appropriate checks as required for merge to the publication branch +(`main`, or `next`→`main` per ADR-0008). Class-3 repos require the baseline; +class-2 repos additionally require the functional suite before a release tag. + +## See also + +- `decisions/0009-testing-ci-strategy.md` +- `decisions/0007-release-publication-flow.md` +- `.github/instructions/github-actions-ci-cd-best-practices.instructions.md` +- `.github/instructions/shell.instructions.md` diff --git a/decisions/0008-branching-model.md b/decisions/0008-branching-model.md new file mode 100644 index 000000000..83782f084 --- /dev/null +++ b/decisions/0008-branching-model.md @@ -0,0 +1,82 @@ +# 8. Branching Model + +- **Status:** PROPOSED +- **Date:** 2026-05-29 +- **Deciders:** TBD +- **Supersedes:** None +- **Superseded by:** None + +## Context + +The org's repositories do not share a single branching model, and the +inconsistency is real, not cosmetic: + +- Some repos run a `next` → `main` integration flow (`src`, `wiki`, `zi`, + `zsh-lint`, `zsh-eza`). +- Others are trunk-based on `main` only (`zd`, packaged `zsh`, + `z-a-meta-plugins`, `zsh-fancy-completions`, `zunit`, `.github`). + +The meta-workspace catalog (`workspace/repos.yml`) had drifted from this reality +and had to be reconciled by inspecting live remotes. The root cause is that no +decision says *which class of repo uses a `next` branch and which does not*, so +each repo's model is discovered empirically rather than governed. `zsh-lint` +recently gained a `next` branch during its Go reboot, which re-surfaced the +ambiguity. + +`decisions/0007-release-publication-flow.md` already defines four repository +classes by delivery model. Branching should be derived from those classes rather +than decided per repo, so the catalog stops drifting at the source. + +## Decision + +Branch model follows the ADR-0007 repository class. + +1. **Continuously deployed artifacts** (`wiki`, `src`, `zd` images) — use a + `next` → `main` integration branch. `next` is the default development branch; + merging to `main` is the deploy/publication boundary. Branch names: + `feature-`, `bug-`, `hotfix-`; hotfixes branch from `main`, all + other work from `next`. +2. **Versioned tools and packages** (`zunit`, `zsh-lint`, packaged `zsh`) — `main` + is continuously validated development output; publication is a `vX.Y.Z` tag + (per ADR-0007). A `next` branch is **optional**: adopt it only when the repo's + change volume justifies an integration buffer (as `zsh-lint` did for its Go + reboot). Trunk-on-`main` is the default for low-volume tools. +3. **Git-consumed source** (`zi`, most plugins/annexes) — `next` → `main` where an + integration branch adds value (`zi`, `zsh-eza`); trunk-on-`main` is acceptable + for small, low-churn plugins (`z-a-meta-plugins`, `zsh-fancy-completions`). + `main` is always the consumable ref. +4. **Meta/infrastructure** (`.github`) — trunk-based on `main`. No `next` branch. + +`workspace/repos.yml` records each repo's actual branch model and notes when a +repo is trunk-only. Whenever a repo adds or removes a `next` branch, the catalog +entry is updated in the same change. + +## Consequences + +- `workspace/repos.yml` has an authoritative rule to validate against, instead of + drifting from empirical discovery. +- New repos inherit a branch model from their ADR-0007 class at creation time. +- The default-branch and branch-naming guidance in the workspace `CLAUDE.md` + ("default development branch `next`") is understood as the *integration-flow* + default, not a universal rule — trunk-only repos are explicitly sanctioned by + this ADR for classes 2–4 where noted. +- Promotion from `next` to `main` is a publication boundary only for class 1 + (deploy) repos; for other classes the merge validates but does not mint a + release (consistent with ADR-0007). + +## Alternatives considered + +- **One model for all repos (`next` → `main` everywhere).** Rejected: forces an + integration branch onto single-maintainer, low-churn plugins where it only adds + ceremony, and onto `.github` where there is nothing to integrate. +- **Trunk-only everywhere.** Rejected: the continuously deployed repos benefit + from a staging branch before a change goes live, and `zi`'s scale warrants an + integration buffer. +- **Leave it per-repo and informal.** Rejected: that is the status quo that let + the catalog drift and required a manual remote audit to repair. + +## References + +- `decisions/0007-release-publication-flow.md` — repository classes this builds on. +- `workspace/repos.yml` (meta-workspace) — per-repo branch model catalog. +- `decisions/0003-conventional-commits.md` — commit/branch naming conventions. diff --git a/decisions/0009-testing-ci-strategy.md b/decisions/0009-testing-ci-strategy.md new file mode 100644 index 000000000..53a8cf1cc --- /dev/null +++ b/decisions/0009-testing-ci-strategy.md @@ -0,0 +1,91 @@ +# 9. Testing and CI Strategy + +- **Status:** PROPOSED +- **Date:** 2026-05-29 +- **Deciders:** TBD +- **Supersedes:** None +- **Superseded by:** None + +## Context + +CI exists across the org's repos but its scope is decided per repo and per +workflow rather than from a shared policy. The result is uneven: some repos run +`zsh -n` syntax checks plus `zcompile`, others add ZUnit suites, `zsh-lint` has +Go tests, and the container repo builds a multi-arch matrix. There is no written +statement of *what level of testing each repository class owes*, which makes it +hard to know whether a repo is under- or over-tested, and what a reviewer should +require before merge. + +ADR-0007 classified repos by delivery model and ADR-0008 derived branching from +those classes. Testing depth should be derived the same way, so CI scope is a +property of the repo class rather than an accident of history. + +## Decision + +Required CI scope follows the ADR-0007 repository class. Every class shares a +common baseline; higher-risk classes add to it. + +### Baseline (all repos) + +- All workflows comply with the org workflow conventions: SHA-pinned actions, + top-level least-privilege `permissions:`, `concurrency:` for push/PR triggers, + no-emoji workflow/job `name:` (ADR-0005), kebab-case filenames. +- Zsh sources pass `zsh -n` (syntax) and `zcompile` (compile) checks. +- Commit/PR-title lint enforces Conventional Commits (ADR-0003) and rejects the + disallowed-trailer pattern. + +### By class + +1. **Continuously deployed artifacts** (`wiki`, `src`, `zd`) — build must succeed + on the development branch before deploy. Wiki runs lint (ESLint/Stylelint) and + a production build; `zd` runs the Docker build matrix; `src` validates the + installer/loader. CodeQL where a supported language is present. +2. **Versioned tools and packages** (`zunit`, `zsh-lint`, packaged `zsh`) — full + functional test suite is **required** and gates release tags. ZUnit for Zsh + tools; `go test` for the `zsh-lint` Go CLI. A release tag must not be cut from + a commit whose suite is red. +3. **Git-consumed source** (`zi`, most plugins/annexes) — **validation-only**: the + baseline checks above, plus ZUnit where the plugin ships tests. No release + automation and no coverage gate; these repos are consumed from source and the + bar is "does not break on load." +4. **Meta/infrastructure** (`.github`) — baseline plus workflow/markdown linting. + +### Coverage + +Coverage is **observed, not gated**, except where a class-2 tool chooses to set a +threshold for its own suite. The org does not impose a uniform coverage number; +ratcheting is a per-repo maintainer decision. + +### Required checks + +Each repo marks its class-appropriate checks as required for merge to its +publication branch (`main`, or `next`→`main` per ADR-0008). Validation-only repos +require the baseline; class-2 repos additionally require the functional suite. + +## Consequences + +- A reviewer can determine the expected CI bar from the repo's class instead of + reading each workflow. +- New repos start with the correct CI scope for their class. +- "Validation-only for git-consumed repos" is now an explicit rule, preventing + release machinery from creeping into class-3 repos (consistent with ADR-0007). +- The testing instruction file (`.github/instructions/testing.instructions.md`) + operationalizes this ADR for day-to-day authoring. + +## Alternatives considered + +- **Uniform full test suite + coverage gate everywhere.** Rejected: imposes + functional-suite and coverage overhead on git-consumed plugins where a load/ + syntax check is the meaningful bar, and slows low-churn repos for no benefit. +- **Leave CI scope per repo (status quo).** Rejected: produces uneven, undocumented + expectations and no shared definition of "tested enough to merge/release." +- **Centralize all CI into reusable workflows only.** Deferred: reusable workflows + (e.g. `zd`'s `test-native.yml`) are encouraged but mandating a single shared + pipeline is heavier than the class-based baseline this ADR sets. + +## References + +- `decisions/0007-release-publication-flow.md` — repository classes. +- `decisions/0008-branching-model.md` — branch model per class. +- `decisions/0005-workflow-naming-conventions.md` — workflow naming baseline. +- `z-shell/zd` `.github/workflows/test-native.yml` — reusable ZUnit workflow. diff --git a/decisions/0010-security-incident-response.md b/decisions/0010-security-incident-response.md new file mode 100644 index 000000000..5d8f9eccf --- /dev/null +++ b/decisions/0010-security-incident-response.md @@ -0,0 +1,100 @@ +# 10. Security Incident Response + +- **Status:** PROPOSED +- **Date:** 2026-05-29 +- **Deciders:** TBD +- **Supersedes:** None +- **Superseded by:** None + +## Context + +`.github/SECURITY.md` tells reporters *how to report* a vulnerability and the +coordinated-disclosure expectation. It says nothing about what the org does once +a report arrives: who owns it, how fast it is acknowledged and triaged, how a fix +is shipped, and what happens afterward. Without that, response time and quality +depend on whoever happens to see the report. + +The org ships shell that runs in users' interactive shells and a container image +used in CI, so a vulnerability can have broad blast radius. A written response +process — acknowledgement SLA, severity-based timelines, escalation, and a +post-incident review — closes the gap between "we accept reports" and "we handle +them predictably." + +## Decision + +### Ownership + +An org maintainer is the incident owner for each report. The owner acknowledges, +triages severity, coordinates the fix, and runs the post-incident review. By +default the accepting maintainer (**ss-o**) owns incidents unless explicitly +reassigned. + +### Acknowledgement SLA + +- Acknowledge a security report within **3 business days** of receipt. +- Triage to a severity within **5 business days**. + +### Severity and remediation targets + +Severity uses CVSS-style judgment (impact × exploitability). Target time-to-fix +or documented mitigation from triage: + +| Severity | Examples | Target | +| -------- | ------------------------------------------------- | --------------- | +| Critical | RCE, secret/credential exposure, supply-chain | **7 days** | +| High | Privilege escalation, auth bypass | **30 days** | +| Medium | Limited-scope info disclosure, DoS | **90 days** | +| Low | Hardening, defense-in-depth | Best effort | + +Targets are goals, not guarantees; the owner records the rationale when a target +slips. + +### Escalation + +If the owner cannot act within the acknowledgement SLA, the report is escalated +to another org maintainer. Critical incidents are worked immediately and may +warrant a temporary mitigation (yank a tag, pin a dependency, disable a workflow) +before the full fix. + +### Remediation and disclosure + +- Fixes land through the normal branch model (ADR-0008); critical fixes may use a + `hotfix-` branch from the publication branch. +- Coordinate disclosure with the reporter per `SECURITY.md`: no public disclosure + until a fix is published or the report is declined, and credit the reporter. +- Where a release artifact exists (ADR-0007 class 2), cut a patched tag and note + the security fix in the release notes. + +### Post-incident review + +For Critical and High incidents, the owner writes a short post-incident review: +timeline, root cause, fix, and a follow-up action (often a tracker issue) to +prevent recurrence. The review is kept in the owning repo or the tracker, not in +ephemeral notes. + +## Consequences + +- Reports get a predictable acknowledgement and remediation path instead of + ad-hoc handling. +- `runbooks/security-incident-response.md` operationalizes this ADR step by step. +- `SECURITY.md` remains the reporter-facing entry point; this ADR governs the + internal response. +- Post-incident reviews build durable security memory and feed the tracker. + +## Alternatives considered + +- **Keep only `SECURITY.md`.** Rejected: it covers intake but leaves response + undefined, which is where time is actually lost. +- **Adopt a formal external framework (e.g. full ISO/NIST IR process).** Rejected + as disproportionate for a small-maintainer OSS org; this ADR takes the + load-bearing pieces (SLA, severity targets, escalation, review) without the + overhead. +- **Per-repo security policies.** Rejected: vulnerabilities often span repos + (shared loader, container, plugins); one org-level process avoids gaps. + +## References + +- `.github/SECURITY.md` — reporter-facing reporting and disclosure policy. +- `runbooks/security-incident-response.md` — step-by-step responder runbook. +- `decisions/0007-release-publication-flow.md` — how patched releases are cut. +- `decisions/0008-branching-model.md` — hotfix branching for critical fixes. diff --git a/runbooks/deprecation.md b/runbooks/deprecation.md new file mode 100644 index 000000000..e4fbf4ee4 --- /dev/null +++ b/runbooks/deprecation.md @@ -0,0 +1,69 @@ +# Runbook — Deprecation and Sunset + +How to retire a plugin, annex, package, or other artifact without breaking the +users and tooling that depend on it. + +**Hard rule:** never delete a published artifact or break an install path +silently. Announce, provide a migration, then archive — in that order. + +## When to use this + +Use this when an artifact is no longer maintained or has been superseded: + +- a plugin/annex replaced by another or folded into core +- a package no longer published +- a repo that should become read-only + +Do not use this for routine workflow-hygiene cleanup (removing a stale CI +workflow) — that is ordinary maintenance, kept separate from release semantics. + +## Step 1 — Decide and record + +1. Confirm the artifact's class (ADR-0007) and what consumes it. +2. File a tracker issue describing the deprecation, the replacement (if any), and + the migration path. If the decision is non-obvious or cross-repo, draft an ADR + (`runbooks/adr.md`). +3. Identify every install path: `zi` ice, meta-plugin labels + (`z-a-meta-plugins`), wiki references, and the catalog `workspace/repos.yml`. + +## Step 2 — Announce + +- Add a deprecation notice to the repo README and the wiki page, stating the + status, the replacement, and the timeline. +- Where the artifact loads, prefer a non-fatal warning over a hard break. +- Label the tracker issue and link the announcement. + +## Step 3 — Provide a migration + +- Document the replacement and the exact steps to switch (new label, new repo, + new ice). +- Keep the old install path working through a transition window; do not remove it + the same day you announce. +- For git-consumed artifacts (class 3), the consumable ref must keep resolving + until the window closes. + +## Step 4 — Sunset + +After the transition window: + +- For a versioned artifact (class 2), cut a final tagged release noting end of + support; do not yank prior tags. +- Remove the artifact from meta-plugin maps, wiki ecosystem listings, and + `workspace/repos.yml` (or mark it archived there). +- Archive the repo (read-only) rather than deleting it, so existing references + and history remain resolvable. +- Close the tracker issue with the final state and links. + +## Anti-patterns + +- deleting a repo or yanking published tags, breaking existing installs +- removing an artifact from catalogs while it is still referenced elsewhere +- announcing and removing in the same change with no transition window +- mixing deprecation with unrelated release automation work + +## See also + +- `decisions/0007-release-publication-flow.md` +- `decisions/0008-branching-model.md` +- `runbooks/release.md` +- `runbooks/triage.md` diff --git a/runbooks/onboarding.md b/runbooks/onboarding.md new file mode 100644 index 000000000..c7b302b94 --- /dev/null +++ b/runbooks/onboarding.md @@ -0,0 +1,63 @@ +# Runbook — Maintainer Onboarding + +How to bring a new maintainer (or a new agent operator) up to speed on the +z-shell org's conventions, permissions, and where the source of truth lives. + +**Hard rule:** grant the least access that the role needs, and record who granted +it. Never share credentials or org secrets directly. + +## Step 1 — Read the governing docs + +Before touching anything, read, in order: + +1. `AGENTS.md` (org-level instructions) and the workspace `CLAUDE.md`. +2. `decisions/` — the accepted ADRs. These are the durable rules: + - 0001 meta-repo pattern, 0002 zi canonical, 0003 Conventional Commits, + 0005 workflow naming, 0006 wiki content roots, 0007 release flow, + 0008 branching model, 0009 testing/CI, 0010 security response. +3. `PATTERNS.md` and the relevant `runbooks/`. + +## Step 2 — Understand the source of truth + +- Active progress lives in **GitHub issues, PRs, and the Z-Shell Tracker** — not + in local notes or agent memory. +- `.gitmodules` is the Git-native source of truth for child-repo paths/remotes; + the meta-workspace `workspace/repos.yml` is the human/LLM-readable catalog. +- Durable decisions go in `decisions/`; long-form docs go in the wiki. + +## Step 3 — Permissions (least privilege) + +Grant only what the role requires; record the grant: + +- **Triage:** issue/PR triage and labels (per `runbooks/triage.md`). +- **Write:** branch + PR on assigned repos. Direct pushes to publication branches + are avoided; use PRs. +- **Maintain/Admin:** reserved for accepting ADRs (see `runbooks/adr.md` decision + authority), managing required checks, and org settings. +- Org secrets (e.g. `DISALLOWED_TRAILER_PATTERN`, project tokens) are never shared + in plaintext or inlined in workflow YAML. + +## Step 4 — Local environment + +- Clone via the meta-workspace; child repos are submodules. Note that **git + worktrees do not check out submodules** — child-repo work happens in the main + clone. +- Configure commit signing: commits are signed (`gpg.format=ssh`); set a + `user.signingkey`. CI rejects the disallowed-trailer pattern, so never add a + `Co-authored-by` trailer. +- Follow Conventional Commits and the branch model for the repo's class + (ADR-0008). + +## Step 5 — First contribution + +- Pick a `good first issue` or a triaged item. +- Branch per ADR-0008 (`feature-` from `next` or `main` by class). +- Run the class-appropriate checks locally (ADR-0009) before opening a PR. +- Leave an `Agent handoff` comment if the work will be resumed by someone else. + +## See also + +- `AGENTS.md` +- `decisions/` +- `runbooks/triage.md`, `runbooks/release.md`, `runbooks/adr.md` +- `runbooks/deprecation.md` diff --git a/runbooks/security-incident-response.md b/runbooks/security-incident-response.md new file mode 100644 index 000000000..32854584b --- /dev/null +++ b/runbooks/security-incident-response.md @@ -0,0 +1,81 @@ +# Runbook — Security Incident Response + +How to handle a security report from intake to post-incident review. This +operationalizes `decisions/0010-security-incident-response.md`. For reporter-facing +policy, see `.github/SECURITY.md`. + +**Hard rule:** never handle exploit details on a public thread. Move them to a +private channel immediately and keep them there until a fix is published. + +## Step 1 — Intake and acknowledge + +1. Confirm the report arrived through a private channel (security policy contact). + If it landed on a public issue/PR, hide exploit details and move it private. +2. Assign an incident owner — by default the accepting maintainer (**ss-o**), + unless reassigned. +3. Acknowledge to the reporter within **3 business days**. +4. Open a private tracking record (private issue or maintainer channel). Do not + put exploit details in a public tracker item. + +## Step 2 — Triage severity + +Within **5 business days**, assign a severity using impact × exploitability: + +| Severity | Examples | Target time-to-fix | +| -------- | --------------------------------------------- | ------------------ | +| Critical | RCE, secret/credential exposure, supply-chain | 7 days | +| High | Privilege escalation, auth bypass | 30 days | +| Medium | Limited-scope info disclosure, DoS | 90 days | +| Low | Hardening, defense-in-depth | Best effort | + +Record which repos/artifacts are affected and the blast radius (interactive +shell? CI container? a single plugin?). + +## Step 3 — Escalate if needed + +- If the owner cannot act within the acknowledgement SLA, escalate to another org + maintainer. +- For Critical incidents, consider an immediate temporary mitigation before the + full fix: yank or move a tag, pin a vulnerable dependency, or disable an + affected workflow. + +## Step 4 — Remediate + +1. Fix on a branch per ADR-0008. Critical fixes may use `hotfix-` from the + publication branch. +2. Add a regression test where the class allows it (ADR-0009). +3. For release-bearing repos (ADR-0007 class 2), cut a patched `vX.Y.Z` tag and + note the security fix in the release notes. +4. Keep the reporter updated on progress. + +## Step 5 — Disclose + +- Coordinate timing with the reporter per `SECURITY.md`: no public disclosure + until a fix is published or the report is declined. +- Credit the reporter unless they ask otherwise. +- After the fix ships, the public record (release notes / advisory) may describe + the issue at the appropriate level of detail. + +## Step 6 — Post-incident review (Critical / High) + +Write a short review and store it in the owning repo or tracker (never only in +ephemeral notes): + +- timeline (reported → acknowledged → triaged → fixed → disclosed) +- root cause +- the fix and any mitigation used +- one concrete follow-up action to prevent recurrence (file a tracker issue) + +## Anti-patterns + +- discussing exploit details on a public thread +- silent fixes with no reporter coordination or credit +- skipping the post-incident review for a Critical incident +- leaving severity untriaged past the SLA + +## See also + +- `decisions/0010-security-incident-response.md` +- `.github/SECURITY.md` +- `runbooks/triage.md` (security-report special case) +- `runbooks/release.md` From e6227f97d669843b7fcadd0832035e4d700f987a Mon Sep 17 00:00:00 2001 From: Salvydas Lukosius Date: Fri, 29 May 2026 04:53:27 +0100 Subject: [PATCH 03/12] fix(runbook): use GitHub Security Advisories for private incident tracking Public repos have no private issues; replace the "private issue or maintainer channel" intake step with GitHub repository Security Advisories (draft GHSA), which provides private collaboration, a private fork, and CVE issuance. Also generalize the incident-owner reference from a hardcoded handle to "security contact (currently ss-o)". --- runbooks/security-incident-response.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/runbooks/security-incident-response.md b/runbooks/security-incident-response.md index 32854584b..f29d6f7e6 100644 --- a/runbooks/security-incident-response.md +++ b/runbooks/security-incident-response.md @@ -9,13 +9,17 @@ private channel immediately and keep them there until a fix is published. ## Step 1 — Intake and acknowledge -1. Confirm the report arrived through a private channel (security policy contact). - If it landed on a public issue/PR, hide exploit details and move it private. -2. Assign an incident owner — by default the accepting maintainer (**ss-o**), - unless reassigned. +1. Confirm the report arrived through a private channel — prefer GitHub + **repository Security Advisories** ("Security" tab → "Report a vulnerability"), + which gives a private draft advisory, a private collaboration space and fork, + and CVE issuance. If it landed on a public issue/PR, hide exploit details and + move it into a draft advisory. +2. Assign an incident owner — by default the security contact (currently + **ss-o**), unless reassigned. 3. Acknowledge to the reporter within **3 business days**. -4. Open a private tracking record (private issue or maintainer channel). Do not - put exploit details in a public tracker item. +4. Track the incident in the draft GitHub Security Advisory (GHSA), not a public + issue — public repos have no private issues. Keep exploit details in the GHSA + only. ## Step 2 — Triage severity From 63dbd9a910b7433daa90f522d0595e3762c2353b Mon Sep 17 00:00:00 2001 From: Salvydas Lukosius Date: Fri, 29 May 2026 05:00:58 +0100 Subject: [PATCH 04/12] docs(governance): tighten ADR-0008/0009/0010 after architecture review ADR-0008: replace per-repo "use judgment" discretion with a canonical per-repo branch-model table that repos.yml derives from, so drift is structurally prevented; record updating CLAUDE.md as a post-acceptance action. ADR-0009: label Conventional-Commits/trailer enforcement as target-state (no live org-wide CI control exists yet), reference rather than restate workflow conventions, and add dependency-scanning (ADR-0004) plus SAST for compiled class-2 tools (ADR-0010). ADR-0010: name GitHub Security Advisories as the private intake/tracking channel, generalize the hardcoded owner to "security contact (currently ss-o)", and keep the severity table canonical here (runbook now references it instead of dupe). --- decisions/0008-branching-model.md | 77 +++++++++++++------- decisions/0009-testing-ci-strategy.md | 22 ++++-- decisions/0010-security-incident-response.md | 14 +++- runbooks/security-incident-response.md | 12 +-- 4 files changed, 80 insertions(+), 45 deletions(-) diff --git a/decisions/0008-branching-model.md b/decisions/0008-branching-model.md index 83782f084..861045cf8 100644 --- a/decisions/0008-branching-model.md +++ b/decisions/0008-branching-model.md @@ -29,37 +29,58 @@ than decided per repo, so the catalog stops drifting at the source. ## Decision -Branch model follows the ADR-0007 repository class. - -1. **Continuously deployed artifacts** (`wiki`, `src`, `zd` images) — use a - `next` → `main` integration branch. `next` is the default development branch; - merging to `main` is the deploy/publication boundary. Branch names: - `feature-`, `bug-`, `hotfix-`; hotfixes branch from `main`, all - other work from `next`. -2. **Versioned tools and packages** (`zunit`, `zsh-lint`, packaged `zsh`) — `main` - is continuously validated development output; publication is a `vX.Y.Z` tag - (per ADR-0007). A `next` branch is **optional**: adopt it only when the repo's - change volume justifies an integration buffer (as `zsh-lint` did for its Go - reboot). Trunk-on-`main` is the default for low-volume tools. -3. **Git-consumed source** (`zi`, most plugins/annexes) — `next` → `main` where an - integration branch adds value (`zi`, `zsh-eza`); trunk-on-`main` is acceptable - for small, low-churn plugins (`z-a-meta-plugins`, `zsh-fancy-completions`). - `main` is always the consumable ref. -4. **Meta/infrastructure** (`.github`) — trunk-based on `main`. No `next` branch. - -`workspace/repos.yml` records each repo's actual branch model and notes when a -repo is trunk-only. Whenever a repo adds or removes a `next` branch, the catalog -entry is updated in the same change. +Branch model follows the ADR-0007 repository class, and the **canonical +per-repo table below is the authoritative source** that `workspace/repos.yml` +derives from. There is no per-repo discretion: a repo's branch model is whatever +this table says, and changing it requires editing this ADR (a superseding +decision), not an ad-hoc branch creation. + +### Canonical branch model + +| Repo | Class | Branch model | Development branch | Publication boundary | +| ---------------------- | ----- | ---------------- | ------------------ | --------------------------- | +| `wiki` | 1 | `next` → `main` | `next` | merge to `main` (deploy) | +| `src` | 1 | `next` → `main` | `next` | merge to `main` (deploy) | +| `zd` | 1 | `next` → `main` | `next` | merge to `main` (image) | +| `zunit` | 2 | trunk on `main` | `main` | `vX.Y.Z` tag | +| `zsh-lint` | 2 | `next` → `main` | `next` | `vX.Y.Z` tag | +| packaged `zsh` | 2 | trunk on `main` | `main` | `vX.Y.Z` tag (deferred) | +| `zi` | 3 | `next` → `main` | `next` | `main` is consumable ref | +| `zsh-eza` | 3 | `next` → `main` | `next` | `main` is consumable ref | +| `z-a-meta-plugins` | 3 | trunk on `main` | `main` | `main` is consumable ref | +| `zsh-fancy-completions`| 3 | trunk on `main` | `main` | `main` is consumable ref | +| `.github` | 4 | trunk on `main` | `main` | n/a | + +### Rules + +- **Class 1 (deploy):** `next` → `main`; merging to `main` is the deploy boundary. +- **Class 2 (versioned tools):** `main` is continuously validated; publication is a + `vX.Y.Z` tag (ADR-0007). A `next` branch is used only where the table assigns it + (`zsh-lint`, for its Go reboot); the default is trunk-on-`main`. +- **Class 3 (git-consumed):** `main` is always the consumable ref. High-churn repos + use `next` → `main` (`zi`, `zsh-eza`); low-churn plugins are trunk-on-`main`. +- **Class 4 (meta):** trunk on `main`; no `next`. +- **Branch naming (all classes):** `feature-`, `bug-`, `hotfix-`. + Hotfixes branch from `main`; other work branches from the repo's development + branch (the "Development branch" column). For trunk repos, feature branches + also start from `main`. + +`workspace/repos.yml` mirrors this table and must match it. A repo's branch model +is not changed by creating a branch — it is changed by amending this ADR (or a +superseding ADR) and updating the catalog in the same change. ## Consequences -- `workspace/repos.yml` has an authoritative rule to validate against, instead of - drifting from empirical discovery. -- New repos inherit a branch model from their ADR-0007 class at creation time. -- The default-branch and branch-naming guidance in the workspace `CLAUDE.md` - ("default development branch `next`") is understood as the *integration-flow* - default, not a universal rule — trunk-only repos are explicitly sanctioned by - this ADR for classes 2–4 where noted. +- `workspace/repos.yml` derives from the canonical table above, so drift is + structurally prevented: the catalog is validated against an explicit table, not + "use judgment." +- New repos are added to the table (with their ADR-0007 class) as part of repo + creation, before the first branch is cut. +- **Action on acceptance:** the workspace `CLAUDE.md` currently states "default + development branch: `next` … all other work branches from `next`" as a universal + rule. On acceptance, update that section to reference this ADR's per-repo table + so agents do not get conflicting guidance for trunk-only repos. (Not done while + this ADR is PROPOSED — `CLAUDE.md` should not cite an unaccepted decision.) - Promotion from `next` to `main` is a publication boundary only for class 1 (deploy) repos; for other classes the merge validates but does not mint a release (consistent with ADR-0007). diff --git a/decisions/0009-testing-ci-strategy.md b/decisions/0009-testing-ci-strategy.md index 53a8cf1cc..236ba5bf8 100644 --- a/decisions/0009-testing-ci-strategy.md +++ b/decisions/0009-testing-ci-strategy.md @@ -27,12 +27,18 @@ common baseline; higher-risk classes add to it. ### Baseline (all repos) -- All workflows comply with the org workflow conventions: SHA-pinned actions, - top-level least-privilege `permissions:`, `concurrency:` for push/PR triggers, - no-emoji workflow/job `name:` (ADR-0005), kebab-case filenames. +- All workflows comply with the org workflow conventions (SHA-pinned actions, + least-privilege `permissions:`, `concurrency:`, no-emoji `name:` per ADR-0005, + kebab-case filenames). These conventions are defined in the workspace `CLAUDE.md` + and the CI instructions — this ADR references them rather than restating, so + there is a single source of truth. - Zsh sources pass `zsh -n` (syntax) and `zcompile` (compile) checks. -- Commit/PR-title lint enforces Conventional Commits (ADR-0003) and rejects the - disallowed-trailer pattern. +- Dependency and secret scanning per `decisions/0004-dependabot-unification.md`. +- **Target state (not yet a live org-wide control):** Conventional Commits + (ADR-0003) and the disallowed-trailer rule enforced in CI. Today the + disallowed-trailer check runs via the `DISALLOWED_TRAILER_PATTERN` org secret; + Conventional Commits is convention-and-review, not a uniform CI gate. Adding a + shared commit/PR-title-lint check is a follow-up, tracked separately. ### By class @@ -43,7 +49,9 @@ common baseline; higher-risk classes add to it. 2. **Versioned tools and packages** (`zunit`, `zsh-lint`, packaged `zsh`) — full functional test suite is **required** and gates release tags. ZUnit for Zsh tools; `go test` for the `zsh-lint` Go CLI. A release tag must not be cut from - a commit whose suite is red. + a commit whose suite is red. Compiled tools additionally run SAST (CodeQL + and/or `gosec` for the `zsh-lint` Go CLI); a release artifact is part of the + security surface governed by `decisions/0010-security-incident-response.md`. 3. **Git-consumed source** (`zi`, most plugins/annexes) — **validation-only**: the baseline checks above, plus ZUnit where the plugin ships tests. No release automation and no coverage gate; these repos are consumed from source and the @@ -88,4 +96,6 @@ require the baseline; class-2 repos additionally require the functional suite. - `decisions/0007-release-publication-flow.md` — repository classes. - `decisions/0008-branching-model.md` — branch model per class. - `decisions/0005-workflow-naming-conventions.md` — workflow naming baseline. +- `decisions/0004-dependabot-unification.md` — dependency scanning baseline. +- `decisions/0010-security-incident-response.md` — SAST/release security surface. - `z-shell/zd` `.github/workflows/test-native.yml` — reusable ZUnit workflow. diff --git a/decisions/0010-security-incident-response.md b/decisions/0010-security-incident-response.md index 5d8f9eccf..e99f28a5e 100644 --- a/decisions/0010-security-incident-response.md +++ b/decisions/0010-security-incident-response.md @@ -22,12 +22,20 @@ them predictably." ## Decision +### Intake channel + +Private reports are received and tracked through **GitHub repository Security +Advisories** (the "Report a vulnerability" / draft-advisory flow), which provides +private collaboration, a private fork for the fix, and CVE issuance. Public repos +have no private issues, so a draft advisory — not an issue — is the tracking +record. `SECURITY.md` is the reporter-facing entry point. + ### Ownership An org maintainer is the incident owner for each report. The owner acknowledges, -triages severity, coordinates the fix, and runs the post-incident review. By -default the accepting maintainer (**ss-o**) owns incidents unless explicitly -reassigned. +triages severity, coordinates the fix, and runs the post-incident review. The +**security contact** (currently **ss-o**) owns incidents by default unless +explicitly reassigned. ### Acknowledgement SLA diff --git a/runbooks/security-incident-response.md b/runbooks/security-incident-response.md index f29d6f7e6..649dab33b 100644 --- a/runbooks/security-incident-response.md +++ b/runbooks/security-incident-response.md @@ -23,14 +23,10 @@ private channel immediately and keep them there until a fix is published. ## Step 2 — Triage severity -Within **5 business days**, assign a severity using impact × exploitability: - -| Severity | Examples | Target time-to-fix | -| -------- | --------------------------------------------- | ------------------ | -| Critical | RCE, secret/credential exposure, supply-chain | 7 days | -| High | Privilege escalation, auth bypass | 30 days | -| Medium | Limited-scope info disclosure, DoS | 90 days | -| Low | Hardening, defense-in-depth | Best effort | +Within **5 business days**, assign a severity using impact × exploitability and +apply the time-to-fix target. The canonical severity→target table lives in +`decisions/0010-security-incident-response.md` ("Severity and remediation +targets") — use it as the single source rather than duplicating it here. Record which repos/artifacts are affected and the blast radius (interactive shell? CI container? a single plugin?). From f8d944ca9b96fe7ec75e6fe294c3f5357dc63b55 Mon Sep 17 00:00:00 2001 From: Salvydas Lukosius Date: Fri, 29 May 2026 05:03:28 +0100 Subject: [PATCH 05/12] fix(adr-0008): correct zd to trunk-only and reframe class as input not determinant Re-verified every repo's branch model via git ls-remote: next exists only on src, wiki, zi, zsh-lint, zsh-eza; all others are main-only. The prior table wrongly listed zd (class 1) as next->main. zd deploys directly from main. This also disproves "branch model follows the class": class 1 and class 2 are each split across next and trunk, so the per-repo table is authoritative and the ADR-0007 class is only an input (it sets the publication boundary and a default). --- decisions/0008-branching-model.md | 36 ++++++++++++++++++------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/decisions/0008-branching-model.md b/decisions/0008-branching-model.md index 861045cf8..494fdb189 100644 --- a/decisions/0008-branching-model.md +++ b/decisions/0008-branching-model.md @@ -29,11 +29,15 @@ than decided per repo, so the catalog stops drifting at the source. ## Decision -Branch model follows the ADR-0007 repository class, and the **canonical -per-repo table below is the authoritative source** that `workspace/repos.yml` -derives from. There is no per-repo discretion: a repo's branch model is whatever -this table says, and changing it requires editing this ADR (a superseding -decision), not an ad-hoc branch creation. +The **canonical per-repo table below is the authoritative source** for branch +model, and `workspace/repos.yml` derives from it. The ADR-0007 repository class +is an *input* to the choice (it sets the publication boundary and a default), but +it does **not** by itself determine the branch model — repo churn/scale does. +Reality confirms this: within class 1, `wiki`/`src` run `next` → `main` while +`zd` is trunk-only; within class 2, `zsh-lint` uses `next` while `zunit` is +trunk. So the table, not the class, is binding. There is no ad-hoc per-repo +discretion: a repo's branch model is whatever this table says, and changing it +requires amending this ADR (or a superseding one), not creating a branch. ### Canonical branch model @@ -41,7 +45,7 @@ decision), not an ad-hoc branch creation. | ---------------------- | ----- | ---------------- | ------------------ | --------------------------- | | `wiki` | 1 | `next` → `main` | `next` | merge to `main` (deploy) | | `src` | 1 | `next` → `main` | `next` | merge to `main` (deploy) | -| `zd` | 1 | `next` → `main` | `next` | merge to `main` (image) | +| `zd` | 1 | trunk on `main` | `main` | push to `main` (image) | | `zunit` | 2 | trunk on `main` | `main` | `vX.Y.Z` tag | | `zsh-lint` | 2 | `next` → `main` | `next` | `vX.Y.Z` tag | | packaged `zsh` | 2 | trunk on `main` | `main` | `vX.Y.Z` tag (deferred) | @@ -51,23 +55,25 @@ decision), not an ad-hoc branch creation. | `zsh-fancy-completions`| 3 | trunk on `main` | `main` | `main` is consumable ref | | `.github` | 4 | trunk on `main` | `main` | n/a | -### Rules +### How the class informs the default -- **Class 1 (deploy):** `next` → `main`; merging to `main` is the deploy boundary. +- **Class 1 (deploy):** `main` is the deploy ref. A `next` → `main` staging buffer + is used where deploy traffic justifies it (`wiki`, `src`); `zd` deploys directly + from `main`. - **Class 2 (versioned tools):** `main` is continuously validated; publication is a - `vX.Y.Z` tag (ADR-0007). A `next` branch is used only where the table assigns it - (`zsh-lint`, for its Go reboot); the default is trunk-on-`main`. + `vX.Y.Z` tag (ADR-0007). Default is trunk-on-`main`; `next` is used only where the + table assigns it (`zsh-lint`, for its Go reboot). - **Class 3 (git-consumed):** `main` is always the consumable ref. High-churn repos use `next` → `main` (`zi`, `zsh-eza`); low-churn plugins are trunk-on-`main`. - **Class 4 (meta):** trunk on `main`; no `next`. - **Branch naming (all classes):** `feature-`, `bug-`, `hotfix-`. Hotfixes branch from `main`; other work branches from the repo's development - branch (the "Development branch" column). For trunk repos, feature branches - also start from `main`. + branch (the "Development branch" column). For trunk repos, feature branches also + start from `main`. -`workspace/repos.yml` mirrors this table and must match it. A repo's branch model -is not changed by creating a branch — it is changed by amending this ADR (or a -superseding ADR) and updating the catalog in the same change. +`workspace/repos.yml` mirrors this table and must match it. Adding or removing a +repo's `next` branch is a decision recorded here first, then reflected in the +catalog in the same change. ## Consequences From 6f0b435c29dda291fee7d0ac7ffe87d48e791ee9 Mon Sep 17 00:00:00 2001 From: Salvydas Lukosius Date: Fri, 29 May 2026 07:06:27 +0100 Subject: [PATCH 06/12] docs(decisions): propose ADR 0011 for zsh-lint architecture --- ...zsh-lint-semantic-analyzer-architecture.md | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 decisions/0011-zsh-lint-semantic-analyzer-architecture.md diff --git a/decisions/0011-zsh-lint-semantic-analyzer-architecture.md b/decisions/0011-zsh-lint-semantic-analyzer-architecture.md new file mode 100644 index 000000000..f9c9e7e53 --- /dev/null +++ b/decisions/0011-zsh-lint-semantic-analyzer-architecture.md @@ -0,0 +1,53 @@ +# 11. zsh-lint Semantic Analyzer Architecture + +Date: 2026-05-29 + +## Status + +PROPOSED + +## Context + +`zsh-lint` is transitioning from a legacy interactive shell plugin to a standalone, Go-based semantic analyzer (Epic ZSH-3). The parser front end relies on `mvdan/sh/syntax`. We need a unified architecture for how the tool will traverse the Abstract Syntax Tree (AST), manage contextual state (like variable scoping), and evaluate linting rules. + +Shell scripts are highly dynamic, meaning a single-pass naive visitor pattern is often insufficient to detect complex issues (e.g., using a variable before it is declared, or aliasing). + +## Decision + +We will implement a **Two-Pass Analysis Architecture** for the semantic engine: + +1. **Pass 1: Context & Scope Resolution (The Indexer)** + - Traverses the `syntax.File` AST to build a `ScopeMap`. + - Records variable declarations, function definitions, and alias definitions. + - Determines the boundaries of local vs. global scope. +2. **Pass 2: Rule Evaluation (The Linter)** + - Traverses the AST a second time. + - Feeds each `syntax.Node` to a registry of initialized `Rule` implementations. + - Passes a rich `*AnalyzerContext` object alongside the node, which provides the rules access to the `ScopeMap` generated in Pass 1, as well as a `Report(diagnostic)` method. + +### The Rule Interface +To ensure extensibility, every rule must satisfy a strict interface: +```go +type Rule interface { + Name() string + Analyze(ctx *AnalyzerContext, node syntax.Node) +} +``` + +## Consequences + +### Positive +- **Decoupled Rules**: Rule authors do not need to worry about scope resolution; they can simply query `ctx.IsDeclared("varName")`. +- **Extensibility**: Adding a new lint rule requires only writing a struct that satisfies the `Rule` interface and registering it in the engine. +- **Precision**: Two passes allow the engine to detect "use before declaration" errors with high accuracy. + +### Negative +- **Performance Overhead**: Walking the AST twice per file is slower than a single pass. However, `mvdan/sh` is highly optimized in Go, so the impact on typical shell scripts should be negligible compared to the architectural clarity gained. +- **Complexity**: Managing the `AnalyzerContext` state between passes introduces slight complexity to the core engine. + +## Alternatives Considered + +1. **Single-Pass State Mutation**: A single traversal that builds scope and evaluates rules simultaneously. + - *Rejected* because shell functions can be declared at the bottom of a file but invoked at the top. A single pass would yield false positives for "undefined function" errors. +2. **Regex-Based Linting**: Using `regexp` against the raw file string. + - *Rejected* because it completely ignores the structural context of the shell grammar, leading to massive false-positive rates (e.g., matching a keyword inside a string literal). \ No newline at end of file From 560ba808da491e50ef32b020569e5e3d240de9f2 Mon Sep 17 00:00:00 2001 From: Salvydas Lukosius Date: Fri, 29 May 2026 07:27:42 +0100 Subject: [PATCH 07/12] chore(meta): secure workflows and unify agent instructions - Symlink AI instructions to central AGENTS.md to prevent fragmentation - Apply IDE-agnostic terminology across instructions - Ratify governance ADRs and align agent personas Part of Epic ZSH-16. --- .github/agents/github-actions-expert.agent.md | 2 +- .github/agents/workspace-architect.agent.md | 2 +- .github/copilot-instructions.md | 4 +--- .github/instructions/context-engineering.instructions.md | 2 +- .github/instructions/mcp-plugins.instructions.md | 6 +++--- CLAUDE.md | 6 +----- GEMINI.md | 1 + 7 files changed, 9 insertions(+), 14 deletions(-) mode change 100644 => 120000 .github/copilot-instructions.md mode change 100644 => 120000 CLAUDE.md create mode 120000 GEMINI.md diff --git a/.github/agents/github-actions-expert.agent.md b/.github/agents/github-actions-expert.agent.md index b429992c0..cbc6f6efc 100644 --- a/.github/agents/github-actions-expert.agent.md +++ b/.github/agents/github-actions-expert.agent.md @@ -1,7 +1,7 @@ --- name: "GitHub Actions Expert" description: "GitHub Actions specialist focused on secure CI/CD workflows, action pinning, OIDC authentication, permissions least privilege, and supply-chain security" -tools: [vscode, execute, read/readFile, agent, 'github/*', 'io.github.upstash/context7/*', 'github-ghas-tools/*', edit/editFiles, search, todo] +tools: [clarify, execute, read/readFile, agent, 'github/*', 'context7/*', 'github-ghas-tools/*', edit/editFiles, search, todo] --- # GitHub Actions Expert diff --git a/.github/agents/workspace-architect.agent.md b/.github/agents/workspace-architect.agent.md index 409f25b73..f0c833551 100644 --- a/.github/agents/workspace-architect.agent.md +++ b/.github/agents/workspace-architect.agent.md @@ -1,7 +1,7 @@ --- name: "Workspace Architect" description: "Use when organizing the Z-Shell multi-repo workspace, updating agent instructions, enforcing privacy boundaries (hooks), or curating meta-workspace memory." -tools: [read, edit, search, execute, vscode_askQuestions] +tools: [read, edit, search, execute, clarify] user-invocable: true --- You are the **Z-Shell Workspace Architect**, a specialized agent responsible for maintaining the organization's multi-repo meta-workspace layout, privacy constraints, and AI orchestration instructions. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index 176e521fb..000000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,3 +0,0 @@ -Follow the canonical instructions in [`../AGENTS.md`](../AGENTS.md). - -For shared progress and handoffs, also follow [`AGENT_MEMORY.md`](AGENT_MEMORY.md). diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 120000 index 000000000..be77ac83a --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1 @@ +../AGENTS.md \ No newline at end of file diff --git a/.github/instructions/context-engineering.instructions.md b/.github/instructions/context-engineering.instructions.md index fdacb6be6..b21f9071c 100644 --- a/.github/instructions/context-engineering.instructions.md +++ b/.github/instructions/context-engineering.instructions.md @@ -21,7 +21,7 @@ Principles for helping GitHub Copilot understand your codebase and provide bette ## Working with Copilot -- **Keep relevant files open in tabs**: Copilot uses open tabs as context signals. Working on auth? Open auth-related files. +- **Provide explicit file context**: If using a CLI agent or non-IDE assistant, provide explicit file paths. If using an IDE assistant, keep relevant files open in tabs. Working on auth? Reference or open auth-related files. - **Position cursor intentionally**: Copilot prioritizes code near your cursor. Put cursor where context matters. - **Use Copilot Chat for complex tasks**: Inline completions have minimal context. Chat mode sees more files. diff --git a/.github/instructions/mcp-plugins.instructions.md b/.github/instructions/mcp-plugins.instructions.md index ce12bdeee..781116e74 100644 --- a/.github/instructions/mcp-plugins.instructions.md +++ b/.github/instructions/mcp-plugins.instructions.md @@ -1,5 +1,5 @@ --- -description: "When and how to use the project MCP plugins (Context7, Cloudflare, Greptile) and Claude Code toolkits (pr-review-toolkit, hookify, security-guidance) across the Z-Shell workspace." +description: "When and how to use the project MCP plugins (Context7, Cloudflare, Greptile) and CLI Agent toolkits (pr-review-toolkit, hookify, security-guidance) across the Z-Shell workspace." applyTo: "**" --- @@ -44,9 +44,9 @@ maintainer workspace. Prefer these over guesswork or generic web search. - **When NOT to use:** single-file lookups where local grep or Read is faster. - **Auth required:** yes (OAuth). Read-only. -## Claude Code toolkits +## CLI Agent toolkits - **pr-review-toolkit:** reviewer, silent-failure, type-design, test, and comment subagents. Run on a diff before requesting human review. -- **hookify:** generate Claude Code hooks from recurring conversation mistakes. +- **hookify:** generate CLI hooks/heuristics from recurring conversation mistakes. - **security-guidance:** security review of pending changes before merge. diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index fd7469ea2..000000000 --- a/CLAUDE.md +++ /dev/null @@ -1,5 +0,0 @@ -# Claude instructions - -The canonical instructions for this repository and the wider z-shell organization live in [AGENTS.md](AGENTS.md). - -Also follow the GitHub-native handoff workflow in [.github/AGENT_MEMORY.md](.github/AGENT_MEMORY.md). diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 000000000..47dc3e3d8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/GEMINI.md b/GEMINI.md new file mode 120000 index 000000000..47dc3e3d8 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file From 7db1b3d7601fb1218a7e403f4495494ba7cd1aa8 Mon Sep 17 00:00:00 2001 From: Salvydas Lukosius Date: Fri, 29 May 2026 08:16:55 +0100 Subject: [PATCH 08/12] docs(agents): enforce strict curation criteria for workspace memory --- .github/agents/memory-curator.agent.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/agents/memory-curator.agent.md b/.github/agents/memory-curator.agent.md index 77b9a0698..7ab61a464 100644 --- a/.github/agents/memory-curator.agent.md +++ b/.github/agents/memory-curator.agent.md @@ -21,6 +21,23 @@ You manage two storage tiers: - Do not create new memory files inside `repos/org/z-shell-dot-github/memory/` — that directory was removed; write to the meta-workspace `memory/` only. - After editing any local memory file (except `user-profile.md`), always sync it to the Gist. +## Content Guidelines (What to Memorize) +Memory must be extremely high-signal, compact, and durable. + +- **DO SAVE:** + - **Organizational Heuristics:** Hard rules about file placement, AI usage, or repo boundaries (e.g., "Never use zdharma-continuum"). + - **Architectural Decisions:** Crucial setups that span the workspace (e.g., "Secrets are loaded via env.secrets.sh chmod 600"). + - **Cross-Repo Conventions:** Preferred frameworks, mandatory linting rules, or standardized paths. + - **User Feedback & Corrections:** Explicit negative feedback ("Don't do X again") or preferences. + +- **DO NOT SAVE (Transient Data):** + - Task progress, completed-work logs, or session outcomes. + - Specific PR numbers, issue numbers, or commit SHAs (unless documenting a permanently closed historical chapter). + - Temporary TODO state or next steps for a project. + - Artifacts that will be stale in a week. + +*If an insight is procedural (a sequence of commands to accomplish a task), it belongs in a shared script or skill, not memory. Keep memory declarative.* + ## Approach ### Saving a new insight From adf6d9a062d402c97b7ebb8945fba589ebca6402 Mon Sep 17 00:00:00 2001 From: Salvydas Lukosius Date: Mon, 1 Jun 2026 03:21:41 +0100 Subject: [PATCH 09/12] ci(workflows): enforce concurrency and permissions rules --- .github/workflows/dependency-review.yml | 4 ++++ .github/workflows/trunk.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 21dedc960..97eb02963 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -1,6 +1,10 @@ --- name: Dependency Review +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + on: workflow_call: {} diff --git a/.github/workflows/trunk.yml b/.github/workflows/trunk.yml index 78615efb0..416d36d5a 100644 --- a/.github/workflows/trunk.yml +++ b/.github/workflows/trunk.yml @@ -1,6 +1,10 @@ --- name: Trunk Code Quality +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + on: workflow_call: secrets: From 00bfb7c02069325c8b2c6dadb89a399e012c319f Mon Sep 17 00:00:00 2001 From: Salvydas Lukosius Date: Mon, 1 Jun 2026 03:49:59 +0100 Subject: [PATCH 10/12] docs(patterns): remove stale zsh-lint references --- PATTERNS.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/PATTERNS.md b/PATTERNS.md index e45267647..2d3afef2b 100644 --- a/PATTERNS.md +++ b/PATTERNS.md @@ -15,7 +15,6 @@ Observed in: - `repos/plugins/zsh-eza/zsh-eza.plugin.zsh` - `repos/plugins/zsh-fancy-completions/zsh-fancy-completions.plugin.zsh` - `repos/annexes/z-a-meta-plugins/z-a-meta-plugins.plugin.zsh` -- `repos/tools/zsh-lint/zsh-lint.plugin.zsh` Pattern: @@ -58,7 +57,6 @@ Observed in: - `repos/plugins/zsh-fancy-completions/zsh-fancy-completions.plugin.zsh` - `repos/annexes/z-a-meta-plugins/z-a-meta-plugins.plugin.zsh` -- `repos/tools/zsh-lint/zsh-lint.plugin.zsh` - `repos/plugins/zsh-eza/zsh-eza.plugin.zsh` Pattern: From 3329f72a855fe3fbf1774f59020a9a5d3df5bc76 Mon Sep 17 00:00:00 2001 From: Salvydas Lukosius Date: Mon, 1 Jun 2026 04:55:41 +0100 Subject: [PATCH 11/12] ci(workflows): enforce top-level concurrency --- .github/workflows/activity_metrics.yml | 7 ++++--- .github/workflows/gh-deploy-assets.yml | 7 ++++--- .github/workflows/lychee.yml | 7 ++++--- .github/workflows/metrics.yml | 7 ++++--- .github/workflows/pages-deployment.yml | 7 ++++--- .github/workflows/pagespeed.yml | 7 ++++--- .github/workflows/reader.yml | 7 ++++--- 7 files changed, 28 insertions(+), 21 deletions(-) diff --git a/.github/workflows/activity_metrics.yml b/.github/workflows/activity_metrics.yml index 9681efed0..e13bbb576 100644 --- a/.github/workflows/activity_metrics.yml +++ b/.github/workflows/activity_metrics.yml @@ -11,15 +11,16 @@ on: permissions: {} +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: metrics-activity: runs-on: ubuntu-latest timeout-minutes: 30 permissions: contents: write - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true env: projects_svg: metrics/plugin/projects/projects.svg org-people_svg: metrics/plugin/people/org-people.svg diff --git a/.github/workflows/gh-deploy-assets.yml b/.github/workflows/gh-deploy-assets.yml index b38c33359..e776ba696 100644 --- a/.github/workflows/gh-deploy-assets.yml +++ b/.github/workflows/gh-deploy-assets.yml @@ -9,15 +9,16 @@ on: permissions: {} +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + jobs: deploy: runs-on: ubuntu-latest environment: github-pages permissions: contents: write - concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true steps: - name: "⤵️ Check out code from GitHub" uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/.github/workflows/lychee.yml b/.github/workflows/lychee.yml index b04634884..3a365e96d 100644 --- a/.github/workflows/lychee.yml +++ b/.github/workflows/lychee.yml @@ -22,15 +22,16 @@ on: permissions: {} +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: links-check: runs-on: ubuntu-latest permissions: contents: read issues: write - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: "📤 Restore cache" diff --git a/.github/workflows/metrics.yml b/.github/workflows/metrics.yml index 5f0be9dc5..f6d351a7e 100644 --- a/.github/workflows/metrics.yml +++ b/.github/workflows/metrics.yml @@ -11,6 +11,10 @@ on: permissions: {} +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: metrics: runs-on: ubuntu-latest @@ -18,9 +22,6 @@ jobs: environment: metrics permissions: contents: write - concurrency: - group: ci-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true env: metrics_svg: metrics/plugin/metrics.svg repositories_metrics_svg: metrics/plugin/repositories_metrics.svg diff --git a/.github/workflows/pages-deployment.yml b/.github/workflows/pages-deployment.yml index 5bf532dbc..7c0ae794c 100644 --- a/.github/workflows/pages-deployment.yml +++ b/.github/workflows/pages-deployment.yml @@ -8,6 +8,10 @@ on: permissions: {} +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + jobs: deploy: name: Deploying @@ -17,9 +21,6 @@ jobs: permissions: contents: read deployments: write - concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true steps: - name: "⤵️ Check out code from GitHub" uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/.github/workflows/pagespeed.yml b/.github/workflows/pagespeed.yml index c79363005..89eb178d5 100644 --- a/.github/workflows/pagespeed.yml +++ b/.github/workflows/pagespeed.yml @@ -10,15 +10,16 @@ on: permissions: {} +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: pagespeed: runs-on: ubuntu-latest permissions: contents: write timeout-minutes: 30 - concurrency: - group: pagespeed-${{ github.ref }} - cancel-in-progress: true env: pagespeed_svg: metrics/plugin/pagespeed/detailed.svg pagespeed_url: ${{ secrets.PAGESPEED_TEST_URL }} diff --git a/.github/workflows/reader.yml b/.github/workflows/reader.yml index 318fa10a0..fe75d736e 100644 --- a/.github/workflows/reader.yml +++ b/.github/workflows/reader.yml @@ -11,15 +11,16 @@ on: permissions: {} +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: content-metrics: runs-on: ubuntu-latest timeout-minutes: 30 permissions: contents: write - concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true env: zsh_activity_svg: metrics/plugin/rss/zsh/activity.svg zsh_activity_url: https://sourceforge.net/p/zsh/activity/feed From 08264ce617f92ecfe528b6668e3471c375680783 Mon Sep 17 00:00:00 2001 From: Salvydas Lukosius Date: Mon, 1 Jun 2026 05:02:26 +0100 Subject: [PATCH 12/12] ci(zsh): pin setup action to immutable revision --- .github/workflows/zsh-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/zsh-ci.yml b/.github/workflows/zsh-ci.yml index e4534fcc7..092f3bfca 100644 --- a/.github/workflows/zsh-ci.yml +++ b/.github/workflows/zsh-ci.yml @@ -32,7 +32,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Zsh - uses: z-shell/.github/actions/setup-zsh@main + uses: z-shell/.github/actions/setup-zsh@48e314c59d04b54ae7f05d57570845f1a11a286a # main with: version: ${{ inputs.zsh-version }}