<!-- SPDX-License-Identifier: CC-BY-SA-4.0 -->
<!-- SPDX-FileCopyrightText: Netresearch DTT GmbH -->
# retro — session retrospectives for agent systems
**LLM-driven session retrospection for Claude Code and Codex.** Improve accepted task completion at the required quality within economically feasible time and cost. `/retro` examines friction, unmet goals and reusable successes across instructions, knowledge, tools, environment and collaboration, then routes useful measures to seven destinations.
[](LICENSE-MIT)
[](https://github.com/netresearch/retro-skill/actions/workflows/lint.yml)

> Where continuous friction-detection hooks pile up write-only noise nobody triages, `/retro` reads what actually happened and hands you a short list of approved, durable learnings — a global rule, project rules, or skill PRs.
## Contents
- [What it does](#what-it-does)
- [Why](#why)
- [Requirements](#requirements)
- [Install](#install)
- [Usage — the six modes](#usage--the-six-modes)
- [A worked example](#a-worked-example)
- [The seven destinations](#the-seven-destinations)
- [How it works](#how-it-works)
- [How it stays safe](#how-it-stays-safe)
- [Honest limitations](#honest-limitations)
- [Optional auto-trigger](#optional-auto-trigger)
- [Repository layout](#repository-layout)
- [Related projects](#related-projects)
- [Tests](#tests)
- [Dependencies](#dependencies)
- [Governance and policies](#governance-and-policies)
- [Contributing](#contributing)
- [License](#license)
## What it does
`/retro` analyzes the current session from its transcript without continuous background hooks. It classifies useful findings into seven destinations, asking first who owns the truth and whether a rule still serves its purpose. It checks the existing mandate for each target, action and audience, executes covered changes and asks only for uncovered decisions.
A single sweep returns at most ten actionable proposals, grouped by destination, each with a short *why* and a *how-to-apply*.
The [learning contract](skills/retro/references/learning-contract.md) distinguishes
observations, facts, decisions, preferences and hypotheses. Mad/Sad/Glad are
brief evidence-seeking perspectives, not claimed emotions. A first success may
yield a bounded learning; a justified no-op is equally valid. Written, accepted,
available, applied and effective remain separate states. Green CI and an
unreverted merge alone do not prove a better approach. Claims stay bounded to
tested tasks and agent configurations, including cheaper executors actually
used. Efficiency includes retro, verification, delegation and human rework.
## Why
The prior approach — the Coach plugin — used continuous friction-detection hooks that turned out to be write-only noise. The field evidence was stark:
- `~/.claude-coach/candidates.json` held **1011 pending / 0 approved / 0 rejected**.
- A **35 MB** `events.sqlite` produced roughly **35× duplicate fingerprints** of the same issue.
An LLM reading the *actual* transcript classifies friction more accurately and far more cheaply, and returns **≤10** actionable proposals instead of 1011 candidates nobody triages. That is the entire premise of `/retro`: one efficient pass over what really happened, not a firehose of background candidates.
## Requirements
| Tool | Used for |
|---|---|
| Claude Code | The host that runs the plugin and the `/retro` command |
| `uv` | Runs every Python script on the Python 3.11 or later its header declares (PEP 723), downloading that interpreter when the host has none; `python3` alone is not enough, macOS ships 3.9. For mechanical polling detection, session scope and review feedback (`detect-mechanical.py`, `derive-session-scope.py`, `collect-review-findings.py`) it also installs the shell parser tree-sitter-bash the scripts declare; the first run downloads it |
| `jq` | Skill discovery, manifest parsing, and the optional hook |
| `gh` and/or `glab` | Creating pull/merge requests for skill updates |
| `git` | Cloning source repos and materializing changes |
## Install
`/retro` ships as a Claude Code plugin, distributed through the [Netresearch marketplace](https://github.com/netresearch/claude-code-marketplace). Add the catalog once, then install `retro` from it — both steps inside Claude Code:
```text
/plugin marketplace add netresearch/claude-code-marketplace
/plugin install retro@netresearch-claude-code-marketplace
```
The catalog entry points back at this repository as its source, so the code still comes from here. `marketplace add` needs a `.claude-plugin/marketplace.json` catalog, which this repo does not ship — pointing it at `netresearch/retro-skill` fails with `Marketplace file not found`.
### Without a marketplace
Since Claude Code 2.1.157, a plugin directory under your personal skills directory loads on its own:
```bash
mkdir -p ~/.claude/skills
git clone https://github.com/netresearch/retro-skill.git ~/.claude/skills/retro
```
It loads as `retro@skills-dir` on the next session, hooks and commands included. Update with `git -C ~/.claude/skills/retro pull` and start a new session; remove it by deleting the directory. There is no `claude plugin update` on this route.
Alternatively, install via Composer (the skill-repo convention):
```bash
composer require netresearch/retro-skill
```
## Usage — the six modes
| Command | Mode | When to use |
|---|---|---|
| `/retro` | **Sweep** — analyze the entire current session; returns ≤10 proposals grouped by destination | At session end, or when friction has accumulated |
| `/retro "<problem>"` | **Spotlight** — focus on one described issue; fewer tokens than a full sweep | Mid-session, for a direct fix |
| `/retro outcome [session-id\|--since N]` | **Outcome** (layer D) — replay a *past* session through what happened to its output afterwards (reverted commits, rejected PRs, CI failures, follow-up fix sessions) | Periodically, e.g. monthly. **Do not run within 24h of the session** — the outcomes have not landed yet |
| `/retro audit [--scope project\|repo\|skill]` | **Constitutional audit** — cross-session architectural review (design drift, convention erosion) over weeks/months | Monthly or quarterly health check |
| `/retro promote` | **Promote** — inventory accumulated local memory and re-home each note upward; retain active access until the accepted target is delivered and reachable, then drain | When local memory has piled up and needs its canonical owner |
| `/retro done` | **Done** — definition-of-done gate: eight evidence-backed checks (task, findings, retro, cleanup, questions, tickets, time, docs) | As the last command of a session, before calling it finished |
Sweep and Spotlight ask what caused friction, what benefit remains unmet and
what worked under which conditions. Outcome and Audit examine availability,
later application and benefit of previous decisions across suitable tasks.
## A worked example
The following is an **illustrative example** of a Sweep, not a captured transcript — your output will differ.
```text
> /retro
Analyzed session (4200 words). Mechanical pre-pass + LLM enrichment found 3 findings.
[skill-update] git-workflow — DCO sign-off missing
Why: Two commits this session were rejected by the DCO check; the skill
never mentions `git commit -s`.
How: PR to the git-workflow SOURCE repo adding a sign-off rule + eval stub.
[project-rule] AGENTS.md — use bun, not npm
Why: You corrected the assistant twice ("we use bun here").
How: Append a titled rule to <project>/AGENTS.md.
[personal-rule] CLAUDE.md — always fetch before reasoning about origin/<branch>
Why: A stale ref caused a rejected push, then a forced retry.
How: Append a titled rule to ~/.claude/CLAUDE.md.
Approve / edit / reject each? [1] a [2] e [3] r
> ...
Report
PRs opened: 1 (git-workflow: feat: require DCO sign-off)
Files written: 2 (<project>/AGENTS.md, ~/.claude/CLAUDE.md)
```
The example assumes these changes were not already authorized. Existing
permission for the same target, action and audience is reused; a new audience,
product decision or team norm outside that mandate still needs its owner.
## The seven destinations
Every finding routes to exactly one destination — chosen authority-first: before asking where to *store* a learning, retro asks who should *own* the truth. A fact about the world (tool behaviour, an API, a standard) belongs to its canonical owner outside the agent system; a skill is the canonical source only for agent behaviour and its own procedure:
| Destination | When | Materializes to |
|---|---|---|
| `canonical-source` | The fact's canonical owner is an artefact outside the agent system (upstream docs, code, schema, handbook, or a team record: inventory, ticket, repository docs, runbook, wiki) | Open a PR/patch against the owning artefact, or edit the team record through its integration after approval; the skill keeps a reference + agent-specific delta |
| `personal-rule` | A personal, cross-project preference | Append a titled rule to `~/.claude/CLAUDE.md` (the always-loaded global rules file) |
| `project-rule` | A convention for *this* project | Append a titled rule to `<project>/AGENTS.md` |
| `skill-update` | An existing skill is wrong, weak, under-triggering, or carries an obsolete instruction (removal is a valid edit) | Open a PR against the skill's **source repo** (never the plugin cache) |
| `new-skill` | A skill-shaped gap no skill covers | Scaffold a brand-new skill repo via the `skill-repo` convention |
| `checkpoint` | A mechanical check worth gating on | Add a YAML entry to the target skill's `checkpoints.yaml` |
| `harness-artefact` | A repo-infrastructure gap | Bootstrap a hook / CI / template via `agent-harness` |
`personal-rule` was called `user-memory` in earlier versions. The old name stays valid as input — a user's phrasing, an older proposal, an archived report — and is always reported back as `personal-rule`. It materializes a durable instruction in the always-loaded global rules file, which is not a memory of past sessions, and the name said otherwise.
## How it works
The pipeline is built in layers (the project calls them *Schicht* A/B/C/D — layer A/B/C/D). The deterministic layer runs first to cut token cost; the LLM is always the primary classifier.
1. **Mechanical pre-pass (layer A)** — `skills/retro/scripts/detect-mechanical.py` parses the transcript for exactly **20** deterministic signals (A1–A20): tool errors, retry clusters, output verbosity, tool-call inefficiency, sequential-vs-parallel, user-correction phrases, prompt/prompt-sequence/tool-sequence repetition, skill-reminder-vs-invoke, wrong-tool choice, re-read-same-file, skipped verification, work on `main`/`master`, bot attribution in commits, outdated-tool warnings, upstream failure, permission re-approval, repeated command shapes, and wait-loop polling. Deterministic; it does **not** classify.
Then `skills/retro/scripts/collect-review-findings.py` reads native PR/MR review threads and linked issue feedback, including issues a GitHub PR names as `GH-N` in its title or description (GitHub's own reference syntax). Other tracker integrations supply normalized local evidence with `--feedback-file`. Short branch/title/tool references remain contextual hints until explicitly resolved; they never select a tracker. See [the feedback contract](skills/retro/references/feedback-contract.md) for delegation, coverage states and migration from the removed Jira CLI flags.
2. **LLM enrichment (layer B)** — adds **21** inferential signals, B16–B21 of them reusable-learning signals (wrong skill choice, skill capability gap, hallucination, convention violation, missing skill, repeated mistake, assumption-without-asking, doc drift, dug-out knowledge, …) and filters layer-A false positives. Includes a trigger-coverage sweep over every installed skill's description.
3. **Cross-session enrichment (layer C, optional)** — scans `~/.claude/projects/<slug>/*.jsonl` across projects via `skills/retro/scripts/scan-cross-session.py`. **6** signals: same-friction-again, cross-project pattern, memory drift, ineffective skill update, follow-up-fix session, written rule violated repeatedly.
4. **Skill discovery (runtime)** — `skills/retro/scripts/find-org-skills.py` lists every skill in every configured marketplace, installed or not, to match the friction topic against its description and resolve the source-repo URL; `skills/retro/scripts/find-installed-skills.sh` adds on-disk paths and git remotes of installed skills.
5. **Classification** — map each finding to one of the seven destinations, authority first (`skills/retro/references/classification-heuristic.md`), using the catalogue from step 4.
6. **Eval consultation** — if the matched skill has an `evals/` directory, read it for context and propose an eval stub (TDD style). retro ships its **own** evals under `skills/retro/evals/` testing its classification, validated by `skills/retro/scripts/validate-evals.py`.
7. **Proposal generation** — per finding: a *Why* paragraph and a *How-to-apply* paragraph, grouped by destination, ≤10 items.
8. **Authorization** — reuse the valid mandate; approve / edit / reject only uncovered decisions.
9. **Materialization** — per-destination convention. PRs use Conventional Commits with DCO sign-off (`git commit -s`; without it the PR is **BLOCKED** even when all checks pass), preserve signing, and verify private-repo and audience authority.
10. **Report** — a summary table of created PRs and written files.
The full signal catalog lives in [`skills/retro/references/friction-catalog.md`](skills/retro/references/friction-catalog.md).
## How it stays safe
- **No silent writes.** Verify existing permission for target, action and audience; report covered changes and ask only for decisions outside that mandate.
- **Patches target the source repo, never the cache.** `~/.claude/plugins/cache/` is overwritten on every plugin update; edits there would be lost. `/retro` clones the source repo (or uses an existing worktree) and opens a PR via `gh` / `glab`.
- **The LLM classifies; the pre-pass only saves tokens.** The deterministic layer A never decides a destination.
- **What the scripts read, write and send** — and where masking of transcript text stops — is set out in the [security assurance case](docs/SECURITY-ASSURANCE.md).
- **Never:** auto-merge; harness-invented attribution in commits or PRs ("Generated with Claude Code", `Co-Authored-By: Claude`) — a disclosure trailer the user's own rules prescribe is required rather than banned; `--no-verify`; patching the cache; hardcoding a static skill list; generating 1000+ candidates.
## Honest limitations
`/retro` detects friction **observable in or near the session**. It does **not** detect:
- **Silent badness** — choices that "work" but are wrong and generate no friction signal.
- **External signals outside forge and tracker** — customer complaints, production alerts, Slack/Matrix/Sentry feedback. Native PR/MR and linked-issue feedback is read; other tracker feedback is supplied through its owning integration.
- **Constitutional drift over time** *without* `audit` mode — per-session retro can't see slow erosion.
- **Outcomes the agent never saw** — unless work was reverted, a PR rejected, or a follow-up session occurred.
For the last two, use `/retro outcome` (post-hoc) or `/retro audit` (cross-session). Ingesting error trackers, monitoring and chat is a **future direction**, not a shipped feature.
## Optional auto-trigger
There is an opt-in `SessionEnd` hook (`hooks/session-end.json`) that **only prints a reminder** for sessions over 1000 words — it never auto-runs `/retro`:
```text
Session was non-trivial (4200 words). Run /retro to extract learnings.
```
It is **off by default**. Claude Code does **not** load hooks from a `~/.claude/hooks/` directory — hooks are read only from `settings.json`. To enable the reminder, copy the `hooks` object out of `hooks/session-end.json` and **merge** it into `~/.claude/settings.json` (all projects) or `.claude/settings.json` (one project):
```json
{
"hooks": {
"SessionEnd": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "bash -c 'tp=$(jq -r \".transcript_path // empty\" 2>/dev/null); [ -z \"$tp\" ] || [ ! -r \"$tp\" ] && exit 0; tokens=$(wc -w < \"$tp\" 2>/dev/null || echo 0); if [ \"${tokens:-0}\" -gt 1000 ]; then printf \"Session was non-trivial (%s words). Run /retro to extract learnings.\\n\" \"$tokens\"; fi'"
}
]
}
]
}
}
```
Do not rename the file to `hooks/hooks.json` — that would make Claude Code auto-load it and break the off-by-default contract.
## Repository layout
```text
retro-skill/
├── skills/retro/ # the self-contained skill subtree (ships via npx-skills)
│ ├── SKILL.md # main skill definition (all modes)
│ ├── checkpoints.yaml # skill quality gates
│ ├── references/ # 12 reference docs
│ │ ├── feedback-contract.md
│ │ ├── friction-catalog.md
│ │ ├── destination-taxonomy.md
│ │ ├── classification-heuristic.md
│ │ ├── skill-discovery.md
│ │ ├── patch-workflow.md
│ │ ├── eval-integration.md
│ │ ├── project-harness-inspection.md
│ │ ├── documentation-lookup.md
│ │ ├── promote-mode.md
│ │ ├── done-mode.md
│ │ └── workflow.md
│ ├── evals/ # retro's own classification evals (dogfood)
│ │ ├── README.md
│ │ └── *.md # validated by skills/retro/scripts/validate-evals.py
│ └── scripts/
│ ├── detect-mechanical.py # layer-A pre-pass
│ ├── derive-session-scope.py # repositories, artefacts and days a session touched
│ ├── collect-review-findings.py # review, issue and ticket feedback on the session's PRs/MRs
│ ├── feedback-contract.py # validates supplied tracker feedback (--feedback-file)
│ ├── mask-secrets.py # credential masking for the text five scripts quote
│ ├── opencode-transcript.py # renders an opencode session as layer-A JSONL
│ ├── codex-transcript.py # renders a Codex rollout as layer-A JSONL
│ ├── scan-memory-inventory.py # Promote: memory backlog pre-pass
│ ├── scan-cross-session.py # layer-C JSONL scanner
│ ├── find-org-skills.py # runtime skill discovery (catalogue + installed)
│ ├── find-installed-skills.sh # installed-only detail (paths, remotes)
│ ├── check-upstream-sources.py # canonical-source drift check
│ ├── materialize-pr.sh # skill-update PR helper
│ ├── check-eval-samples.py # refuses a new or tightened eval without samples
│ └── validate-evals.py # validates retro's own evals (RT-40..42)
├── commands/retro.md # /retro slash command (Claude Code plugin only)
├── hooks/session-end.json # optional SessionEnd reminder (off by default; plugin-level, outside skills/retro/)
├── tests/ # test_*.py, run with python -m unittest discover -s tests
├── docs/specs/ # retro-skill.md (original spec, superseded), retro-promote-mode.md
├── docs/opencode-live-test.md # checking the opencode adapter against a real, isolated opencode 2.x
├── .github/workflows/ # lint.yml, validate.yml, release.yml, auto-merge-deps.yml, security.yml, scorecard.yml, labeler.yml, check-template-drift.yml, eval-validate.yml, harness-verify.yml
├── AGENTS.md
├── composer.json
├── .claude-plugin/plugin.json
├── LICENSE-MIT
└── LICENSE-CC-BY-SA-4.0
```
## Related projects
`/retro` materializes into conventions defined by sibling skills:
| Project | Role |
|---|---|
| [agent-harness-skill](https://github.com/netresearch/agent-harness-skill) | Verifies integration points; bootstraps `harness-artefact` materializations |
| [agent-rules-skill](https://github.com/netresearch/agent-rules-skill) | Feedback-memory schema for `project-rule` materialization |
| [skill-repo-skill](https://github.com/netresearch/skill-repo-skill) | PR/branch convention for `skill-update`; scaffolding for `new-skill` |
| [automated-assessment-skill](https://github.com/netresearch/automated-assessment-skill) | Checkpoint YAML schema for `checkpoint` materialization |
Deeper reading: the [`skills/retro/references/`](skills/retro/references/) docs, [`AGENTS.md`](AGENTS.md), and the original spec at [`docs/specs/retro-skill.md`](docs/specs/retro-skill.md), which is superseded and kept as a historical record.
## Tests
The behavioural tests live in `tests/test_*.py` and use the standard library's `unittest`. Run them from the repository root with the commands CI runs (CI runs them once per version of its matrix):
```bash
uv run --python 3.11 --with tree-sitter==0.26.0 --with tree-sitter-bash==0.25.1 python -m unittest discover -s tests -v
uv run --python 3.11 python -m py_compile skills/retro/scripts/*.py
uv run --python 3.11 python skills/retro/scripts/validate-evals.py
```
`--python` matters here: `uv run python …` reads no script header and takes the interpreter from `.python-version` or the system, which can be older than 3.11.
The tests run on the Python 3.11 or later that the commands above select, and need `bash`, `git`, `jq` and `uv` on `PATH`, and no network: `gh` is either a stub script on `PATH` (`tests/test_materialize_pr.py`) or replaced by an injected runner that returns recorded responses (`tests/test_collect_review_findings.py`, `tests/test_tracker_neutrality.py`). Transcripts, opencode databases, memory stores, plugin caches and git repositories are built in temporary directories.
Each script under `skills/retro/scripts/` except `feedback-contract.py` has its own test file, named after it (`detect-mechanical.py` → `tests/test_detect_mechanical.py` and `tests/test_detect_mechanical_review.py`); `feedback-contract.py` is exercised through `tests/test_tracker_neutrality.py`, and the two shell scripts are run for real by `tests/test_find_installed_skills.py` and `tests/test_materialize_pr.py`. `check-upstream-sources.py` is tested on its offline paths only. `skills/retro/evals/*.md` are scenarios for grading a retro transcript; `validate-evals.py` checks their structure, it does not run them.
The [behavior evaluation guide](skills/retro/references/eval-integration.md)
connects selected Markdown cases to the existing shared text runner or an
isolated native agent. `prepare-behavior-evals.py` separates execution inputs
from grading criteria and preserves old/new context hashes; preparation alone
is not an executed test. The [acceptance index](skills/retro/evals/README.md)
maps the 26 initial scenarios and four tool-enabled fixtures. Actual decisions,
file states and later real-world benefit are reported separately.
A failing test is reported by `unittest` as `FAIL` (an assertion did not hold) or `ERROR` (the test raised), each with the test's id and a traceback, and the run exits non-zero.
In CI, the `lint` workflow (`.github/workflows/lint.yml`, calling `netresearch/skill-repo-skill`'s `ci-python.yml`) runs these three commands on every pull request to `main` and every push to `main`, on Python 3.11, 3.12, 3.13 and 3.14.
Branch coverage of the Python scripts can be measured with coverage.py; the `patch = subprocess` setting also counts the scripts the tests start as subprocesses:
```bash
printf '[run]\nbranch = True\nparallel = True\npatch = subprocess\ndata_file = /tmp/retro-coverage.data\nsource = skills/retro/scripts\n' > /tmp/retro.coveragerc
COVERAGE_RCFILE=/tmp/retro.coveragerc uv run --with coverage --with tree-sitter==0.26.0 --with tree-sitter-bash==0.25.1 python -m coverage run -m unittest discover -s tests
COVERAGE_RCFILE=/tmp/retro.coveragerc uv run --with coverage python -m coverage combine
COVERAGE_RCFILE=/tmp/retro.coveragerc uv run --with coverage python -m coverage report
COVERAGE_RCFILE=/tmp/retro.coveragerc uv run --with coverage python -m coverage json -o /tmp/retro-coverage.json
```
`coverage report` prints a combined statement and branch figure; the branch count alone is `totals.covered_branches` of `totals.num_branches` in the JSON report.
Measured on 2026-09-30 at commit `fbc99ca`: 1584 of 1804 branches (87.8 %) of the twelve Python scripts; the two shell scripts are not measured. CI does not measure coverage.
A pull request that adds or changes behaviour in a script adds or updates a test in `tests/` that fails without the change.
## Dependencies
- **Runtime:** the scripts need Python 3.11 or later and use only the standard library, except `detect-mechanical.py`, `derive-session-scope.py` and `collect-review-findings.py`, which declare `tree-sitter==0.26.0` and `tree-sitter-bash==0.25.1`. Every script that runs as a command declares `requires-python = ">=3.11"` in an inline script-metadata block (PEP 723) and is started with `uv run`, which picks a matching interpreter; `tests/test_python_floor.py` keeps the headers and the documented invocations in step. For the three tree-sitter scripts, `uv run --script` installs exactly the declared versions from the configured package index (PyPI by default) into a cached environment on first use. The shell scripts need `bash`, `git`, `jq` and `uv`; `materialize-pr.sh` also needs `gh`. The tools a `/retro` run uses are listed under [Requirements](#requirements); they are installed by the user and not declared in any manifest. Ruff targets the Python 3.11 minimum using `.ruff.toml`, and the CI matrix includes that minimum.
- **Packaging:** `composer.json` requires `netresearch/composer-agent-skill-plugin`, which registers the skill in a consuming Composer project. `composer.lock` is ignored (`.gitignore`); the skill-repo convention publishes no lock file. The Claude Code plugin manifests (`plugin.json`, `.claude-plugin/plugin.json`) declare no dependencies.
- **Development and CI:** the pre-commit hooks are pinned by `rev:` in `.pre-commit-config.yaml`. The test command pins the tree-sitter packages with `--with` in `.github/workflows/lint.yml`, matching the scripts' inline metadata. The workflows call reusable workflows from `netresearch/skill-repo-skill`, `netresearch/.github` and (`security.yml`) `netresearch/typo3-ci-workflows` at `@main`; the third-party actions inside those are pinned to commit SHAs there.
- **Selection and tracking:** a third-party package is added only when the standard library cannot do the job, and it is pinned to an exact version. The tree-sitter packages are the only ones so far: they replaced regular expressions for reading the structure of shell commands in `derive-session-scope.py`, after review rounds kept finding defects in the regex splitting (commit `6f05551`). Renovate (`renovate.json`, `config:recommended` with the pre-commit manager enabled) opens update pull requests, and `.github/workflows/auto-merge-deps.yml` hands Renovate and Dependabot pull requests to the organisation's auto-merge workflow. Licence and vulnerability rules for dependencies are those of the organisation's security policy linked below.
## Governance and policies
This repository follows the Netresearch organisation policies:
- [Governance](https://github.com/netresearch/.github/blob/main/GOVERNANCE.md): ownership, roles, how decisions are made and disputes resolved, and continuity.
- [Roadmap](https://github.com/netresearch/.github/blob/main/ROADMAP.md): planned and explicitly excluded work for the coming year.
- [Handling of dependency and code analysis findings](https://github.com/netresearch/.github/blob/main/SECURITY.md#handling-of-dependency-and-code-analysis-findings): thresholds, deadlines and the exception process for dependency (SCA) and static analysis (SAST) findings.
- [Secret management](https://github.com/netresearch/.github/blob/main/SECURITY.md#secret-management): how CI and release credentials are stored, accessed and rotated.
- [Access roster](https://github.com/netresearch/.github/blob/main/docs/access-roster.md): who holds administrative access to this repository and the organisation.
The security assurance case of this repository (what the scripts read, write and send, trust boundaries, countermeasures and limits) is [docs/SECURITY-ASSURANCE.md](docs/SECURITY-ASSURANCE.md).
Checks that run on every pull request to `main` in this repository: `Validate` (`.github/workflows/validate.yml`: skill structure, plugin manifest sync, markdownlint, yamllint, actionlint, JSON syntax, plugin and SKILL.md version checks, ShellCheck at style severity, ruff lint and format check, checkpoint schema) and `lint` (`.github/workflows/lint.yml`: Python compile, the unit tests across the configured Python matrix, eval validation; its Bandit job is skipped because `run-bandit` is not set). Also on every pull request: `Security` (`.github/workflows/security.yml`: Betterleaks secret scanning of the git history, zizmor on the workflow files, Dependency Review, and Composer Audit plus Opengrep static analysis through `netresearch/typo3-ci-workflows`), `Eval Validation` (`.github/workflows/eval-validate.yml`), `Harness Verification` (`.github/workflows/harness-verify.yml`: `AGENTS.md` of at most 150 lines with resolvable relative links), `Template drift` (`.github/workflows/check-template-drift.yml`: fails when a file governed by the organisation's `skill` template differs from it; `lint.yml` is recorded as intentional drift in `.github/template.yaml`), `Labeler` (`.github/workflows/labeler.yml`: a `pull_request_target` workflow that applies the labels of `.github/labeler.yml` and checks out no pull request code), Auto-merge dependency PRs (`.github/workflows/auto-merge-deps.yml`), skipped unless Renovate or Dependabot opened it and for pull requests labelled `deps-major` or `deps-no-automerge`, CodeQL (actions, python) as the `codeql` job of `security.yml`, and, configured outside the workflow files, SonarCloud Code Analysis (SonarCloud automatic analysis), the DCO check and the CodeRabbit review status. OpenSSF Scorecard (`.github/workflows/scorecard.yml`) does not run on pull requests: it runs on pushes to `main` and `master`, weekly and on manual dispatch. Code scanning reports the CodeQL and SonarCloud results again as the `CodeQL` and `SonarCloud` check runs. GitHub secret scanning with push protection is enabled as a repository setting. No Bandit workflow runs in this repository; static analysis comes from CodeQL, SonarCloud and Opengrep, whose findings follow the organisation's [static analysis rule](https://github.com/netresearch/.github/blob/main/SECURITY.md#static-analysis-sast).
## Contributing
Issues and PRs are welcome at <https://github.com/netresearch/retro-skill/issues>.
- **DCO sign-off is required** — commit with `git commit -s`. Without the `Signed-off-by` trailer the PR is blocked even when all checks pass.
- Use **Conventional Commits** (`feat:`, `fix:`, `chore:`, …).
- The `lint` workflow runs python compile, the unit tests and `validate-evals.py`; the `validate` workflow runs validate-skill, markdownlint, yamllint, plugin/SKILL version parity, actionlint, JSON syntax, manifest sync, ShellCheck, ruff and the checkpoint-schema check — run them locally before pushing.
## License
Code is licensed under [MIT](LICENSE-MIT); content under [CC-BY-SA-4.0](LICENSE-CC-BY-SA-4.0). SPDX: `(MIT AND CC-BY-SA-4.0)`.
Maintained by **Netresearch DTT GmbH** · <https://www.netresearch.de/>