feat: add GitHub Copilot CLI host adapter - #27
Conversation
Synthesized from dev — see docs/release.md on dev for the blocklist.
Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: archcore-ai#24
|
CI approval needed: the fresh Plugin Tests run for commit |
🤖 Solution Draft LogThis log file contains the complete execution trace of the AI solution draft process. 💰 Cost estimation:
📊 Context and tokens usage:
Total: (641.0K + 19.2M cached) input tokens, 94.1K output tokens, $29.805555 cost 🤖 Models used:
📎 Log file uploaded as Gist (6369KB)Now working session is ended, feel free to review and add any feedback on the solution draft. |
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>
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>
Summary
.plugin/plugin.jsonmanifest and camelCase hooks configurationarchcore-*MCP tool names without changing the behavior pinned for existing hosts0.4.23version bumpReproduction and root cause
The prepared adapter still encoded provisional Copilot payload assumptions. Live captures showed that native
create/editcalls put the file in an absolutepathfield rather thanfile_path, and Copilot exposes the Archcore MCP update tool asarchcore-update_documentrather than the shared canonical name. In addition, hooks without an explicit project-relativecwdexecute 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 passmake test-codex-smoke— all 5 Codex marketplace/install/runtime smoke tests pass.archcore/*.mdcreate is denied and no target is writtenadditionalContextreaches the model in an empty projectarchcore-update_documentnormalization regressionUpstream dependencies found during live testing
Two end-to-end acceptance gaps are outside this plugin adapter and now have focused upstream reports:
hookSpecificOutputenvelope forarchcore hooks copilot session-start; Copilot requires top-leveladditionalContextThe 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-
devhead cannot be reopened after the prepared branch was rebased onto the requested base.Fixes #24