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
41 changes: 27 additions & 14 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,25 @@
# paths below are kept out of the signed source .zip that the [VOTE]
# votes on. Keep this list to VCS/CI/editor metadata — never source.
#
# IMPORTANT — do NOT export-ignore a directory that shipped files
# still reference (by markdown link, `test -f`, or symlink). The
# committed agent-view symlink dirs (`.claude/skills/`, `.github/skills/`,
# `.kiro/skills/`) relay through `.agents/skills/*` → `skills/*`, and
# specs test those paths, so `.agents/` MUST ship or every relay dangles
# in the archive. Likewise `.github/ISSUE_TEMPLATE/` and
# `.github/PULL_REQUEST_TEMPLATE.md` are linked by skills, and
# `projects/_template/.gitignore` is example content — so only the true
# CI/dev entries under `.github/` are stripped, and `.gitignore` is
# anchored to the repo root. `release-verify-rc` re-checks the unpacked
# tarball (symlink-lint + validators + no-.pyc) so a regression here
# fails the RC before the [VOTE].
# IMPORTANT — symlinks in the archive. The committed agent-view dirs are
# regenerable adoption wiring, not source. `.agents/skills/*` are the
# canonical view (single-hop symlinks straight to the real `skills/*`),
# and the relay dirs `.claude/skills/`, `.github/skills/`, `.kiro/skills/`
# just chain through `.agents/skills/*`. A safe extractor (ATR's upload
# validator) rejects a symlink whose target is itself a symlink (the
# relay chain reads as a target "outside" the entry) — so the relay dirs
# are export-ignored here, leaving only the single-hop `.agents/skills/*`
# links (which resolve to real `skills/*` dirs). Shipped files that used
# to link the relay view (`projects/_template/*.md`) point at
# `.agents/skills/*` instead. Keep `.agents/` in the release: dropping it
# too would strip the one resolvable view and orphan those links.
#
# `.github/ISSUE_TEMPLATE/` and `.github/PULL_REQUEST_TEMPLATE.md` are
# linked by skills, and `projects/_template/.gitignore` is example
# content, so only the true CI/dev entries under `.github/` are stripped
# and `.gitignore` is anchored to the repo root. `release-verify-rc`
# re-checks the unpacked tarball (symlink-lint + validators + no-.pyc) so
# a regression here fails the RC before the [VOTE].

# Root-only VCS / dev-tool metadata (anchored so identically-named files
# deeper in the tree — e.g. projects/_template/.gitignore — still ship).
Expand All @@ -49,8 +56,14 @@
# Editor metadata.
.idea/ export-ignore

# Regenerable agent-view relay symlink dirs — chains through `.agents/`
# that a safe extractor rejects. `.agents/skills/*` (single-hop → real
# `skills/*`) is kept; these relays are not.
.claude/skills/ export-ignore
.github/skills/ export-ignore
.kiro/skills/ export-ignore

# CI / bot config only. The rest of `.github/` (ISSUE_TEMPLATE,
# PULL_REQUEST_TEMPLATE.md, and the skills relay) is referenced by
# shipped skills and resolves via `.agents/`, so it stays in the release.
# PULL_REQUEST_TEMPLATE.md) is referenced by shipped skills, so it stays.
.github/workflows/ export-ignore
.github/dependabot.yml export-ignore
17 changes: 17 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,23 @@ repos:
entry: python3 tools/symlink-lint/src/symlink_lint/__init__.py
types: [symlink]
pass_filenames: false
# Release-archive guard: build the source archive exactly as the release
# does (git archive of the export tree, honouring .gitattributes
# export-ignore) and reject any symlink a safe extractor refuses — a
# dangling link (its target got export-ignored) or a chain (target is
# itself a symlink, which ATR's upload validator rejects as "outside the
# extraction directory"). This is what -1'd an RC upload; the hook fails
# the commit before an RC is cut. Re-fires whenever .gitattributes, any
# symlink, or the skills/agent-view tree that the archive ships changes.
- repo: local
hooks:
- id: symlink-lint-archive
name: symlink-lint (release archive)
language: system
entry: python3 tools/symlink-lint/src/symlink_lint/__init__.py --archive
files: ^(\.gitattributes|\.(agents|claude|github|kiro)/skills/.*|skills/.*|projects/_template/.*)$
types_or: [symlink, file]
pass_filenames: false
# Workspace-membership guard. The single source of truth for
# which Python projects get pre-commit hooks + the CI pytest
# matrix is the `[tool.uv.workspace] members` array in the root
Expand Down
2 changes: 1 addition & 1 deletion projects/_template/committer-onboarding-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,5 +249,5 @@ committer_governance_maintainer_roster:
- [`contributor-nomination-config.md`](contributor-nomination-config.md)
— nomination-brief thresholds and assessment window.
- [`pmc-roster.md`](pmc-roster.md) — PMC-member roster for vote tally.
- [`committer-onboarding`](../../.claude/skills/magpie-committer-onboarding/SKILL.md)
- [`committer-onboarding`](../../.agents/skills/magpie-committer-onboarding/SKILL.md)
— the skill that reads this configuration.
8 changes: 4 additions & 4 deletions projects/_template/security-intake-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,13 +234,13 @@ disclosure_governance:
rating reference, and public security policy URL.
- [`canned-responses.md`](canned-responses.md) — reporter-facing reply
templates shaped by the `reporter_acknowledgement_model` declared here.
- [`security-issue-import`](../../.claude/skills/magpie-security-issue-import/SKILL.md)
- [`security-issue-import`](../../.agents/skills/magpie-security-issue-import/SKILL.md)
— reads `security_inbox.kind`, `forwarders.enabled`, and the
`disclosure_governance` block for the acknowledgement step.
- [`security-cve-allocate`](../../.claude/skills/magpie-security-cve-allocate/SKILL.md)
- [`security-cve-allocate`](../../.agents/skills/magpie-security-cve-allocate/SKILL.md)
— reads `cve_authority.tool` and `governance.cve_allocation_gate`.
- [`security-issue-sync`](../../.claude/skills/magpie-security-issue-sync/SKILL.md)
- [`security-issue-sync`](../../.agents/skills/magpie-security-issue-sync/SKILL.md)
— reads `disclosure_governance.window_days`, `grace_period_days`, and
`pre_announce_distributors` for stale-window and pre-announcement checks.
- [`security-issue-import-via-forwarder`](../../.claude/skills/magpie-security-issue-import-via-forwarder/SKILL.md)
- [`security-issue-import-via-forwarder`](../../.agents/skills/magpie-security-issue-import-via-forwarder/SKILL.md)
— reads `forwarders.enabled` and the per-adapter config in `project.md`.
8 changes: 4 additions & 4 deletions tools/spec-loop/specs/mentoring-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,10 @@ a project can offer a first-time contributor.

```bash
test -f docs/mentoring/spec.md
test -f .claude/skills/magpie-good-first-issue-author/SKILL.md
test -f .claude/skills/magpie-mentoring-welcome/SKILL.md
test -f .claude/skills/magpie-contributor-to-committer/SKILL.md
test -f .claude/skills/magpie-good-first-issue-sweep/SKILL.md
test -f .agents/skills/magpie-good-first-issue-author/SKILL.md
test -f .agents/skills/magpie-mentoring-welcome/SKILL.md
test -f .agents/skills/magpie-contributor-to-committer/SKILL.md
test -f .agents/skills/magpie-good-first-issue-sweep/SKILL.md
uv run --project tools/skill-and-tool-validator --group dev skill-and-tool-validate
uv run --project tools/skill-evals skill-eval tools/skill-evals/evals/good-first-issue-author/
uv run --project tools/skill-evals skill-eval tools/skill-evals/evals/mentoring-welcome/
Expand Down
10 changes: 5 additions & 5 deletions tools/spec-loop/specs/pr-management-family.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,11 @@ is listed here for navigability since its domain is PR threads.
## Validation

```bash
test -f .claude/skills/magpie-pr-management-triage/SKILL.md
test -f .claude/skills/magpie-pr-management-stats/SKILL.md
test -f .claude/skills/magpie-pr-management-code-review/SKILL.md
test -f .claude/skills/magpie-pr-management-quick-merge/SKILL.md
test -f .claude/skills/magpie-pr-management-mentor/SKILL.md
test -f .agents/skills/magpie-pr-management-triage/SKILL.md
test -f .agents/skills/magpie-pr-management-stats/SKILL.md
test -f .agents/skills/magpie-pr-management-code-review/SKILL.md
test -f .agents/skills/magpie-pr-management-quick-merge/SKILL.md
test -f .agents/skills/magpie-pr-management-mentor/SKILL.md
test -f docs/pr-management/README.md
uv run --project tools/skill-and-tool-validator --group dev skill-and-tool-validate
```
Expand Down
20 changes: 10 additions & 10 deletions tools/spec-loop/specs/release-management-lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,16 +154,16 @@ code lands.
test -f docs/release-management/spec.md
test -f docs/release-management/process.md
test -f projects/_template/release-management-config.md
test -f .claude/skills/magpie-release-prepare/SKILL.md
test -f .claude/skills/magpie-release-keys-sync/SKILL.md
test -f .claude/skills/magpie-release-rc-cut/SKILL.md
test -f .claude/skills/magpie-release-vote-draft/SKILL.md
test -f .claude/skills/magpie-release-archive-sweep/SKILL.md
test -f .claude/skills/magpie-release-audit-report/SKILL.md
test -f .claude/skills/magpie-release-announce-draft/SKILL.md
test -f .claude/skills/magpie-release-verify-rc/SKILL.md
test -f .claude/skills/magpie-release-vote-tally/SKILL.md
test -f .claude/skills/magpie-release-promote/SKILL.md
test -f .agents/skills/magpie-release-prepare/SKILL.md
test -f .agents/skills/magpie-release-keys-sync/SKILL.md
test -f .agents/skills/magpie-release-rc-cut/SKILL.md
test -f .agents/skills/magpie-release-vote-draft/SKILL.md
test -f .agents/skills/magpie-release-archive-sweep/SKILL.md
test -f .agents/skills/magpie-release-audit-report/SKILL.md
test -f .agents/skills/magpie-release-announce-draft/SKILL.md
test -f .agents/skills/magpie-release-verify-rc/SKILL.md
test -f .agents/skills/magpie-release-vote-tally/SKILL.md
test -f .agents/skills/magpie-release-promote/SKILL.md
uv run --project tools/skill-and-tool-validator --group dev skill-and-tool-validate
uv run --project tools/skill-evals skill-eval tools/skill-evals/evals/release-announce-draft/
```
Expand Down
10 changes: 5 additions & 5 deletions tools/spec-loop/specs/repo-health-family.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,11 @@ grouped, prioritised report → wait for confirmation before any write.
## Validation

```bash
test -f .claude/skills/magpie-ci-runner-audit/SKILL.md
test -f .claude/skills/magpie-workflow-security-audit/SKILL.md
test -f .claude/skills/magpie-dependency-audit/SKILL.md
test -f .claude/skills/magpie-license-compliance-audit/SKILL.md
test -f .claude/skills/magpie-flaky-test-triage/SKILL.md
test -f .agents/skills/magpie-ci-runner-audit/SKILL.md
test -f .agents/skills/magpie-workflow-security-audit/SKILL.md
test -f .agents/skills/magpie-dependency-audit/SKILL.md
test -f .agents/skills/magpie-license-compliance-audit/SKILL.md
test -f .agents/skills/magpie-flaky-test-triage/SKILL.md
uv run --project tools/skill-and-tool-validator --group dev skill-and-tool-validate
```

Expand Down
4 changes: 2 additions & 2 deletions tools/spec-loop/specs/reviewer-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ source: >
but no skill turns that roster plus repository signal into an assignee
suggestion. triage-mode.md § What it does ("propose routing to the
right human") names the behaviour. Skill ships experimental in
.claude/skills/magpie-reviewer-routing/ with an eval suite under
.agents/skills/magpie-reviewer-routing/ with an eval suite under
tools/skill-evals/evals/reviewer-routing/.
acceptance:
- The skill is read-only on tracker state and proposes-then-confirms;
Expand Down Expand Up @@ -46,7 +46,7 @@ side: a grounded brief a human acts on, not a state change.

## Where it lives

- Skill: `reviewer-routing` under `.claude/skills/magpie-reviewer-routing/`,
- Skill: `reviewer-routing` under `.agents/skills/magpie-reviewer-routing/`,
in the Agentic Triage family alongside `pr-management-triage` and
`issue-triage`. Eval suite under `tools/skill-evals/evals/reviewer-routing/`.
- Roster source: the project's configured reviewer roster
Expand Down
4 changes: 2 additions & 2 deletions tools/spec-loop/specs/skill-reconciler.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ source: >
safety baseline that must stay eventually-consistent across every copy.
meta-and-quality-tooling.md (the skill-authoring/quality family this
joins). Skill ships experimental in
.claude/skills/magpie-skill-reconciler/ with an eval suite under
.agents/skills/magpie-skill-reconciler/ with an eval suite under
tools/skill-evals/evals/skill-reconciler/.
acceptance:
- The reconciler is read-only: it produces a structured diff and a
Expand Down Expand Up @@ -54,7 +54,7 @@ that a maintainer should not ignore.

## Where it lives

- Skill: `skill-reconciler` at `.claude/skills/magpie-skill-reconciler/`,
- Skill: `skill-reconciler` at `.agents/skills/magpie-skill-reconciler/`,
in the meta / quality family with `write-skill`, `optimize-skill`, and
`list-skills` (see [meta-and-quality-tooling.md](meta-and-quality-tooling.md)).
Eval suite under `tools/skill-evals/evals/skill-reconciler/`.
Expand Down
47 changes: 39 additions & 8 deletions tools/symlink-lint/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,32 @@ every other agent dir (`.claude/`, `.github/`, `.windsurf/`, `.goose/`,
canonical, not straight at source). Catches relays that bypass
`.agents/` — acyclic, so rule 1 alone would miss them.

**Dangling / unresolvable links are skipped**, never flagged: an adopter's
canonical links legitimately dangle until the gitignored `.apache-magpie/`
snapshot is installed. Broken-target detection is `setup verify`'s job.
**Dangling / unresolvable links are skipped** by rules 1–2, never flagged:
an adopter's canonical links legitimately dangle until the gitignored
`.apache-magpie/` snapshot is installed. Broken-target detection is
`setup verify`'s job.

3. **Release archive is extractor-safe** (run with `--archive`). Builds the
source archive exactly as the release does — `git archive
--worktree-attributes` of the *staged* tree (`git write-tree`), honouring
`.gitattributes` `export-ignore` — then rejects any symlink in it that a
safe extractor refuses:
- **chain** — the link's target is itself a symlink. A validator that
will not follow a symlink-to-symlink (ASF's [ATR](https://release-test.apache.org/)
upload validator does exactly this) reads the target as escaping the
extraction directory and rejects the whole upload. This is the shape
that `-1`'d an RC: the `.claude` / `.github` / `.kiro` relay dirs chain
through `.agents/`, so they are `export-ignore`d and only the single-hop
`.agents/skills/*` view (which points straight at real `skills/*`)
ships.
- **dangling** — the link's target is absent from the archive (its real
file got `export-ignore`d), orphaning the link.

Unlike rules 1–2 this inspects the archive, not the working tree, so a
dangling link here *is* a defect — it means the RC would ship a broken
link. Runs as the `symlink-lint-archive` hook (below); `release-verify-rc`
re-checks the same property against the unpacked tarball before the
`[VOTE]`.

## Prerequisites

Expand All @@ -62,7 +85,8 @@ snapshot is installed. Broken-target detection is `setup verify`'s job.
## How to use

```bash
python3 tools/symlink-lint/src/symlink_lint/__init__.py
python3 tools/symlink-lint/src/symlink_lint/__init__.py # rules 1–2 (working tree)
python3 tools/symlink-lint/src/symlink_lint/__init__.py --archive # rule 3 (release archive)
# or, once the workspace is synced:
uv run --project tools/symlink-lint symlink-lint
```
Expand All @@ -71,10 +95,17 @@ Exit `0` if clean; `1` otherwise, with each offender printed to stderr.

## Wiring

Runs as the `symlink-lint` [prek](https://github.com/j178/prek) hook
(`.pre-commit-config.yaml`), fired on any staged symlink and always on
`prek run --all-files` (CI). Behaviour is locked by the pytest suite under
[`tests/`](tests/), run by the workspace `pytest` hook + CI matrix.
Two [prek](https://github.com/j178/prek) hooks (`.pre-commit-config.yaml`):

- `symlink-lint` — rules 1–2, fired on any staged symlink and always on
`prek run --all-files` (CI).
- `symlink-lint-archive` — rule 3 (`--archive`), fired whenever
`.gitattributes`, any agent-view `skills/` tree, `skills/`, or
`projects/_template/` changes (the inputs that shape the release archive),
and always on `prek run --all-files`.

Behaviour is locked by the pytest suite under [`tests/`](tests/), run by the
workspace `pytest` hook + CI matrix.

## Tests

Expand Down
Loading