Skip to content

kumouri/zethus

v0.7.1Apache-2.0

A development process for GitHub Copilot that it follows and enforces: research, spec, sign-off, phased implementation, tests, docs, local gates and an evidence PR.

Zethus 🪨

A development process for GitHub Copilot, in a form Copilot can follow and enforce. One custom agent runs every change through research → spec → sign-off → phased implementation → tests → docs → local gates → a PR with evidence, and refuses to skip a stage. Ten standalone skills hold one procedure each. Four Markdown templates and five stdlib Python scripts do the mechanical parts. Install it as a Copilot plugin from this repo's marketplace, so it follows you into every repo without changing any of them, or copy it into a repository's .github/ folder for a whole team and the cloud agent. Nothing in it is specific to one organization.

Amphion and his twin Zethus built the walls of Thebes. Amphion played his lyre and the stones set themselves. Zethus carried each stone by hand and set it true. Amphion is the Claude Code pipeline. This is its Copilot twin: same walls, the other brother's method.

The procedure

flowchart LR
    O["0 · Orient<br/><i>fresh base</i>"] --> R["1 · Research<br/><i>facts cited to file:line</i>"]
    R --> S["2 · Spec<br/><i>minimum or full</i>"]
    S --> SO{{"sign-off"}}
    SO -->|"decisions → ADRs"| I["3 · Implement<br/><i>one phase; Phase 0 changes nothing</i>"]
    I --> T["4 · Tests<br/><i>incl. refusals · mutation check</i>"]
    T --> D["5 · Docs<br/><i>docs-pointer-check</i>"]
    D --> G["6 · Gates<br/><i>fresh base · run-local-gates</i>"]
    G --> P["7 · PR<br/><i>evidence · never self-merged</i>"]
    P -.->|"next phase"| I
    I -.->|"stuck twice"| F["fresh-eyes<br/>investigation"]
    F -.->|"ranked leads"| I

The agent states which stage and step it's on at the top of every reply, and keeps its plan visible throughout (see Seeing the plan). It asks decisions as a short list of options with the recommendation first and marked, and it records each answer as an ADR. Its refusals are written into the agent file as a table. "Skip the spec" gets the minimum spec, about ten minutes. "Just push, CI will tell us" gets the local gates. "Merge it" gets a no: a person merges, only on green. One narrow exemption is written down too. A change with no behaviour change (a typo, a comment, formatting) may skip research, spec and tests, but never docs, gates or the PR.

It doesn't stop between stages. When one stage's exit condition is met, it starts the next in the same turn, and every stage's skill names that next action. That includes the step after sign-off, where recording the ADRs and checking those doc edits finish Stage 2 and Phase 0 starts. It ends a turn only at a listed stop: sign-off or a decision requested, a stale base, a failure it couldn't resolve, a refusal, a question answered, or the change done. Each time, it ends with where it stopped, what's next and why (never stop silently).

Your questions come first. Ask something mid-task, above all about the agent's own behaviour, and answering it becomes the current task: answered fully, investigated if needed, before the agent offers to resume (questions come first).

Each expensive check runs once per phase. Docs comes before Gates, so the full build and test suite runs once, last, on the exact tree that is pushed, docs included. The phase's tests run once, in Stage 4. Implement exits once they're written and compiling, and the mutation check proves its restore with git diff --quiet instead of re-running them. The base is fetched at the two freshness checkpoints and when a new phase branch is cut, and nowhere else: the PR stage reuses the fetch the gates just made. See run each check once per phase.

The next phase starts once the previous one is pushed to its PR, not once it merges. Merging is the team's call, on the team's schedule, so a process that merges only when a whole feature is done doesn't block the work. A pushed phase is marked in review in the spec, never built. See the next phase starts on push.

Independent work doesn't wait in line. The stages order exit conditions, and uncommitted code alone doesn't hold anything. Two or three independent tasks may run at once when neither reads the other's output or the files the other is changing, such as docs while the local gates run (when the gates don't read docs) or read-only research while a build runs. Each is named in the visible plan, and the final gate run still happens last, on the exact tree that is pushed. Remote or long-running work starts as soon as the pending change doesn't touch what that work runs: the agent checks the image build and deploy definition instead of guessing, says so in one line, and asks once if the definitions don't settle it. Results collected while local tooling was still changing are re-judged with its final version, and the run log says so. If you say a piece of work isn't affected, it starts without being re-litigated. See independent work runs concurrently.

Seeing the plan

On starting, and on entering each stage, the agent writes the plan before doing the stage's work. The plan is the stage map (done, current, still to come) plus the current stage's concrete steps, each worded so you can tell what "done" means. One step is in progress at a time, or two or three when they are independent and each is named in the plan, and the header line of every reply names what's in progress. The list itself is updated when a stage is entered and when the plan changes, not each time a step finishes: finished steps are ticked at the next update. That saves a todo call, and a model round trip, per step (plan update cadence). When the plan changes, the list changes in the same turn and the reply says what changed and why. A stage isn't exited while any of its steps is open, unless the step was dropped with a reason you've seen.

Where you see it depends on the Copilot surface, because only some of them render the todo tool:

Surfacetodo toolWhere the plan shows
VS Code agent modeSupported (GitHub); the list shows at the top of the Chat view. It shipped as experimental in 1.103, behind chat.todoListTool.enabled (VS Code)The todo list. Each reply's header names the current step, and the full checklist appears in the reply only on the turn the plan is written or changes.
Copilot CLINot documented as supported, and reported to resolve to nothing (copilot-sdk#1641)The checklist under the header of every reply
JetBrains agent modeNot documented either wayWhichever the agent has. If it has the tool but you can't see a list, say so once, or set plan.checklist to "always"
Copilot cloud agent"Not supported in cloud agent today" (GitHub)The checklist under the header of every reply

The checklist looks like this, directly under the header line:

Stage 3 · Implement — Phase 1, step 2 of 4: wire `retryLimit` into the retry loop

✓ 0 Orient · ✓ 1 Research · ✓ 2 Spec · **▶ 3 Implement** · 4 Tests · 5 Docs · 6 Gates · 7 PR

- [x] Add `retryLimit` to the config loader, default 3; a test reads it back
- [ ] ▶ Wire `retryLimit` into the retry loop in `RetryingClient.send`
- [ ] Log the attempt number at WARN on each retry
- [ ] Flag anything outside Phase 1's scope in the *As built* list

Where the todo tool is shown, the agent doesn't repeat the full list in every reply. Two copies of the same list would be noise, and the step named in the header is enough to tie a reply to the plan. plan.checklist: "always" turns the copy back on.

No script checks the plan. The kit's scripts check the repository, and the plan lives in the chat. A script could check a plan file the agent wrote, but not that you saw it, so a green result would vouch for the wrong thing. The plan is held instead the way the rest of the procedure is: a rule in the agent file, a stage exit condition (no open steps), a row in the refusal table, and a header on every reply that names the step, so a missing plan shows straight away. tests/test_zethus.py pins those rules into the agent file.

Signed commits

Every commit Zethus authors is signed with its name as well as carrying the AI co-author trailer. The signature (commits.signature, default — Zethus) sits on its own line just before the trailer block, because git and GitHub only read trailers in the last paragraph:

feat(retry): phase 1 — read retryLimit from config

Read the retry limit from config instead of the hard-coded 3.

— Zethus

Co-authored-by: <the commits.aiTrailer value>

Set commits.signature to "" to leave the signature off; the trailer is still required. Like the plan, this is a rule in the agent file rather than a script check: the kit has no commit hook, and tests/test_zethus.py pins the rule and its order.

What's in the kit

FileInstalls toPurpose
copilot-instructions.md.github/copilot-instructions.mdThe standing rules, short and imperative. Copilot loads them for every request in the repo.
com.github.copilot/rules/zethus-docs.instructions.md.github/instructions/docs.instructions.mdPath-scoped rules (applyTo: "**/*.md"): Markdown is canonical, router vs leaf docs, pointers resolve, spec status vocabulary, ADRs superseded rather than rewritten.
com.github.copilot/agents/zethus.agent.md.github/agents/The enforcer. The stage table with exit conditions, the visible plan, per-turn behaviour, and the refusal table.
skills/research-existing-code.github/skills/Facts cited to file:line, the search behind every negative claim, and what the research did not cover.
skills/write-spec-minimum.github/skills/The one-screen spec for one PR, and the fast path when someone says "just code it".
skills/write-spec-full.github/skills/Verbatim ask, measured problem, facts, assumptions checked, options, phases, rollback, out of scope, open questions, then stop for sign-off.
skills/write-adr.github/skills/One imperative decision sentence, options, consequences, enforced where, revisit when. Pastes cleanly into a wiki or ticket.
skills/fresh-eyes-investigation.github/skills/Artifact plus one line of intent in; ranked leads out, each established or conjecture. Never a diagnosis; an empty list is valid.
skills/implement-phase.github/skills/One phase per branch, stacked on the previous phase's (or per commit series, in rebase style), Phase 0 first; the next phase starts once this one is pushed, not merged. Stay inside the phase; classify anything unplanned; keep an As built list.
skills/test-plan.github/skills/Per-phase tests covering the change, the failure path, the refusal, and what must not change, plus a mutation check.
skills/pre-push-gates.github/skills/Every CI gate run locally, on the working tree, before a push. A skip is not a pass.
skills/zethus-pr-description.github/skills/An evidence-first PR body diffed against the real integration branch, including What was not checked.
skills/docs-sync-check.github/skills/Fix the docs that describe the changed code in the same PR, in place. Leave every other doc alone.
templates/spec-full.md · spec-minimum.md.github/zethus/templates/Both spec shapes. The minimum spec's headings are a strict subset of the full spec's, so promoting one only adds sections. A test enforces this.
templates/adr.md.github/zethus/templates/The ADR record: a field table (including Enforced where), decision, context, options, consequences.
templates/pr.md.github/zethus/templates/The PR body: What · Why · Changes · Evidence · What was not checked · deferrals · decisions · docs · AI assistance.
scripts/run-local-gates.py.github/zethus/scripts/Discovers and runs lint/build/test, then prints a PASS/FAIL/SKIP table and an explicit verdict. Prefers a repo's mvnw/gradlew wrapper; on Windows a bare mvn or ./mvnw resolves to mvn.cmd/mvnw.cmd through PATHEXT. Exit codes: 0 full green · 1 red · 3 green but incomplete · 2 nothing to run.
scripts/base-freshness.py.github/zethus/scripts/Fetches, counts the commits in HEAD..origin/<base>, and stops the stage if there are more than branchModel.maxBehind. Prints the merge-base to diff against, and whether the branch was rewritten since its last push. The agent runs it at two points: Stage 0 (Orient) and immediately before the push (pre-push-gates) — see Branch models. --merge-base-only prints the merge-base alone and always exits 0, for a stage that needs a diff base and must not be stopped. Exit codes: 0 fresh · 1 stale, rebase first · 2 usage error · 3 no answer (the fetch failed).
scripts/new-adr.py.github/zethus/scripts/Creates docs/adr/YYYY-MM-DD-slug.md and adds its row to the ADR index. Ids are keyed by date, so parallel branches never collide.
scripts/new-spec.py.github/zethus/scripts/new-spec.py full|minimum "Title" creates docs/specs/<slug>.md as DRAFT.
scripts/docs-pointer-check.py.github/zethus/scripts/Fails on relative Markdown links that don't resolve, including case mismatches that only break on Linux. With --sync-base, it also lists docs whose described code changed (report-only).
zethus.config.example.json.github/zethus.config.jsonProject facts: integration branch, gate commands, spec and ADR dirs, the doc-to-code map, the AI co-author trailer and commit signature.
plugin.json, com.github.copilot/hooks/—The plugin: its manifest, and the sessionStart hook that injects the working agreement and says where the scripts and templates are.
agents/, hooks/hooks.json—Generated copies of the agent and the hook registration at the plugin root, for IntelliJ (why).
install.py—Copies all of the above into place: into a repo (--target) or for your user account (--user, with --uninstall and --migrate-to-plugin). Never clobbers a file it didn't write, and writes nothing if there are conflicts. --with-mnemosyne also sets up Mnemosyne.

The scripts are Python 3.10+ standard library only. There are no PowerShell twins: Python runs natively on Windows, and a second implementation would be a second thing to drift.

The kit's Markdown writes <kit root> for the folder holding scripts/ and templates/, because a plugin can live anywhere. install.py turns it into a real path, and the plugin's hook announces it.

Install

The plugin is the default for VS Code, IntelliJ and the Copilot CLI. install.py is for repo mode, and so for the cloud agent, which never loads a user's plugins.

Plugin (default)Repo mode (--target)User mode (--user)
Where it goesThe client's plugin folderThe repo's .github/, committed~/.copilot/, on your machine only
Who gets itYou, in every repo you openEveryone who clones the repo, plus the Copilot cloud agentYou, in every repo you open
Changes the repoNoYes: a PR adding .github/ filesNo. Optional per-repo config stays untracked
UpdatesFrom the marketplaceRe-run in a PRRe-run on each machine
Right whenYou want it everywhere, with one settingThe team adopts the process, or the cloud agent must follow itA client on your machine can't use plugins

Install the plugin (default)

Add the marketplace kumouri/mesmer-grimoire#main, then install zethus from it. Pin #main: the repo's default branch is develop, which would serve unreleased work.

copilot plugin marketplace add kumouri/mesmer-grimoire#main
copilot plugin install zethus@mesmer-grimoire
  • Copilot CLI: the commands above. It needs a build with Agent Plugins 1.0; 1.0.65 ignored this layout's agent and hooks, and 1.0.105 and 1.0.106-2 run it.
  • VS Code: add the same entry to chat.plugins.marketplaces, then install from the Extensions view. Its hooks are in preview, and organizations can turn them off.
  • IntelliJ: add the same entry under Tools → GitHub Copilot → Customizations → Plugin Marketplaces. It takes GitHub shorthand, a git URI or a local repo (as a file:/// URI). A shorthand and a URI for one repo count as one marketplace, so adding both installs nothing twice.

Then pick the zethus agent, or invoke a skill as /zethus:write-spec-minimum. Set per-repo facts as in Config for user-level use. The same marketplace carries mnemosyne, whose hooks record lessons-capture candidates while Zethus works; its README says what else it needs.

What a plugin can't do by itselfWhat Zethus does
Ship always-on instructions: the Copilot CLI doesn't load a plugin's rules/A sessionStart hook injects copilot-instructions.md as additionalContext
Expand a variable inside a skill's textThe same hook announces the kit root. If it didn't run, a skill's own note says the kit is two folders above that skill's folder

If an organization turns hooks off, the working agreement doesn't load. The agent file still carries the procedure, so select it. The docs rule is in com.github.copilot/rules/, which only VS Code loads, as its source reads it.

Beside an install.py copy. Project and personal skills silently beat a plugin's skills of the same name, and the agreement would load twice, so the two never both act:

  • install.py --user finds a plugin the Copilot CLI recorded, writes nothing and says so. --force installs anyway.
  • install.py --user --migrate-to-plugin removes an earlier user-mode copy. It removes what --uninstall removes, keeps your own files, and exits 1 if an edited skill stays and still shadows the plugin (--force removes edited files too).
  • In a repo with the kit in .github/zethus/, the plugin's hook prints nothing: the repo's copy is in charge there.
  • If it finds a user-mode copy, the hook injects only a line asking the model to tell you to migrate. That covers a plugin installed by VS Code or IntelliJ, which the installer may not see.

One plugin, two layouts

The Copilot CLI and IntelliJ look for a plugin's agent and hooks in places that don't overlap, so Zethus ships both:

HostReads the agent fromReads hooks from
Copilot CLI (1.0.105, 1.0.106-2)com.github.copilot/agents/ only, because plugin.json declares the Agent Plugins 1.0 $schemacom.github.copilot/hooks/hooks.json only
IntelliJ's Copilot language server (plugin 1.20.0, server 1.555.7)agents/ at the plugin roothooks.json, else hooks/hooks.json, at the root

With only the first layout, IntelliJ listed the 22 skills but no agent and no hook. Neither host reads the other's place, so nothing loads or fires twice there. plugin.json can't point IntelliJ at com.github.copilot/ instead: the 1.0 schema is closed, and the CLI ignores unknown keys and logs each one as an error. The files under com.github.copilot/ are the canonical ones, and the ones install.py copies. scripts/sync_zethus_plugin_root.py writes the root copies from them. VS Code hasn't been checked against either layout: if it reads both places, it would load the agreement twice.

What was tested. On Windows, with a temporary COPILOT_HOME:

  • Copilot CLI: a local marketplace on 1.0.105 (--no-auto-update) and 1.0.106-2, both from npm copies. The model quoted the kit root the hook announced, the hook ran once per session, and the zethus agent loaded. Before the root copies existed, a GitHub marketplace on 1.0.106-2 also ran a bundled script from that root.
  • IntelliJ: its bundled language server, driven over stdio with a sandboxed home. It added a local marketplace, installed zethus through its own install call, and listed the zethus agent, the SessionStart hook and all 22 skills. It only lists the hook; nothing here ran the hook inside the IDE.
  • VS Code: nothing has been run, so its steps rest on its docs and settings screens.

Install with install.py

Two more modes. Pick one per machine and repo; they don't need each other.

Install into a repository

git clone https://github.com/kumouri/mesmer-grimoire.git
python mesmer-grimoire/zethus/install.py --target path/to/your-repo --dry-run   # see the plan
python mesmer-grimoire/zethus/install.py --target path/to/your-repo

Then, in the target repo:

  1. Edit .github/zethus.config.json. Set gates.steps to mirror your CI workflow, gate for gate. run-local-gates.py --list shows what it would run.
  2. Commit the .github/ changes in a PR like any other change.
  3. In your Copilot chat, pick the zethus agent to run the whole procedure. Or invoke a single skill, such as /write-spec-minimum or /fresh-eyes-investigation, on its own.

If the repo already has a .github/copilot-instructions.md, the installer leaves it alone and installs the rules as .github/instructions/zethus.instructions.md with applyTo: "**". Copilot combines path-specific and repository-wide instructions, so both apply. Copying by hand works too: the Installs to column above is the whole mapping.

Repo mode never deletes anything. If you are upgrading a repo from a kit where the PR skill was called pr-description, delete .github/skills/pr-description/ in the same PR, unless it is Amphion's skill of that name. User mode removes files that left the kit by itself.

Install for your user account (every repo, no commit)

python mesmer-grimoire/zethus/install.py --user --dry-run   # see the plan
python mesmer-grimoire/zethus/install.py --user
python mesmer-grimoire/zethus/install.py --user --uninstall # later, to remove it

Add --jetbrains-legacy only for an older JetBrains Copilot plugin that doesn't read ~/.copilot/instructions (see the support table below).

Kit pieceUser-mode location
Working agreement (copilot-instructions.md)~/.copilot/instructions/zethus.instructions.md, with applyTo: "**". Your own ~/.copilot/copilot-instructions.md is left alone.
com.github.copilot/rules/zethus-docs.instructions.md~/.copilot/instructions/zethus-docs.instructions.md
com.github.copilot/agents/zethus.agent.md~/.copilot/agents/
skills/<name>/~/.copilot/skills/<name>/
Templates, scripts~/.copilot/zethus/templates/, ~/.copilot/zethus/scripts/
Example config~/.copilot/zethus/config.example.json, for reference only
Working agreement, for older JetBrains buildsOnly with --jetbrains-legacy: global-copilot-instructions.md in JetBrains' Copilot folder, and only if you don't have one

The installed Markdown is rewritten on the way. Every <kit root>/… path becomes the absolute path of your user-level copy, and each mention of the config lists the full resolution order below. So the agent and skills run python <home>/.copilot/zethus/scripts/run-local-gates.py from any repo.

The user install is careful about your files:

  • A file it didn't write is never overwritten, even with --force. If you already have a ~/.copilot/skills/zethus-pr-description/, for example, it stops, lists the clash, and writes nothing.
  • Every file it writes is recorded with its SHA-256 in ~/.copilot/zethus/install-manifest.json. Re-running it upgrades the files you haven't edited. A kit file you have edited is a conflict unless you pass --force.
  • --uninstall removes only the manifest's files that you haven't edited (--force removes edited ones too), then any folders that became empty. Your own files, including ~/.copilot/zethus/config.json, stay.
  • It writes no config.json. A user default holding example gates would override discovery in every repo, so npm run lint would run in a Maven project.

~/.copilot is used even when COPILOT_HOME is set. Only the Copilot CLI honours that variable, so a kit installed there would be invisible to VS Code and JetBrains.

Which Copilot clients read the user-level files

VS Code and the CLI were checked against GitHub's and VS Code's documentation in September 2026. The JetBrains column rests on something stronger than the docs: a user's own IntelliJ settings screen (below). User-level files are local, so the Copilot cloud agent on github.com never sees them; it needs repo mode.

PieceVS Code Copilot ChatCopilot CLIJetBrains (IntelliJ etc.)
Agent, ~/.copilot/agents/Yes (docs)Yes (docs)Yes: a default location in the settings screen (changelog agrees)
Skills, ~/.copilot/skills/Yes (docs)Yes (docs)Yes: a default location in the settings screen (docs agree). Older builds: see bug #1517 below
Instructions, ~/.copilot/instructions/*.instructions.mdYes (docs)Yes (docs)Yes: a default location in the settings screen. The docs still describe only global-copilot-instructions.md (docs)
Hooks, ~/.copilot/hooks/ (--with-mnemosyne)Not checkedNot checkedYes: a default location in the settings screen
JetBrains global-copilot-instructions.md (--jetbrains-legacy only)——Windows: %LOCALAPPDATA%\github-copilot\intellij\. macOS: ~/.config/github-copilot/intellij/. Linux: not documented, so the installer skips it and says so (docs)

The JetBrains evidence. In September 2026, the IntelliJ Copilot settings page Tools → GitHub Copilot → Customizations listed these default locations, all enabled. The plugin version wasn't visible, so it is unknown which build introduced them.

SettingDefault locations
Instruction File Locations (*.instructions.md).github/instructions, ~/.copilot/instructions
Agent File Locations (*.agent.md).claude/agents, .github/agents, ~/.copilot/agents
Skill File Locations (folders of SKILL.md).agents/skills, .claude/skills, .github/skills, ~/.agents/skills, ~/.claude/skills, ~/.copilot/skills
Prompt File Locations (*.prompt.md).github/prompts, ~/.copilot/prompts
Hook File Locations (*.json).github/hooks, ~/.copilot/hooks

The same page has toggles for organization instructions, AGENTS.md and CLAUDE.md (nested variants experimental), plus a Plugin Marketplaces list.

Older JetBrains builds. Before this settings page, the docs described one global instructions file, global-copilot-instructions.md, and no ~/.copilot/instructions (docs). An open report says user-level .copilot skills weren't detected on Windows (#1517). If your plugin has no Customizations page, or the kit doesn't show up:

  • update the plugin; or
  • re-run with --jetbrains-legacy to also write global-copilot-instructions.md.

Don't use the flag on a current build. Copilot would then read the working agreement twice, once from each file.

Sources, with the sentence each claim rests on:

  • VS Code: custom agents: Agent Host reads agents "from the selected host's folder", ~/.copilot/agents or ~/.claude/agents.
  • VS Code: agent skills: personal skills in ~/.copilot/skills/, ~/.claude/skills/ or ~/.agents/skills/.
  • VS Code: custom instructions: "For personal, always-on instructions in Copilot Agent Host sessions, use ~/.copilot/copilot-instructions.md", and "User instructions in Agent Host folders, such as ~/.copilot/instructions … do not roam through Settings Sync."
  • VS Code: Copilot settings reference: the defaults of chat.agentFilesLocations, chat.agentSkillsLocations and chat.instructionsFilesLocations, each deprecated and used only by the Local agent.
  • Copilot CLI: customization reference: user agents in ~/.copilot/agents/ load first.
  • Copilot CLI: add skills: personal skills in ~/.copilot/skills or ~/.agents/skills.
  • Copilot CLI: add custom instructions: $HOME/.copilot/copilot-instructions.md and $HOME/.copilot/instructions/**/*.instructions.md; COPILOT_HOME replaces $HOME/.copilot.
  • About agent skills: lists ~/.copilot/skills and ~/.agents/skills for agent mode in IDEs, JetBrains included.
  • GitHub changelog, 2026-05-13: in JetBrains, "define custom agents at the global level using the .agent.md file under ~/.copilot/agents."
  • copilot-intellij-feedback #1517: open report that the JetBrains plugin doesn't detect user-level .copilot skills on Windows. It predates the settings screen above.
  • Repository instructions in your IDE, JetBrains tab: a global global-copilot-instructions.md on macOS and Windows. The page documents no path-specific instruction files for JetBrains, and no Linux location. The settings screen above contradicts the first point for current builds.

Notes:

  • VS Code has two kinds of session. Agent Host sessions (the Copilot or Claude harness) read ~/.copilot/ directly (agents, instructions). Local agent sessions read the folders listed in chat.agentFilesLocations, chat.agentSkillsLocations and chat.instructionsFilesLocations. All three default to include ~/.copilot/agents, ~/.copilot/skills and ~/.copilot/instructions (docs). Those settings are deprecated but still work, and they are also how you would add another folder.
  • Files under ~/.copilot/ don't roam through Settings Sync (docs). Run the installer on each machine.
  • In JetBrains, the settings page above is where to look if the zethus agent doesn't appear. Check that ~/.copilot/agents and ~/.copilot/skills are listed and enabled.
  • With --jetbrains-legacy, an existing global-copilot-instructions.md is left alone. Paste the rules from ~/.copilot/instructions/zethus.instructions.md into it yourself. On an older build, the path-scoped docs rules have no user-level equivalent.
  • No current documentation lists a repository .copilot/ folder for agents, skills or instructions; the repo-level folders are .github/, .claude/ and .agents/. Zethus uses a repo's .copilot/ only for its own untracked config (below).

Config for user-level use

The scripts take the first config they find. --config beats all of them.

#LocationUse it for
1.github/zethus.config.jsonThe team's committed answers (repo mode).
2$ZETHUS_CONFIGA path, relative to the repo root unless absolute. Point it anywhere.
3.copilot/zethus.config.jsonPer-repo answers you may not commit. Keep it untracked (below).
4.claude/amphion.config.jsonAmphion's config; the shared keys mean the same thing.
5~/.copilot/zethus/config.jsonYour defaults for every repo, such as commits.aiTrailer. Leave gates out unless every repo you use runs the same gates.
—(none)Discovery from the repo's build files, then ask.

To keep a per-repo config out of git without touching the repo's .gitignore (which is itself a tracked change), use the clone's private exclude file:

mkdir -p .copilot
cp ~/.copilot/zethus/config.example.json .copilot/zethus.config.json   # then edit it
echo ".copilot/" >> .git/info/exclude
git status --short          # .copilot/ must not appear

In a worktree, .git is a file, not a folder. git rev-parse --git-path info/exclude prints the exclude file to use.

For a Maven project on Windows, discovery alone gives ./mvnw.cmd -B verify if the repo has the wrapper, else mvn -B verify, which resolves to mvn.cmd. To mirror a CI that runs mvn verify, write that gate explicitly:

{ "gates": { "steps": [ { "name": "maven verify", "command": "mvn -B verify" } ] } }

Add Mnemosyne in the same step

Mnemosyne is the reflexion memory whose hooks record lessons-capture candidates while Zethus works. --with-mnemosyne installs it after the kit, in either mode:

python mesmer-grimoire/zethus/install.py --user --with-mnemosyne --dry-run
python mesmer-grimoire/zethus/install.py --user --with-mnemosyne
python mesmer-grimoire/zethus/install.py --target path/to/your-repo --with-mnemosyne
python mesmer-grimoire/zethus/install.py --user --with-mnemosyne --mnemosyne-repo ~/notes/memory
python mesmer-grimoire/zethus/install.py --user --with-mnemosyne --mnemosyne-copilot-hooks-dir ~/my-hooks
python mesmer-grimoire/zethus/install.py --user --uninstall --with-mnemosyne

It doesn't repeat any of Mnemosyne's setup. It calls Mnemosyne's own installer, mnemosyne/install.py, and passes on --target or --user, --dry-run, --uninstall, --mnemosyne-repo (as --repo) and --mnemosyne-copilot-hooks-dir (as --copilot-hooks-dir). That installer sets up:

  • the mnemosyne-reflexion package;
  • a local git memory repo at ~/repos/mnemosyne-local, with no remote;
  • MNEMOSYNE_REPO, set persistently;
  • the Claude Code plugin;
  • with --target, the repo's Copilot hook files, .github/hooks/mnemosyne.json and its bridge script, which you commit along with the kit;
  • with --user, the same two files in ~/.copilot/hooks, for every repo. The IntelliJ plugin reads that folder and can add others; --mnemosyne-copilot-hooks-dir writes to one of those instead. These files aren't committed, and a repo that has its own copy doesn't fire twice.

The Mnemosyne README has the details. Mnemosyne runs only after the kit installs cleanly, and the run's exit code is the worse of the two. --force isn't passed on, so an existing MNEMOSYNE_REPO pointing somewhere else, or a hook file you edited, stops the Mnemosyne step until you run its installer yourself with --force.

Configuration

Every key is optional. Where a value is missing, the scripts and agent work it out from the repository, then ask.

Where the config file lives is covered in Config for user-level use. The keys are the same in every location.

KeyUsed byMeaning
branchModel.baseagent, zethus-pr-description, docs-sync-check, base-freshnessThe integration branch. Default: develop if it exists, else the default branch.
branchModel.styleagent, implement-phase, zethus-pr-description, base-freshness"branch-per-change" (default): a new branch per change, cut from the freshly fetched base, one branch and PR per phase, each phase stacked on the previous one's pushed branch. "rebase": one long-lived branch kept rebased onto the base; phases are commit series on it, every diff is taken against the merge-base with origin/<base>, and the PR body says when the branch was rebased. See Branch models.
branchModel.maxBehindbase-freshness, agent, pre-push-gatesHow many commits the branch may be behind origin/<base> before a freshness checkpoint (Stage 0, and the pre-push gates) refuses to continue. Default 0: any commit behind means rebase first.
gates.steps[]run-local-gatesOrdered {name, command, exitCode?}. command is a string or an argv list; there's no shell.
gates.lint / .build / .test / .mandatedChecks[]run-local-gatesAmphion's gate keys, read when gates.steps is absent. A mandated check with only a prose expect shows as MANUAL (not checked).
spec.dir, spec.templates.{full,minimum}new-specDefault docs/specs, and the kit's templates.
adr.dir, adr.templatenew-adrDefault docs/adr, and the kit's template.
docSync.map[]docs-pointer-check --sync-base{doc, describes[globs]}: which code each doc describes. Globs use fnmatch rules, so * also matches /.
docs.pointerIgnore[]docs-pointer-checkMarkdown files to skip, such as generated changelogs.
commits.aiTraileragentThe co-author trailer that AI-assisted commits carry.
commits.signatureagentThe line Zethus signs its commits with, just before the trailer block. Default "— Zethus"; "" means no signature line. It goes alongside commits.aiTrailer, never instead of it. See Signed commits.
plan.checklistagent"auto" (default): the plan goes in the todo tool where the agent has one, else as a checklist in every reply. "always": the checklist goes in every reply as well, for a surface that accepts the tool but doesn't show it. See Seeing the plan.

Branch models

Zethus assumes nothing about how long a branch lives. It insists only that the base is fresh.

branch-per-change (default)rebase
BranchesA new one per change, cut from origin/<base>One long-lived branch, rebased onto origin/<base> every few days
PhasesOne branch and one PR each, Phase 0 first; each branch stacked on the previous phase's, its PR targeting that branchOne commit series each, on the same branch, Phase 0 first, pushed to the same PR
Next phase startsOnce the previous phase is committed, green on the gates and pushed. Its PR need not be mergedThe same
Diff baseorigin/<base>...HEAD; for a stacked phase, origin/<previous-phase>...HEADThe merge-base with the freshly fetched origin/<base>, never a local ref
MergingA person merges the stack bottom-up, when their process allows; GitHub retargets the next PR to <base>A person merges the one PR, when the feature is done
After a rebaseRebase the stack from its top with git rebase --update-refs origin/<base>; push each branch with --force-with-leasePush with --force-with-lease; the PR body names the new base

Neither style waits for a merge to start the next phase. See the next phase starts on push for why, and for a stack whose lower PR was squash-merged.

In both, the agent runs base-freshness.py at exactly two points per change:

  1. Stage 0 · Orient — before any work, so the branch is cut from, or rebased onto, a base just confirmed current;
  2. pre-push-gates — immediately before the push and the PR, so the gates, the diff and the PR body are against the base as it stands now.

The stages in between don't re-run it. A stale base doesn't look stale — diffs, measurements and research notes taken on one quietly compare branch drift instead of the change, and the PR can revert work that already landed — so the check counts commits rather than trusting the checkout. But that only costs something when the branch is created and when a diff leaves the machine, which is what makes two checkpoints enough and seven pure interruption (the cadence decision). A stage that needs the merge-base to diff against gets it from --merge-base-only, which prints that one line and always exits 0. Raise branchModel.maxBehind only as a recorded decision; the default of 0 means any commit behind stops the checkpoint.

{ "branchModel": { "base": "develop", "style": "rebase", "maxBehind": 0 } }

Copilot formats used, and where they're documented

The formats were checked against GitHub's and VS Code's documentation in September 2026.

PieceFormatDocumentation
Repository instructions.github/copilot-instructions.md, plain MarkdownAdding repository custom instructions · support matrix
Path-specific instructions.github/instructions/*.instructions.md with applyToVS Code custom instructions
Copilot pluginAgent Plugins 1.0: plugin.json, skills/, com.github.copilot/{agents,rules,hooks}/, plus root agents/ and hooks/ for IntelliJ (why); catalogue in .github/plugin/marketplace.jsonCLI plugin reference · VS Code agent plugins · Agent Plugins spec
Custom agent.github/agents/*.agent.md: name, description, toolsCustom agents configuration · Creating custom agents · VS Code custom agents
Skills.github/skills/<name>/SKILL.md: name (matches the folder), descriptionAbout agent skills · Creating skills · VS Code agent skills

Why skills and not prompt files. Prompt files (.github/prompts/*.prompt.md) work only in IDEs, are in public preview, and are deprecated in current VS Code in favour of skills. Skills load in VS Code and JetBrains agent mode, the Copilot CLI, and the Copilot cloud agent, and they can be invoked by /name as well as picked automatically from their description. The agent's tool list uses GitHub's portable aliases (read, search, edit, execute, web, todo, agent). Surfaces that don't support a tool ignore it; for example, web and todo aren't used by the cloud agent, and todo is documented only for VS Code. The agent's plan falls back to a checklist in its replies wherever todo isn't available (see Seeing the plan).

The limit of enforcement. Copilot now has a blocking hook: a preToolUse hook in .github/hooks/*.json can deny a tool call on the Copilot CLI and the cloud agent (hooks configuration, checked October 2026). This kit doesn't install one yet, so the agent enforces the process through its instructions, its stage gates and its refusal table. The scripts give each stage an objective exit condition: an exit code is harder to talk around than a sentence. Merging is protected by your platform's branch protection, not by this kit. A hook can't hold the turn-level rules either: a userPromptSubmitted hook's output is dropped, and whether agentStop can hold a turn open is unverified (never stop silently).

Modernization extensions

Two design specs for pluggable Zethus extensions — specialized skill families that recover business rules from legacy source, pin exact legacy behaviour in a "constitution" before any rewrite, and add a stage-gate overseer. The shared design and both consuming extensions now have a v1 build:

DocTargetsStatus
docs/modernization-shared.mdThe shared design both extensions build on: rule recovery, the constitution template, the overseer gatePARTIAL — v1 built, below
docs/batch-modernization.mdSpring/JPA/JDBC batches, ksh scripts, Control-M-scheduled jobs, stored-procedure-heavy jobs; first dialect Oracle/PL-SQLPARTIAL — v1 built, below
docs/webapp-modernization.mdStruts, old Spring MVC + JSP, plain JSP/servlets, and thirteen other legacy web targets, rankedPARTIAL — v1 built, below

v1 — batch modernization (Copilot CLI)

Installs the same way as the core kit, alongside it: copy the pieces below into your repo's .github/, or run install.py (its skill/script discovery is generic — it finds these automatically, no separate flag needed). Nothing here is required by the core ten-skill pipeline; install it only if you're recovering rules from legacy batch source.

PieceInstalls toPurpose
skills/recover-business-rules.github/skills/The generic rule-recovery shape (in the manner of research-existing-code), aimed at legacy source
skills/dialect-oracle-plsql.github/skills/Oracle 12c-era PL/SQL reader: packages, triggers, autonomous transactions, a keep/wrap/port disposition per procedure
skills/batch-type-spring-jpa-jdbc.github/skills/Reading guide for plain Spring JPA/JDBC batch loops — commit boundaries, restart/idempotency, skip policy — no dedicated script; see the skill for why
skills/batch-type-ksh-scripts.github/skills/ksh wrapper reader: getopts args, exit codes, trap, locking, retry/backoff loops
skills/batch-type-cron-scheduler-wrappers.github/skills/Scheduler reader, Control-M first: dependency graph + calendar rules, mapped onto a Spring Batch flow or a cloud workflow orchestrator
templates/constitution.md.github/zethus/templates/The behaviour-constitution artifact: nine fixed sections, each rule cited to the ledger
templates/fixture-manifest.example.json.github/zethus/templates/Example shape for a golden-master fixture manifest
scripts/overseer-gate.py.github/zethus/scripts/The overseer's three script-gates: constitution completeness before Implement, golden-master coverage before Tests report done, and a standing higher-environment-credential detector
scripts/hla-input-check.py.github/zethus/scripts/Stops before Stage 2 unless a target HLA document with a default stored-procedure disposition is configured
scripts/plsql-ddl-intake.py.github/zethus/scripts/PL/SQL DDL intake: objects, provenance, wrapped/invisible-body flags, repo-vs-live-extract drift, DBMS_SCHEDULER/DBMS_JOB batch discovery
scripts/control-m-reader.py.github/zethus/scripts/Control-M job-definition reader: dependency graph, calendar rules, Spring Batch flow / Step Functions mapping
scripts/ksh-wrapper-reader.py.github/zethus/scripts/ksh wrapper reader used by batch-type-ksh-scripts
scripts/_ledger.py.github/zethus/scripts/Shared rule-ledger and constitution parsing every script above uses

New config keys these scripts read, all optional (see zethus.config.example.json): modernization.hlaDoc, overseer.environments, overseer.currentEnvironment, overseer.credentialCheck.*.

Decided and unchanged from the design docs: the overseer stays a script-gate, no second agent ("script-gates now, agent later"); this build targets the Copilot CLI, matching the base kit's own primary validation target.

Deferred, per the batch spec's own "ship the ask's named first target" phasing: batch-type-spring-batch-xml, batch-type-stored-procedure-heavy, and the dialect-db2-sql-pl / dialect-tsql / dialect-postgres-plpgsql named seams — none built yet, no code or skill files exist for them. Reporting a flagged credential to security and rotating it stay manual steps in v1; the overseer's job stops at detection.

v1 — legacy web-app modernization (Copilot CLI)

Installs the same way, alongside the core kit and the batch pieces above — install.py's discovery finds these automatically too. Built for this build's own first real target shape: a hybrid app where Struts actions are wrapped in or invoked from Spring MVC controllers, rendering JSP views, already part-way migrated to REST microservices plus a new frontend (the frontend and the auth strategy are both read from the target HLA — see docs/webapp-modernization.md — never a hardcoded default).

PieceInstalls toPurpose
skills/webapp-target-struts.github/skills/Struts 1.x/2.x reader: routes, form-beans/validation, session scope, roles authz, hybrid Spring delegation
skills/webapp-target-spring-mvc-jsp.github/skills/Old Spring MVC + JSP reader: routes, view resolution, @SessionAttributes, method-security authz, hybrid Struts delegation, already-migrated-to-REST detection
skills/webapp-target-plain-jsp-servlets.github/skills/Plain JSP + scriptlets/JSTL + servlet reader: web.xml routes/authz/session-timeout, inline scriptlet/JSTL logic, and the shared raw HttpSession scan every reader's Java sources can use
skills/webapp-strangler-planner.github/skills/Groups recovered routes into cutover units — per-screen-flow where they share session state, per-route otherwise — and excludes already-migrated routes
skills/webapp-session-state-to-stateless.github/skills/Classifies every recovered session attribute into one of five stateless destinations, and the auth-shim decision
templates/fixture-manifest-http.example.json.github/zethus/templates/Example shape for an HTTP-shaped, scripted-synthetic-walk golden-master fixture manifest, including a session-carrying fixture sequence
scripts/webapp-hla-input-check.py.github/zethus/scripts/Stops before Stage 2 unless the target HLA names a frontend and an auth strategy (shim/replace)
scripts/struts-reader.py.github/zethus/scripts/Reader backing webapp-target-struts
scripts/spring-mvc-jsp-reader.py.github/zethus/scripts/Reader backing webapp-target-spring-mvc-jsp
scripts/jsp-servlet-reader.py.github/zethus/scripts/Reader backing webapp-target-plain-jsp-servlets
scripts/strangler-planner.py.github/zethus/scripts/Reader backing webapp-strangler-planner
scripts/_routes.py.github/zethus/scripts/Shared route-manifest shape the three readers emit and the planner consumes
scripts/_session_state.py.github/zethus/scripts/Shared session/state ledger shape, the raw HttpSession scan, and the still-reads-HttpSession check the overseer uses

Also extends pieces the batch v1 build shipped, rather than forking them: templates/constitution.md gains two sections, Session / state and Auth shim (a batch constitution with neither marks both "No rule found," same as any other inapplicable section); scripts/overseer-gate.py gains two subcommands, session-state (a flow can't be marked migrated while any session attribute is unclassified or still read from HttpSession by the new code) and http-fixtures (alongside golden-master: HTTP-shaped fixtures have the fields a replay needs, and none looks like it captured real user data).

Decided (2026-09-28, Telegram pickers — see docs/webapp-modernization.md for the full rationale): no hardcoded frontend default (React/Vite and Next.js stay documented guidance for what to write into the HLA, not an assumption); strangler cutover is per-screen-flow where routes share session state, per-route otherwise; the auth strategy is read from the HLA, with a missing answer stopping the pipeline and recommending shim first; HTTP golden masters are a scripted synthetic walk first, live recording deferred; targets with no clean HTTP boundary get a constitution convention, not a new skill family.

Deferred, per Open question 5's own recommendation: ranked targets 4-16 (JSF, Web Flow, EJB, SOAP, Velocity/FreeMarker, Wicket, GWT, Vaadin, Seam, Tapestry, portlets, legacy JS frontends, app-server packaging) — no reader exists for any of them yet; live-traffic golden-master recording; the OAuth2/OIDC auth-replacement phase itself.

How it relates to Amphion

Amphion is a spec-to-PR pipeline of six Markdown skills for Claude Code. Zethus uses the same design where they overlap, rather than duplicating it:

  • One config vocabulary. The keys that exist in both (gates.*, branchModel.base) mean the same thing. branchModel.style and branchModel.maxBehind are Zethus's own; Amphion ignores them. When no Zethus config is found in the repo, the scripts read .claude/amphion.config.json, so a repo using both answers each question once.
  • Different stages. Amphion covers what happens after the decisions: loading decided context, flag-or-fix when the spec runs out, resume-interrupted-phase when a delegate dies, initialize-ci, and log-friction. Zethus covers the front of the process, which Amphion assumes already happened: research, specs, ADRs and fresh-eyes investigation. It also covers the enforcement, which Amphion leaves to the operator. implement-phase points to Amphion's two exception handlers rather than re-implementing them.
  • Shared headings. Zethus's zethus-pr-description keeps Amphion's Not in this PR (intentional) and Noticed but out of scope headings, so flag-or-fix deferrals land in the same place. docs-sync-check keeps the narrow rule Amphion's retired sync-claude-md followed: change only the docs that describe code this diff touched, in place.
  • Installing both. Amphion's skills use the same SKILL.md format, and Copilot can load them. The two PR skills have different names, so both can be installed side by side: zethus-pr-description is Amphion's pr-description plus evidence, what was not checked, and decisions. Pick one per repo so the agent doesn't have to choose.

Jira integration

Two optional, standalone skills that translate between specs and Jira stories. Like the modernization extensions above, neither is part of the core ten and both install the same way, alongside them. Neither writes to Jira without an explicit human approval given in the same session — see docs/jira-skills.md for what "decent" means and the full guardrail.

PieceInstalls toPurpose
skills/spec-to-stories.github/skills/Turns a Markdown spec into one Jira-ready story per file plus an index, each INVEST-checked with a self-check table and a flag on any failing criterion; groups stories into epics when the spec has phases
skills/story-enrich.github/skills/Researches an existing Jira story against the real codebase (via research-existing-code, cited to file:line) and proposes a human-relevant enrichment plus a diff against the original
scripts/jira-access.py.github/zethus/scripts/Detects a jira/acli CLI or a REST token in the environment. An available Jira/Atlassian MCP tool is checked by the skill itself, not this script. Exit codes: 0 CLI or REST found · 1 neither, degrade to pasted input · 2 usage error
templates/story.md.github/zethus/templates/One story: user story statement, context, acceptance criteria, out of scope, dependencies, INVEST self-check
templates/stories-index.md.github/zethus/templates/The index a batch of stories is listed under, grouped by epic when the spec has phases

New config keys, all optional (see zethus.config.example.json): stories.dir, jira.cli, jira.baseUrlEnv, jira.emailEnv, jira.tokenEnvVars.

Unlike the rest of this kit, these two skills also work unmodified under Claude Code — the SKILL.md format is shared between the two harnesses, see docs/jira-skills.md.

Maintaining this kit

  • Keep it organization-neutral. It must contain no company, client, or project names and no private paths. A project-specific fact goes in a config key, not in the text.
  • Keep the skills standalone. Each one must work when invoked alone, and cross-links between skills are relative (../<skill>/SKILL.md).
  • Keep the plugin's version in plugin.json and its entry in .github/plugin/marketplace.json equal, and bump both on a release: clients use it for update checks. tests/test_copilot_plugins.py enforces that, and that no skill text says .github/zethus/: only install.py writes that.
  • Keep the scripts stdlib-only, with no shell and no network, and cover them in tests/test_zethus.py. That file also enforces Copilot's frontmatter rules on every skill, the minimum ⊂ full spec contract, and that this kit's own Markdown has no broken pointers.
  • Edit the agent and the hook registration only under com.github.copilot/, then run python scripts/sync_zethus_plugin_root.py to rewrite their root copies. tests/test_copilot_plugins.py fails while the copies are stale.
  • When a Copilot format changes, update the table above along with the files that use it.

License

Apache-2.0 © 2026 Ceryce Armstrong