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:
| Surface | todo tool | Where the plan shows |
|---|---|---|
| VS Code agent mode | Supported (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 CLI | Not documented as supported, and reported to resolve to nothing (copilot-sdk#1641) | The checklist under the header of every reply |
| JetBrains agent mode | Not documented either way | Whichever 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
| File | Installs to | Purpose |
|---|---|---|
copilot-instructions.md | .github/copilot-instructions.md | The 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.md | Path-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.json | Project 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 goes | The client's plugin folder | The repo's .github/, committed | ~/.copilot/, on your machine only |
| Who gets it | You, in every repo you open | Everyone who clones the repo, plus the Copilot cloud agent | You, in every repo you open |
| Changes the repo | No | Yes: a PR adding .github/ files | No. Optional per-repo config stays untracked |
| Updates | From the marketplace | Re-run in a PR | Re-run on each machine |
| Right when | You want it everywhere, with one setting | The team adopts the process, or the cloud agent must follow it | A 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 itself | What 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 text | The 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 --userfinds a plugin the Copilot CLI recorded, writes nothing and says so.--forceinstalls anyway.install.py --user --migrate-to-pluginremoves an earlier user-mode copy. It removes what--uninstallremoves, keeps your own files, and exits 1 if an edited skill stays and still shadows the plugin (--forceremoves 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:
| Host | Reads the agent from | Reads 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 $schema | com.github.copilot/hooks/hooks.json only |
| IntelliJ's Copilot language server (plugin 1.20.0, server 1.555.7) | agents/ at the plugin root | hooks.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
zethusagent, theSessionStarthook 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:
- Edit
.github/zethus.config.json. Setgates.stepsto mirror your CI workflow, gate for gate.run-local-gates.py --listshows what it would run. - Commit the
.github/changes in a PR like any other change. - In your Copilot chat, pick the zethus agent to run the whole procedure. Or invoke a single
skill, such as
/write-spec-minimumor/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 piece | User-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 builds | Only 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. --uninstallremoves only the manifest's files that you haven't edited (--forceremoves 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, sonpm run lintwould 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.
| Piece | VS Code Copilot Chat | Copilot CLI | JetBrains (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.md | Yes (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 checked | Not checked | Yes: 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.
| Setting | Default 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-legacyto also writeglobal-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/agentsor~/.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.agentSkillsLocationsandchat.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/skillsor~/.agents/skills. - Copilot CLI: add custom instructions:
$HOME/.copilot/copilot-instructions.mdand$HOME/.copilot/instructions/**/*.instructions.md;COPILOT_HOMEreplaces$HOME/.copilot. - About agent skills: lists
~/.copilot/skillsand~/.agents/skillsfor agent mode in IDEs, JetBrains included. - GitHub changelog, 2026-05-13: in JetBrains, "define custom agents at the global level
using the
.agent.mdfile under~/.copilot/agents." - copilot-intellij-feedback #1517: open report that the JetBrains plugin doesn't
detect user-level
.copilotskills on Windows. It predates the settings screen above. - Repository instructions in your IDE, JetBrains tab: a global
global-copilot-instructions.mdon 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 inchat.agentFilesLocations,chat.agentSkillsLocationsandchat.instructionsFilesLocations. All three default to include~/.copilot/agents,~/.copilot/skillsand~/.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/agentsand~/.copilot/skillsare listed and enabled. - With
--jetbrains-legacy, an existingglobal-copilot-instructions.mdis left alone. Paste the rules from~/.copilot/instructions/zethus.instructions.mdinto 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.
| # | Location | Use it for |
|---|---|---|
| 1 | .github/zethus.config.json | The team's committed answers (repo mode). |
| 2 | $ZETHUS_CONFIG | A path, relative to the repo root unless absolute. Point it anywhere. |
| 3 | .copilot/zethus.config.json | Per-repo answers you may not commit. Keep it untracked (below). |
| 4 | .claude/amphion.config.json | Amphion's config; the shared keys mean the same thing. |
| 5 | ~/.copilot/zethus/config.json | Your 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-reflexionpackage; - 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.jsonand 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-dirwrites 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.
| Key | Used by | Meaning |
|---|---|---|
branchModel.base | agent, zethus-pr-description, docs-sync-check, base-freshness | The integration branch. Default: develop if it exists, else the default branch. |
branchModel.style | agent, 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.maxBehind | base-freshness, agent, pre-push-gates | How 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-gates | Ordered {name, command, exitCode?}. command is a string or an argv list; there's no shell. |
gates.lint / .build / .test / .mandatedChecks[] | run-local-gates | Amphion'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-spec | Default docs/specs, and the kit's templates. |
adr.dir, adr.template | new-adr | Default 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-check | Markdown files to skip, such as generated changelogs. |
commits.aiTrailer | agent | The co-author trailer that AI-assisted commits carry. |
commits.signature | agent | The 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.checklist | agent | "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 | |
|---|---|---|
| Branches | A new one per change, cut from origin/<base> | One long-lived branch, rebased onto origin/<base> every few days |
| Phases | One branch and one PR each, Phase 0 first; each branch stacked on the previous phase's, its PR targeting that branch | One commit series each, on the same branch, Phase 0 first, pushed to the same PR |
| Next phase starts | Once the previous phase is committed, green on the gates and pushed. Its PR need not be merged | The same |
| Diff base | origin/<base>...HEAD; for a stacked phase, origin/<previous-phase>...HEAD | The merge-base with the freshly fetched origin/<base>, never a local ref |
| Merging | A 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 rebase | Rebase the stack from its top with git rebase --update-refs origin/<base>; push each branch with --force-with-lease | Push 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:
- Stage 0 · Orient — before any work, so the branch is cut from, or rebased onto, a base just confirmed current;
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.
| Piece | Format | Documentation |
|---|---|---|
| Repository instructions | .github/copilot-instructions.md, plain Markdown | Adding repository custom instructions · support matrix |
| Path-specific instructions | .github/instructions/*.instructions.md with applyTo | VS Code custom instructions |
| Copilot plugin | Agent 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.json | CLI plugin reference · VS Code agent plugins · Agent Plugins spec |
| Custom agent | .github/agents/*.agent.md: name, description, tools | Custom agents configuration · Creating custom agents · VS Code custom agents |
| Skills | .github/skills/<name>/SKILL.md: name (matches the folder), description | About 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:
| Doc | Targets | Status |
|---|---|---|
docs/modernization-shared.md | The shared design both extensions build on: rule recovery, the constitution template, the overseer gate | PARTIAL — v1 built, below |
docs/batch-modernization.md | Spring/JPA/JDBC batches, ksh scripts, Control-M-scheduled jobs, stored-procedure-heavy jobs; first dialect Oracle/PL-SQL | PARTIAL — v1 built, below |
docs/webapp-modernization.md | Struts, old Spring MVC + JSP, plain JSP/servlets, and thirteen other legacy web targets, ranked | PARTIAL — 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.
| Piece | Installs to | Purpose |
|---|---|---|
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).
| Piece | Installs to | Purpose |
|---|---|---|
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.styleandbranchModel.maxBehindare 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-fixwhen the spec runs out,resume-interrupted-phasewhen a delegate dies,initialize-ci, andlog-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-phasepoints to Amphion's two exception handlers rather than re-implementing them. - Shared headings. Zethus's
zethus-pr-descriptionkeeps Amphion's Not in this PR (intentional) and Noticed but out of scope headings, soflag-or-fixdeferrals land in the same place.docs-sync-checkkeeps the narrow rule Amphion's retiredsync-claude-mdfollowed: change only the docs that describe code this diff touched, in place. - Installing both. Amphion's skills use the same
SKILL.mdformat, and Copilot can load them. The two PR skills have different names, so both can be installed side by side:zethus-pr-descriptionis Amphion'spr-descriptionplus 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.
| Piece | Installs to | Purpose |
|---|---|---|
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.jsonand its entry in.github/plugin/marketplace.jsonequal, and bump both on a release: clients use it for update checks.tests/test_copilot_plugins.pyenforces that, and that no skill text says.github/zethus/: onlyinstall.pywrites 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 runpython scripts/sync_zethus_plugin_root.pyto rewrite their root copies.tests/test_copilot_plugins.pyfails 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