Skip to content

feat: add GitHub Copilot CLI host adapter - #27

Merged
ivklgn merged 29 commits into
archcore-ai:devfrom
konard:issue-24-1f2c5a6a3cbb
Jul 27, 2026
Merged

feat: add GitHub Copilot CLI host adapter#27
ivklgn merged 29 commits into
archcore-ai:devfrom
konard:issue-24-1f2c5a6a3cbb

Conversation

@konard

@konard konard commented Jul 23, 2026

Copy link
Copy Markdown

Summary

  • add the native GitHub Copilot CLI .plugin/plugin.json manifest and camelCase hooks configuration
  • route all Copilot hooks through the shared Archcore scripts from the user project root
  • normalize captured native file-tool paths and archcore-* MCP tool names without changing the behavior pinned for existing hosts
  • add real redacted Copilot CLI 1.0.73 payload fixtures, structural/unit regressions, release documentation, and the four-manifest 0.4.23 version bump

Reproduction and root cause

The prepared adapter still encoded provisional Copilot payload assumptions. Live captures showed that native create/edit calls put the file in an absolute path field rather than file_path, and Copilot exposes the Archcore MCP update tool as archcore-update_document rather than the shared canonical name. In addition, hooks without an explicit project-relative cwd execute from the installed plugin directory.

The normalizer now maps those native payloads into the existing internal contract, and the Copilot hook file pins cwd: ".", ARCHCORE_HOST=copilot, native lifecycle keys, mutation-tool matchers, and bounded pre/post hook execution.

Verification

  • make all — JSON, permissions, ShellCheck, and all 385 Bats tests pass
  • make test-codex-smoke — all 5 Codex marketplace/install/runtime smoke tests pass
  • live Copilot CLI 1.0.73 smoke: a direct .archcore/*.md create is denied and no target is written
  • live Copilot CLI 1.0.73 smoke: project-root post-tool cascade context reaches the model
  • live Copilot CLI 1.0.73 smoke: top-level session additionalContext reaches the model in an empty project
  • live native MCP payload capture verifies the archcore-update_document normalization regression

Upstream dependencies found during live testing

Two end-to-end acceptance gaps are outside this plugin adapter and now have focused upstream reports:

The adapter and its independently testable protections are ready for review; initialized-project session context and project-correct MCP operation remain gated by those upstream fixes.

Addresses #24

Supersedes closed PR #26, whose frozen pre-dev head cannot be reopened after the prepared branch was rebased onto the requested base.

Fixes #24

@konard

konard commented Jul 23, 2026

Copy link
Copy Markdown
Author

CI approval needed: the fresh Plugin Tests run for commit 8bce7fd460cfd2b49e8cbcc2c39718df67cc74cb is gated as action_required before any jobs are created: https://github.com/archcore-ai/plugin/actions/runs/30019746799\n\nI verified the run SHA and timestamp, attempted to download its log (GitHub reports log not found because there are zero jobs), and attempted the fork-run approval endpoint; the available credentials receive HTTP 403 (repository admin rights required). Please approve the workflow run so the already-green local suite can execute in CI.

@konard

konard commented Jul 23, 2026

Copy link
Copy Markdown
Author

🤖 Solution Draft Log

This log file contains the complete execution trace of the AI solution draft process.

💰 Cost estimation:

  • Model: GPT-5.6 Sol
  • Provider: OpenAI
  • Public pricing estimate: $29.805555

📊 Context and tokens usage:

  • 641.0K / 200K (320%) input tokens, 94.1K / 128K (73%) output tokens

Total: (641.0K + 19.2M cached) input tokens, 94.1K output tokens, $29.805555 cost

🤖 Models used:

  • Tool: OpenAI Codex
  • Requested: gpt-5.6-sol
  • Thinking level: xhigh (~31999 tokens)
  • Model: GPT-5.6 Sol (gpt-5.6-sol)

📎 Log file uploaded as Gist (6369KB)


Now working session is ended, feel free to review and add any feedback on the solution draft.

ivklgn and others added 13 commits July 26, 2026 21:26
Resolve four conflicts against the host-wiring-parity work on dev:

- plugin.json x3 (.claude-plugin/.codex-plugin/.cursor-plugin) — version
  only; take dev's 0.5.1.
- component-registry.doc.md — one conflicting hunk (the bin/session-start
  plugin-install guard paragraph). Keep dev's text: it is a superset,
  already listing `.plugin/` alongside the other manifests and adding the
  install-cache fragment layer plus the guard-before-CLI-check ordering.
  All Copilot content from this branch merged cleanly and is preserved.

Also align the new .plugin/plugin.json (Copilot manifest) to 0.5.1 — git
reports no conflict on it since it is a new file, but
test/structure/copilot-plugin.bats asserts version parity with the Claude
manifest.

make verify: 438/438 pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Copilot adapter landed without touching the init contour, so
/archcore:init could not resolve a Copilot session at all.

bin/detect-host has no Copilot branch and gets none: Copilot CLI exports
no marker into the shell commands it runs for the agent — every
documented COPILOT_* var is read FROM the user environment, and adding a
detection variable is an open upstream request
(microsoft/vscode#311734). COPILOT_PLUGIN_ROOT exists only inside hook
processes; detect-host runs from a skill's Bash call. A Copilot session
therefore resolves to __UNKNOWN__ by design.

That makes the skill's ask-fallback the only path to a correct agent id
on this host — and it offered just the three detectable hosts, leaving a
Copilot user with no right answer. Step -1 now offers GitHub Copilot CLI
and maps it to `copilot`, and states why the probe can never return it.

Host wiring stays unlisted for copilot: `archcore init --agent copilot`
currently writes .vscode/mcp.json, which Copilot CLI does not read
(project-level MCP is .github/mcp.json or .mcp.json). Rather than
promise a file list the CLI cannot deliver, init omits the wiring line
on this host and seeds content only — a plugin-installed Copilot session
already carries MCP and hooks from the plugin itself. The row lands once
the CLI writes the right path, together with the version-gate bump.

Agent allow-lists gain the third MCP naming. Claude/Cursor see
mcp__archcore__* or mcp__plugin_archcore_archcore__*; Copilot flattens
to archcore-<tool>, verified against Copilot CLI 1.0.73 in
normalize-stdin.sh. `tools:` is an allow-list and unrecognized names are
ignored, so the missing twin silently stripped both sub-agents of every
archcore tool under Copilot.

Tests pin each gap so it cannot regress: COPILOT_* is not a detection
signal and never outranks a real host; SKILL.md and detect-host agree on
the emitted token set (and the ask-fallback note must go if Copilot ever
becomes detectable); allow-lists carry all three namings in both
directions.

README promotes Copilot CLI from Planned to Implemented with the subdir
install spec.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The same archcore MCP server reaches the model under three names,
depending on how it was registered:

  mcp__archcore__<tool>                  project .mcp.json
  mcp__plugin_archcore_archcore__<tool>  plugin-bundled server
  archcore-<tool>                        Copilot flattens "<server>-<tool>"

bin/validate-archcore and bin/check-precision gate on the project naming
alone. Under either other registration they fired and silently did
nothing: validate-archcore fell through to its Write/Edit arm, whose
path check then failed, and check-precision exited at its filter. A
document created that way skipped post-mutation validation and precision
review with no trace anywhere — the hook looked wired the whole time.

host-wiring-parity.adr already carries both namings in the hook MATCHERS.
That is a different layer: matchers decide whether a script runs, this
decides what it sees. Both are needed, and only one was there.

The fold moves out of the copilot branch into one block after host
extraction, so it covers every host. Copilot was already normalized —
this closes Claude Code (plugin-bundled) and Codex, and keeps a single
place to look when a fourth registration appears.

Tests pin the fold at both levels: normalize-stdin for each host, and
end-to-end through the real validate-archcore and check-precision, since
the failure mode is silence rather than error. Two negative cases guard
the blast radius — a foreign MCP server is never rewritten, and Copilot's
ordinary file tools are not mistaken for a server prefix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot's documented component defaults are agents/, skills/, hooks.json
and .mcp.json — commands/ is not among them (copilot-host-support.rnd).
Every other host picks the seven /archcore:* wrappers up by default, so
the omission was invisible: the manifest looked complete while Copilot
users had no command palette at all.

The wrapper-to-skill parity test is the one that matters long term — a
wrapper naming a removed skill would surface a /archcore:<name> entry
that dead-ends, and Codex has had that guard since its own port.

Whether Copilot actually loads Claude-format wrappers is the open half;
the reconnaissance probe answers it before the rest of the adapter work
builds on top.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
check-code-alignment spawned two processes per matching document per
token — a `grep -qxF` against a seen-file plus a `basename` — so its cost
tracked MATCHES, not corpus size. Measured on a fast laptop:

     matching docs     before     after
                20      155ms      52ms
                50      317ms      63ms
               100      591ms      80ms
               200     1155ms     113ms

Every host runs this hook on a 1-second timeout. Past roughly 170
documents mentioning a common source root, it exceeded that budget
outright — and because injection is additive and non-blocking, a timeout
raises nothing: the write proceeds, no context is injected, nothing is
logged. Push-mode simply stopped working on the repositories with the
most context to give, invisibly.

The dedup moves into a single awk pass over all tokens' hits, keeping
each document's first (most specific) occurrence — same semantics as the
seen-file, since tokens are already processed longest-first. Type
classification uses parameter expansion instead of basename. Spawns are
now O(tokens), independent of matches.

Output is unchanged: verified byte-identical against the previous
implementation across six edit paths on this repo's own knowledge base,
and the existing 14 check-code-alignment tests pass untouched.

test/unit/hook-latency.bats pins all of it, and was confirmed to fail
against the old implementation (578ms on the budget test, 6.3x on the
scaling test) — a latency test that cannot fail is decoration.

Note for the record: the deny guard check-archcore-write was never at
risk here. It runs as a separate hook entry with its own budget and
answers in ~30ms even on a 200-document corpus, so Copilot's fail-open
preToolUse timeout stays unreachable. The latency test keeps that true
too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two of the four component pointers in .plugin/plugin.json were wrong in
opposite directions: agents/ pointed at files Copilot cannot read, and
mcpServers pointed at a server Copilot launches in the wrong directory.

Agents. Copilot CLI loads plugin agents only from *.agent.md files — the
plugin reference calls the field "Path(s) to agent directories (.agent.md
files)", and the how-to and concepts pages say the same. Our agents/ holds
plain NAME.md, so archcore-assistant and archcore-auditor never existed on
that host, and the flat archcore-<tool> entries added to their allow-lists
earlier in this PR were dead weight.

The copies go in copilot-agents/ rather than beside the originals because
.agent.md still matches the *.md glob Claude Code and Cursor use: a sibling
copy would hand both hosts two files declaring the same frontmatter `name:`.
The .toml variants for Codex never had this problem — their extension is
foreign to every md-globbing host. Three tests hold the copies in place:
they exist, they are byte-identical (cmp), and no agent in agents/ lacks a
counterpart — the last one guards the direction cmp cannot see.

MCP. github/copilot-cli#4234: Copilot launches a plugin's MCP children with
cwd set to the plugin install root and passes them no project path, not even
the COPILOT_PROJECT_DIR that plugin hooks receive. For archcore mcp that is
not a wrong answer but a silent one — it finds no .archcore/ in the cache,
reports an uninitialized project, and init_project then creates .archcore/
inside ~/.copilot/installed-plugins/. Every tool returns success.

So the manifest drops mcpServers, and Copilot gets its MCP from the project's
own .mcp.json (archcore init --agent copilot, CLI >= v0.6.4) or from
~/.copilot/mcp-config.json. The plugin-root .mcp.json file stays — Claude Code
discovers it there with no manifest key, which is also why the Cursor remedy
of moving the file out could not be reused. copilot-mcp-architecture.adr
records the reasoning, including what is still unverified: GitHub's reference
gives mcpServers no default path while its concepts page describes plugin-root
.mcp.json as auto-discovered, so whether removing the key is sufficient needs
a live probe. The CLI's existing plugin-cache guard would settle it either
way, but its fragment list does not yet name Copilot's cache — tracked in the
ADR as the follow-up that completes the third layer.

Also filled three gaps copilot-plugin.bats had against codex-plugin.bats:
./-relative paths are resolved rather than string-compared, .plugin/ may hold
nothing but plugin.json, and metadata parity with the Claude manifest now
covers name and description, not version alone.

467 tests pass. Each new assertion was checked against the state it guards.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
hooks.bats did not mention copilot once, and the reason it went unnoticed is
the file's shape: two near-identical tests per host, so adding a host meant
copy-pasting two more, and nobody did. Worse, a copy would have passed.
Copilot names the field "bash", not "command", so `jq '.. | .command?'`
returns nothing for copilot.hooks.json — a copied test iterates an empty set
and reports ok. Coverage that exists only in the test name.

The six per-host tests collapse into a table of four rows plus an enrollment
guard, and the extractor takes .command // .bash. An empty extraction now
fails loudly instead of passing quietly, which is the property that was
missing. Net effect: nothing anywhere verified that Copilot's hook scripts
resolve or that they carry the executable bit; both are checked now, and both
were confirmed to fail when the condition they guard is broken.

Same shape of hole in json-configs.bats: name/version parity compared Claude
against Cursor only, leaving Codex and Copilot free to drift while
verify-plugin-integrity §4 claims all four are compared. Now one table over
every shipped manifest, description included, with an enrollment guard so a
fifth host cannot ship unchecked.

check-code-alignment had a host-shape test for Cursor alone. Copilot's is not
a formality: the path arrives inside an escaped JSON string under toolArgs,
so extraction is host-specific, and the reply must be top-level
additionalContext with no hookSpecificOutput wrapper. Verified end to end —
the same payload forced onto the claude-code branch produces nothing at all,
which is what a break here would look like in production: no context, no
error, write proceeds, 1s budget that fails open.

Also adds test-copilot-smoke, modelled on the Codex one down to the
`command -v` skip so it ships and stays quiet in CI. Its assertions are
filesystem-shaped rather than output-shaped: Copilot's CLI wording is not a
contract we control, but "every path the manifest names survives packaging
with its permissions intact" is exact, and is precisely what broke for Codex
in issue archcore-ai#2. The MCP check carries the #4234 reasoning and treats
`copilot mcp list` as advisory, since whether a plugin-root .mcp.json is
auto-discovered is still unresolved from GitHub's own documentation.

Copilot CLI is not installed here, so the smoke test could only be verified
by simulating an installed tree: the locator, the manifest walk, the hook
extraction and the mcpServers assertion were all exercised against a real
copy of the shipped layout.

466 tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
host-adapter-contract.spec has demanded "a dated three-probe verification"
from every adapter since it was written, and named hooks-validation-system.spec
as where the records live. That section never existed. No protocol, no format,
no records — for any host. The contract's most expensive requirement was the
one nothing checked, and by its own Conformance clause not a single host
counted as supported.

The protocol had two problems to fix, not one.

Coverage: the draft on the codex branch ran main Write, delegated Write and
delegated Edit — three passes over check-code-alignment, never touching
check-archcore-write or validate-archcore. Two of the three guards the contract
makes MUST-provide went unobserved. The contract's own wording had the same
hole. So it becomes one probe per shipped guard: A (alignment), A-d (the same
guard through delegation), B (the deny guard), C (MCP validation), D (the
timeout path). Still three probes in the contract's sense, since A-d is A over
a delegating surface.

Feasibility: conformance 13 forbids probe lines in committed code, which is
what made the protocol unrunnable — the only known way to watch a delegated
hook fire was to instrument a real script. test/probe/mkprobe wraps the guards
in a COPY instead, so nothing needs cleaning up and nothing can leak into a
commit. Two traps are designed around explicitly: the copy gets a sentinel
version, because a version matching an installed build lets the host serve its
cache and report on someone else's tree (that is what cost the first attempt,
per jtbd1-phase2-hardening-delegated.plan); and the probe project is a sibling
of the plugin copy, never a child, because session-start walks upward for
manifests and would exit silently from inside one.

P0 is a gate, not a row: a session that cannot prove it loaded the probe tree
produces no record at all. That is the difference between "the hook did not
fire" and "we were looking at the wrong tree", and last time it was read the
wrong way round.

Records land with every host at deferred:not-yet-run. That is the point — it
converts a silent contract violation into a visible, tested one, and rows get
replaced as runs happen. probe-records.bats derives its host list from
host-coverage-matrix.bats, so a fifth host owes a row automatically.

Three tests, and each found something while being written:

  probe-wrapper.bats compares wrapped against original across all 25 stdin
  fixtures. It failed first on session-start — not a wrapper defect: `archcore
  hooks session-start` emits context only the first time it sees a project, so
  comparing both scripts in one directory always "proved" the wrapper ate the
  output. Every invocation now gets its own project. Re-verified by breaking
  the wrapper on purpose: swallowing stderr fails the test.

  probe-records.bats first parsed the wrong table entirely — the bare marker
  names appear in the spec's prose, so the sed range opened early. It now
  matches only the HTML comment form, and additionally rejects a pipe inside a
  cell, which would shift every column after it and leave the outcome check
  reading the wrong fields while reporting green.

  probe-hygiene.bats runs mkprobe and asserts the plugin tree is unchanged
  afterwards, so a future version that writes in place fails even if it never
  uses the marker string.

The records section lives in its own spec rather than inside
hooks-validation-system.spec as originally planned: the protocol is about host
adapters, not about the internals of the hooks system, and the contract now
points there directly.

479 tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The plugin shipped a fourth host while its own documentation still said three,
and in three places the same file already contradicted itself:

  README.md said "Works in Claude Code, Cursor, and Codex CLI" nine lines above
  a supported-hosts table listing GitHub Copilot CLI as Implemented.

  verify-plugin-integrity already performed 17 checks — sections 3a and 11a for
  Copilot were added when the adapter landed — but reported "X / 15", so those
  two could pass or fail without anyone seeing it. Section 4's row claimed
  "3 hosts" while section 4's own body compares four.

  host-adapter-contract listed Copilot under "planned adapters" in Scope while
  Constraints already carried its blocking-semantics row.

Three claims were not merely stale but wrong:

  multi-host-plugin-architecture said both MCP-shipping hosts "launch the MCP
  with cwd inherited from the user's project process, which is the correct
  workspace". That is exactly what Copilot does not do (#4234), and the ADR
  named Copilot nowhere.

  host-adapter-contract said a shared manifest is preferred "where the host
  reads it (Copilot's loader checks .claude-plugin/plugin.json)" — true but
  misleading: .plugin/ is searched first, and copilot-adapter-design chose it
  deliberately rather than falling back to Claude's.

  copilot-adapter-design listed plugin-shipped .mcp.json among the contracts
  the adapter rests on, and left agent file naming as a question the smoke test
  would answer. Documentation answered both first, and against that ADR: agents
  need *.agent.md, and plugin MCP is unusable. Both premises are now marked as
  broken in place rather than quietly deleted — the ADR records what was
  believed and what replaced it.

Also documents the parts a reader would otherwise have to infer: why
copilot-agents/ exists as a separate directory, why there is no fourth
marketplace catalog, why the commands pointer is load-bearing on exactly one
host, and that on Copilot host wiring is a dependency rather than a nicety.

Remaining living documents follow in part 2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
component-registry described a plugin that no longer exists in three ways: it
called the MCP config "shared MCP registration for Claude Code and GitHub
Copilot CLI" (Copilot ships none), listed the Copilot manifest as carrying an
mcpServers pointer, and said check-code-alignment emits
hookSpecificOutput.additionalContext on Copilot — it emits a bare top-level
additionalContext there, and the wrapper is exactly what Copilot ignores. Its
overview line said three hosts two lines above a sentence saying four.

skills-system was still scoped to three hosts and had no statement of what
happens when detect-host returns __UNKNOWN__ — which on Copilot is not an edge
case but the only path, since that host sets no environment marker a hookless
helper can read.

The gate moves 0.6.1 -> 0.6.4 in the same commit as the spec that pins it,
because a bump split across two commits leaves the suite red in between. v0.6.4
is where the CLI stopped writing .vscode/mcp.json — dropped by Copilot CLI in
v1.0.37 — and started writing the workspace-root .mcp.json it reads. That
matters more on Copilot than anywhere else: with no plugin-shipped MCP, an
older CLI leaves the host with skills and hooks and no document tools.

So init/SKILL.md finally gets a Copilot wiring line. It replaces "copilot has
no file list yet", and with it goes the special empty-route branch that existed
only because there was nothing to offer. The line also says what the other
hosts' lines do not need to: on Copilot, wiring is not an improvement, and if
the version gate disables it the closing message has to say so rather than
seed silently.

Both gate tests were rewritten rather than retargeted. The stale-reference
check now matches any older gate version instead of the single literal "0.6.0"
it was watching, and the SKILL/spec agreement test reads the version out of
SKILL.md instead of hardcoding it a second time — the next bump touches one
literal and the test still catches the spec falling behind.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
agent-system still called itself the spec for "the Archcore Claude Plugin's
subagents" and said nothing about file formats at all — which is where the
whole per-host story now lives: .md for Claude and Cursor, .toml for Codex,
*.agent.md in its own directory for Copilot, and why that directory has to be
separate. It also never explained why every tool appears three times in the
allow-lists, or that the asymmetry matters: an allow-list missing a naming
loses a capability, while the auditor's deny-list missing one silently grants
the read-only agent the power to mutate.

plugin-testing.guide claimed "212 tests" in two places (actual count is past
480 and moves every week), listed four fixture host directories where six
exist, and documented no smoke targets. The counts are now gone rather than
corrected — a number that must be edited on every commit is a number that will
be wrong.

Added the three things the guide would have had to teach anyone touching this
PR: prefer a table over a per-host copy (with the reason — a copied test can
iterate an empty set and report ok), give each session-start invocation its own
project (the CLI emits context once per project, which reads as a swallowed
output), and the checklist for adding a fifth host, where three separate
enrollment guards now fail until it is registered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
plugin-development.guide is the contributor onboarding document, and it was
the furthest behind: prerequisites named three hosts, the local-load block
showed two, the hooks section listed three configs, the env-var list said
"each host's canonical variable" above exactly three bullets, and the MCP
troubleshooting heading covered Claude and Codex only.

Beyond the counting, it now says the things a contributor would otherwise
discover by breaking something: that Copilot's hooks config is a different
shape rather than the same shape with different names (bash not command,
timeoutSec not timeout, flat entries, no postToolUse matcher), so a config
copied from another host loads cleanly and does nothing; that exit 2 is only a
warning there while a preToolUse timeout fails open; that COPILOT_PLUGIN_ROOT
exists only inside hook processes, which is why detect-host cannot use it; and
that agents must be *.agent.md in their own directory. Two new troubleshooting
entries cover the two failures most likely to be misread as bugs — no MCP
tools at all on Copilot (expected before wiring) and agents not appearing
(wrong extension or wrong pointer).

actualize-system said "all hosts (hooks.json, cursor.hooks.json,
codex.hooks.json)" in one place and "all three host hook configs" in another —
already inconsistent with each other before Copilot existed. More usefully, its
JSON snippet is now shown twice: the shared shape, and Copilot's, which differs
in ways that matter. The normative clause added with it is the real content —
where a host's postToolUse takes no matcher, the script must filter to the same
set, because the guarantee is behavioural parity, not config symmetry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
hooks-validation-system carried the one claim in the documentation set that was
not merely stale but unsafe: "Per Claude Code documentation, exit code 2 is a
blocking error." On Copilot exit 2 is a warning and the write proceeds; a deny
there has to be written to stdout as permissionDecision JSON. The spec also
listed Copilot alongside Claude for hookSpecificOutput in two places, which is
backwards — that wrapper is precisely what Copilot ignores. A reader
implementing a fifth host from this document would have shipped a guard that
reports a block the host never performs.

Both specs now state the property the per-host divergence is in service of: a
behavior's trigger set must be identical across hosts, and only the mechanism
that selects it may differ. That is what makes Copilot's matcherless
postToolUse legitimate rather than a gap — the filtering moved into the script
— and it is the clause a future host adapter needs.

plugin-architecture's event matrix gains its Event (Copilot) column, and the
matrix now has a conformance clause tying each row to host-coverage-matrix.bats
so the table cannot drift from the configs it describes. Its scope line, ASCII
diagram, component table and brace-expanded hooks path were all still at three.

Both specs also absorb the two findings from earlier in this PR that belong in
normative text rather than in a commit message: injection cost must not scale
with match count (it is additive, so a timeout is silent, and it stopped
working on exactly the largest knowledge bases), and the normalizer must fold
every MCP naming before a guard inspects a tool name, so that a guard which
fires always also acts.

This completes the living-document sync: no spec, guide, doc, README or skill
in the set still enumerates three hosts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ivklgn and others added 2 commits July 27, 2026 15:40
Minor rather than patch: this release adds a host. The four manifests are
kept byte-identical per bump-plugin-version.cpat, and json-configs.bats now
compares name, description and version across all four rather than two, so a
half-applied bump fails CI instead of shipping.

Applied by hand rather than through jq — jq reformats the capabilities array
in the Codex manifest, and the pattern is explicit that a version bump touches
the version and nothing else.

Tagging and the release itself are the maintainer's.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mutation-tested the tests this branch adds or changes: break the artifact a
test claims to guard, then check the test actually goes red. 41 mutations, 39
caught. The two survivors:

release-blocklist.bats — is_stripped() anchored the path immediately after
`rm -rf `, so it only ever matched a bare top-level name. Everything the
plugin ships lives under plugins/archcore/, so the strip line that would
really break an install reads `rm -rf plugins/archcore/skills` and matched
nothing. Adding lines that delete skills/, commands/, assets/ and
copilot-agents/ from the release left the whole structure suite green — a
synthesized `main` carrying an empty plugin on all four hosts. Seven negative
assertions were affected; the copilot-agents and .plugin ones added on this
branch inherited it. Fixed with an optional path-prefix group, which still
refuses to match `agents` against `copilot-agents`.

init-skill.bats — the claude-code host-wiring row is pinned by filename, the
copilot one was not, so the row added for it could be trimmed to any subset
silently. On copilot that row is load-bearing: the plugin ships no MCP for
that host, so .mcp.json is the only thing granting document tools. Added the
twin assertion, including the "never optional" clause.

Also confirmed by injection: the copilot stdin fixtures are load-bearing, the
canonical MCP naming fold is guarded both ways, detect-host cannot start
emitting "copilot" unnoticed, and the probe wrapper's transparency test fails
if the wrapper swallows stderr, flattens exit status, or stops logging.
hook-latency is stable across five runs; copilot-plugin-smoke skips entirely
without the CLI installed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ivklgn
ivklgn merged commit 2a9f925 into archcore-ai:dev Jul 27, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants