mirror of
https://github.com/mksglu/context-mode.git
synced 2026-09-19 03:27:16 +08:00
next
22 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e918eac6c1 |
fix(windows): stop boot-install EPERM + cmd-window flash from shell:true dropping cwd (#861)
The background install of the three optional fetch deps (turndown, turndown-plugin-gfm, @mixmark-io/domino) spawned npm with `shell: IS_WIN32`. On Windows + Node, `shell: true` DROPS the `cwd` option, so the spawned cmd.exe runs in an arbitrary working dir (C:\Windows under Claude Code). `npm install` then tries to create `C:\Windows\node_modules` -> EPERM on every MCP boot, and a cmd.exe window flashes each time; the install never persists, so the existsSync guard never short-circuits and it re-fires every session (reported by @lravizzoni with npm-debug-log evidence). Prefer running npm's own CLI through node directly (no `.cmd` shim, no shell): resolve `npm-cli.js` beside `process.execPath` and spawn it with `shell: false` (honors `cwd`) + `windowsHide: true` (no console window). Fall back to the `npm.cmd` shim only when npm-cli.js can't be located, so a working host — e.g. a POSIX layout where npm-cli.js isn't beside node — never regresses (POSIX already used shell:false, so its behavior is unchanged). Also surface spawn failures and non-zero exits to stderr; this EPERM was invisible for months behind stdio:"ignore" + an empty error handler. Tests: structural regression pins the node/shell:false/windowsHide/fallback contract (regex-free, matches the start.mjs test pattern); a portable behavioral test pins the runtime property the fix relies on (shell:false honors cwd). #634 background-install contract preserved. Needs Windows CI / on-device confirmation. |
||
|
|
dfff7b873f |
fix(lifecycle): propagate client death through the Linux re-exec proxy (#862)
On Linux, start.mjs re-execs under Bun to dodge the better-sqlite3 SIGSEGV (#564); the node proxy forwards stdin to the Bun child. Its stdin EOF handler was a no-op and the proxy parked forever, so when the MCP client exited the proxy never closed the child's stdin nor exited — the child's direct parent stayed alive (defeating its ppid watchdog) and its stdin never EOF'd, leaving an orphaned pair pinning a CPU core at PPID=1 (reported by @elhoim: 5 orphans, ~3 of 6 cores). The proxy now tears the child down on stdin end/close/error: forward EOF for a graceful self-reap via the child's own lifecycle guard, then escalate SIGTERM (2s) -> SIGKILL (5s) so a wedged child can never outlive its client. Re-exec under Bun is unchanged (#564 preserved). The escalation timers are deliberately not unref'd so teardown liveness never depends on the top-level await surviving a refactor. Behavioral e2e (real proxy bytes, old-vs-new x graceful-vs-wedged): old leaks the child; new reaps it in ~4ms graceful / 5s wedged. Source-introspection test pins the contract; lifecycle + start.mjs suites green. |
||
|
|
1aee4808d0 | fix(install): derive version from package.json across all manifests so none bake stale (#768) | ||
|
|
73cf07e015 | docs/tests: update adapter count 15→17 (copilot-cli + antigravity-cli) | ||
|
|
9f34c6f11b |
Add GitHub Copilot CLI + Antigravity CLI (agy) support (#787)
* feat(adapters): add Antigravity CLI (agy) + GitHub Copilot CLI support
Add two agentic CLI adapters onto next's existing adapter registration —
without the abandoned PR's setup subcommand / consolidated registry.
Antigravity CLI (agy):
- MCP + capture-only PostToolUse hook adapter (agy honors no stdout veto in
auto-run mode; verified against agy 1.0.5). The agy hook payload
{conversationId, toolCall, workspacePaths} is mapped onto the shared
capture pipeline.
- Ships a Claude-layout plugin bundle (configs/antigravity-cli/) installed via
`npm run install:agy` (mirrors install:openclaw), with a version-skew
capture-hook probe in the installer.
GitHub Copilot CLI (1.0.59):
- json-stdio hook adapter with six events: PreToolUse, PostToolUse, PreCompact,
SessionStart, UserPromptSubmit, Stop. Overrides CopilotBaseAdapter to emit the
FLAT {type,command} + top-level "version": 1 hook config Copilot CLI requires.
- MCP install via `copilot mcp add context-mode -- context-mode`.
- Fix a latent Stop-hook bug: a session_end event with no `data` threw inside
insertEvent (createHash(undefined)) and was silently dropped.
Cross-cutting:
- #774: probe agy/copilot config markers before the generic ~/.claude check.
The copilot marker is narrowed to context-mode-written files
(~/.copilot/mcp-config.json | hooks/context-mode.json), not a bare ~/.copilot/
dir, so a co-installed-but-unconfigured Copilot CLI cannot steal detection
from a Claude Code user.
- Dispatcher fails OPEN (exit 0) on a missing hook script: GitHub Copilot CLI
treats an exit-1 PreToolUse hook as DENY, so a version skew (a newer adapter's
hook command on an older global) would otherwise brick the agent.
Fixes #774. Fixes #775.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci: regenerate bundles for antigravity-cli + copilot-cli support
Picks up the new HOOK_MAP entries, client-map keys, validPlatforms,
getSessionDirSegments cases, and the fail-open dispatcher into the
esbuild-generated runtime bundles.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(platform-support): sync support docs to 18 platforms + fix stale Kiro classification
Make README.md and docs/platform-support.md internally consistent and aligned
with the adapter source of truth.
Header sync (18 platforms everywhere):
- The Main Comparison Table (was 11 cols), the Capability Matrix (was 11), and
the README Platform Compatibility table (was 17, missing Kimi Code) now list
the SAME 18 platforms in one shared order. Adds the two branch-new platforms
(GitHub Copilot CLI, Antigravity CLI `agy`) plus previously-omitted Qwen Code,
KiloCode, OpenClaw, Zed, Pi as columns. Each cell sourced from the per-platform
detail sections / adapter source and independently verified.
- Fix five ragged rows in the Main Comparison Table (a dropped trailing OMP cell)
and add CLI Hook Dispatcher rows for qwen-code + copilot-cli.
- GitHub Copilot CLI section: normalize the `**Hook Names:**` label and add the
missing `**Output Modification:**` field for json-stdio-family parity.
Fix stale Kiro classification (code is the source of truth):
- The kiro adapter is json-stdio with working preToolUse/postToolUse hooks
(hooks/kiro/{pretooluse,posttooluse}.mjs + a kiro HOOK_MAP entry), yet the docs
called it "MCP-only (Phase 2 — not implemented)" and the README contradicted
itself ("no hook support" in one place, "native preToolUse/postToolUse" in two
others).
- Reclassify Kiro as json-stdio with PreToolUse + PostToolUse + exit-code-2
blocking across the Overview paradigm table, both wide tables, the dispatcher
table, and the Kiro detail section; document that agentSpawn (SessionStart) and
stop are not yet wired, so session restore after compaction is unavailable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(antigravity-cli): drop vestigial .mcp.json dependency that broke fresh clones
The agy plugin-bundle test asserted configs/antigravity-cli/.mcp.json, but
.mcp.json is gitignored repo-wide and was never committed — so the test passed
on the dev machine (file present locally) yet failed on a fresh clone with
ENOENT. Committing the file is the wrong fix: the .gitignore comment documents
that shipping .mcp.json has silently broken fresh installs before (#253/#531).
- The bundle declares MCP the Claude way via .claude-plugin/plugin.json
mcpServers (committed — the mechanism agy reads on `agy plugin install`),
mirrored by the agy-native mcp_config.json (committed). Remove the vestigial
bundle .mcp.json and stop the test + docs from requiring it. Every file the
plugin test reads is now git-tracked, so a fresh clone passes.
- README: Kiro was still grouped under "Non-hook platforms" in the routing-
enforcement note. Kiro has native preToolUse/postToolUse hooks; it needs the
manual KIRO.md copy only because agentSpawn/SessionStart is not yet wired.
Reword to say so.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(adapters): cross-platform agy installer + copilot-cli COPILOT_HOME parity
Windows fix (real): replace the bash-only agy plugin installer with a
cross-platform Node script so `npm run install:agy` runs natively on Windows
(PowerShell/cmd), not just Git Bash/WSL. agy runs on Windows, so its installer
must too — the old `node -e` wrapper hard-exited 1 on win32. openclaw stays
bash-only (it is genuinely POSIX-only). Removes
scripts/install-antigravity-cli-plugin.sh in favor of
scripts/install-antigravity-cli-plugin.mjs (same preflight + version-skew probe).
copilot-cli hardening (COPILOT_HOME edge case only — the default ~/.copilot
install was and remains correct):
- CopilotCliAdapter.getSessionDir() now roots at getConfigDir() (COPILOT_HOME-
aware), mirroring codex/kimi, so the TS server reads sessions from the same
place the hook runtime (COPILOT_OPTS configDirEnv: COPILOT_HOME) writes them.
Previously a relocated COPILOT_HOME split hook writes ($COPILOT_HOME/...) from
server reads (~/.copilot/...), making sessions appear empty.
- detect.ts copilot-cli marker honors COPILOT_HOME, not just ~/.copilot.
No change to the default (COPILOT_HOME-unset) behavior; a regression test pins
both the ~/.copilot default and the COPILOT_HOME-rooted path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci: regenerate bundles for copilot-cli COPILOT_HOME parity
Picks up CopilotCliAdapter.getSessionDir() and the COPILOT_HOME-aware detect.ts
marker into the esbuild runtime bundles.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(antigravity-cli): installer registers the MCP server (agy plugin install skips it)
`npm run install:agy` ran only `agy plugin install`, which — verified against
agy 1.0.5 — processes a bundle's skills + hooks but logs "mcpServers : skipped
(not found)" and registers NO MCP server. agy reads a plugin's MCP only from a
bundle `.mcp.json` (intentionally not shipped — gitignored repo-wide after
#253/#531) and has no `agy mcp add` command, so context-mode's MCP server was
never registered: users had to add it to ~/.gemini/config/mcp_config.json by hand
(reported on Windows; reproduced on Linux: `mcpServers : skipped (not found)`).
The installer now also writes context-mode into agy's GLOBAL MCP profile
~/.gemini/config/mcp_config.json (idempotent JSON merge, preserves other servers,
tolerates a malformed file) — the file agy actually loads and `context-mode
doctor` checks. Verified end-to-end on agy 1.0.5: `npm run install:agy` →
mcp_config.json gains context-mode → `agy -p "... ctx_execute ... 7 + 5"` → 12.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(server): emit Gemini-safe tool schemas so agy/Gemini CLI expose ctx_* tools
Antigravity CLI (agy) and Gemini CLI use Gemini's function-calling API, which
rejects JSON Schema `const` and `additionalProperties`. When a tool's parameter
schema contains either, the host SILENTLY DROPS that tool from the model's
function list — so agy never sees the ctx_* tools and works around them by
hand-rolling the MCP protocol through its Bash tool (verified on Windows: agy
wrote scratch/call_ctx_stats.js + list_mcp_tools.js MCP clients instead of
calling the tools natively). That defeats the point of context-mode — bash
output floods the context window instead of staying in the sandbox.
context-mode builds schemas with Zod, which emits `const` (from coerce/preprocess
constructs) and `additionalProperties`, with no Gemini sanitization. Wrap the
SDK's tools/list handler to rewrite the EMITTED schema:
- `const: X` -> `enum: [X]` (an identical single-value constraint)
- drop `additionalProperties` (advisory-only; every ctx_* handler parses args
with Zod, which strips unknown keys server-side regardless)
Both transforms are behavior-preserving for every other client (Claude Code,
Copilot, Cursor): const and a one-value enum are equivalent, and no model sends
undeclared properties — only the wire schema changes, never validation or how a
tool is called. Best-effort: if the MCP SDK internals shift, the original handler
is left untouched (no regression). Verified on the real tools/list: all 11 ctx_*
tools now emit 0 `const` / 0 `additionalProperties`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* ci: regenerate bundles for Gemini-safe tool schema sanitizer
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(antigravity-cli): clear agy's stale MCP tool-schema cache on install
agy caches each MCP server's tool schemas under
~/.gemini/antigravity-cli/mcp/<server>/ and does NOT refresh them on reconnect
(verified on agy 1.0.6 against a live Windows install). A cache captured by a
context-mode older than the Gemini-safe-schema fix (
|
||
|
|
04ff30fcc0 |
feat(fetch): add per-call cache ttl (#666)
* feat(fetch): add per-call cache ttl * test: run npm pack dry-run on Windows |
||
|
|
dee946c64d |
fix(pi): prevent mixed-width TUI over-width crashes (CJK/Korean/emoji) (#676)
* fix(pi): CJK wide-character width-aware truncation in PiTextComponent (#665) PiTextComponent/truncateAnsiLine counted every JS character as width 1, but CJK ideographs occupy 2 terminal columns. This produced lines whose actual visibleWidth exceeded the requested width, triggering a pi-tui crash: 'visible width: 162 > terminal width: 147'. Root cause: truncateAnsiLine() iterated chars with visible++ (always 1) instead of accounting for east-asian-width W/F codepoints. Fix: - Add charWidth() helper that returns 2 for CJK/Hangul/fullwidth ranges. - Change truncateAnsiLine to use charWidth and check visible + w > maxWidth (not >=), so a 2-wide char still fits when exactly 2 columns remain. - Export PiTextComponent and truncateAnsiLine for testability. Tests (Slice 10 in pi-mcp-bridge.test.ts): - Pure CJK text respects requested width. - Mixed ASCII + CJK is correctly truncated. - ANSI escape sequences are preserved and not counted toward width. - The real crash line from pi-crash.log fits within terminal width 147. - Edge case: maxWidth 0 or negative returns empty string. Fixes #665 * test(asymmetric-drift): make npm pack dry-run windows-safe --------- Co-authored-by: baifan <ubuntu@BaiFanPC.localdomain> |
||
|
|
fa61570941 |
Fix Claude plugin skills path and pack integrity guard (#661)
* ci: update server.bundle.mjs, cli.bundle.mjs, session hook & security bundles * ci: update install stats * ci: update install stats * ci: update install stats * Fix Claude plugin skills manifest path --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
4986019644 |
fix(codex): detach background npm install so MCP boot doesn't blow Codex's 30s timeout (#634) (#638)
* fix(codex): detach background npm install of fetch-and-index deps so MCP boot doesn't blow Codex's 30s timeout (#634) Codex CLI 0.131.0 enforces a 30s startup_timeout_sec per MCP server (codex-rs/config/src/mcp_types.rs RawMcpServerConfig). Before this patch, every cold MCP boot of context-mode through Codex hit: MCP client for `context-mode` timed out after 30 seconds. Root cause traced via probe instrumentation of start.mjs against a real codex CLI run (codex 0.131.0 + context-mode 1.0.141 installed via `codex plugin marketplace add mksglu/context-mode`): [+0ms] start [+11ms] before selfHealCacheHealHook [+13ms] before normalizeHooks [+13ms] before ensure-deps [+17ms] after ensure-deps [+17ms] before npm-install loop [+23182ms] after npm-install loop / before server.bundle [+23295ms] after server.bundle The synchronous `execSync("npm install " + pkg)` loop for `turndown` + `turndown-plugin-gfm` + `@mixmark-io/domino` blocked the MCP boot path for ~23s. Codex's plugin marketplace git-clones into `~/.codex/plugins/cache/<pkg>/` without running `npm install`, so on every fresh install these three packages were absent and the loop paid the full cold-fetch cost. With Codex's own ~15s websocket prewarm running serially before the MCP handshake on slower hosts, total time to `InitializeResult` blew straight past the 30s budget on macOS Wi-Fi (originally reported by @mksglu). These three deps are only consumed by `ctx_fetch_and_index`'s sandboxed HTML→Markdown subprocess, which resolves them via `require.resolve()` at call time. None of them are touched by the MCP `initialize` handshake or by any other ctx_* tool. Detaching the installs (`spawn(..., { detached: true, stdio: "ignore" }).unref()`) lets the MCP server reply within milliseconds while the installs complete in the background, typically long before any LLM-driven fetch call can fire. If a user invokes `ctx_fetch_and_index` faster than the install completes, the subprocess's existing `require.resolve("turndown")` failure surfaces a typed error to the caller — same posture as any other missing-runtime-dep situation in that code path. Measured end-to-end with real codex 0.131.0 + context-mode 1.0.141 swapped into Codex's plugin cache (Linux/Node v22): Cold boot (no node_modules): ~27s → ~8s Warm boot (deps already present): ~27s → ~7s Coverage added: tests/scripts/start-mjs-mcp-boot.test.ts pins both halves of the contract — (a) the MCP boot slice between `./hooks/ensure-deps.mjs` (last sync step the boot is allowed to block on) and the `server.bundle.mjs` import must not contain any `execSync(... npm install ...)` call, and (b) the three packages are still kicked off via the detached `spawn(NPM_BIN, …)` path so codex marketplace users don't permanently lose fetch-and-index. Red-green verified by stashing the start.mjs change and rerunning the test. * chore(start): extract IS_WIN32 constant to dedupe `process.platform === "win32"` (review nit) |
||
|
|
6cb1490c9b |
fix(family-A): persistence-tier rules — drop stale .mcp.json + portable Tier C hooks (#620)
* fix(family-A): persistence-tier rules — drop stale .mcp.json + portable Tier C hooks Single unified PR for the persistence-tier family. Three issues, one architectural decision: classify every file by who reads / mutates it (plugin-cache vs. user-home vs. workspace-committed) and enforce the correct mutability contract per tier. Issue #604 — hooks.json bidirectional ratchet (already fixed on `next` by merged PR #611 / commit |
||
|
|
63fa0841f5 |
test(postinstall-heal): stub heals to keep regression test under CI budget
The regression test added in
|
||
|
|
ceaec16eff |
fix(postinstall): gate hook-normalization heal with isGlobalInstall (#531 fix-of-fix)
CI run 25734987495 (windows-latest) failed on `npm run build` at the
`assert-asymmetric-drift` step with:
asymmetric-drift: FAIL
- .claude-plugin/plugin.json args[0] is
"D:/a/context-mode/context-mode/start.mjs" but must equal
"${CLAUDE_PLUGIN_ROOT}/start.mjs".
Root cause: scripts/postinstall.mjs section 4 ("Hook normalization at
install time (#414)") calls `normalizeHooksOnStartup` which substitutes
`${CLAUDE_PLUGIN_ROOT}` with an absolute pluginRoot path. Section 4's
only existing guard was a `TMPDIR_UPGRADE_RE` check that catches
/ctx-upgrade staging but does NOT catch contributor / CI installs.
On Windows CI the pipeline runs:
1. `npm install` → triggers `npm run postinstall` → section 4 runs
against the cloned repo, rewriting source-tracked
`.claude-plugin/plugin.json` args[0] from the
placeholder to `D:/a/.../start.mjs`.
2. `npm run build` → invokes `scripts/assert-asymmetric-drift.mjs` (the
new Issue #531 invariant). It reads the now-mutated
file, sees drift, exits 1, build fails.
Fix: gate section 4 with `isGlobalInstall()` — the same heuristic
section -1 already uses ("npm_config_global=true" AND no `.git` walking
up the tree). A contributor's `npm install` from a clone (and CI checkouts)
always have `.git` → isGlobalInstall returns false → section 4 skips →
source files stay untouched. Real `npm install -g context-mode` is
unaffected: no `.git` near the cache dir → guard passes → heal runs.
Test coverage:
- New regression test "postinstall.mjs DOES NOT mutate source-tracked
plugin.json when run from a clone (Windows CI regression)" in
tests/scripts/asymmetric-drift-assert.test.ts. The test runs the REAL
postinstall.mjs (not a mock) against a temp clone-like layout with
`.git` and `node_modules` siblings, asserts plugin.json args[0] is
STILL the placeholder. Any future regression that lets section 4
mutate source files surfaces immediately as a vitest failure.
Verified locally:
$ npm run build → all bundles OK + asymmetric-drift OK + exit 0
$ npx vitest run tests/scripts/asymmetric-drift-assert.test.ts
Test Files 1 passed (1)
Tests 8 passed (8)
|
||
|
|
5a3091a996 |
fix(tests): asymmetric-drift uses .mcp.json.example + postinstall-heal 90s timeout
Two CI failures on v1.0.122 (run 25729116323): 1. tests/scripts/asymmetric-drift-assert.test.ts — 3 tests asserted on repo-root .mcp.json which is no longer tracked after the #531 architectural untrack (commit |
||
|
|
951cd925f6 |
fix(install): CI invariant prevents future .mcp.json / plugin.json asymmetric drift (closes #531 partial — 9 of 10)
Architectural guardrail that prevents the class of bug that caused #531.
The repo ships TWO sibling files carrying MCP server args:
1. .mcp.json (Claude Code reads at plugin load)
2. .claude-plugin/plugin.json (used by Cursor adapter)
v1.0.118 fixed .mcp.json (#411). v1.0.119 fixed plugin.json AND added
self-heal — but ONLY for plugin.json. Asymmetric coverage. Then commit
|
||
|
|
1e73a13d19 |
fix(codex): make plugin discoverable via .agents/plugins/marketplace.json (#525)
PR #525 (tedjy971) reported that context-mode never shows up in Codex CLI's `/plugin` listing despite shipping a `.codex-plugin/marketplace.json`. The reported claims were verified against the Codex Rust source in refs/platforms/codex and OpenAI's published docs — all three load-bearing assertions hold: 1. Codex reads `MARKETPLACE_MANIFEST_RELATIVE_PATHS` = `[.agents/plugins/marketplace.json, .claude-plugin/marketplace.json]` (codex-rs/core-plugins/src/marketplace.rs:21). `.codex-plugin/ marketplace.json` is NOT in this list — Codex never opens it. 2. The local-plugin source `path` must be `./<subdir>`, not `./`. Codex's `resolve_local_plugin_source_path` (marketplace.rs:502-518) does `path.strip_prefix("./")` then rejects empty results with `"local plugin source path must not be empty"`. Our shipped `.claude-plugin/marketplace.json` uses `source: "./"`, which hits this rejection. The error is swallowed silently at marketplace.rs: 446-452 via `warn!(... skipping marketplace plugin that failed to resolve)`, so `codex plugin marketplace add` succeeds with exit 0 but the plugins vec is empty and the user sees nothing in /plugin. 3. `${CODEX_PLUGIN_ROOT}` / `${CLAUDE_PLUGIN_ROOT}` placeholders are NOT interpolated by Codex (upstream openai/codex#19582 OPEN). Grep of codex-rs/core-plugins/src/ confirms zero `interpolat*` / `expand_env*` / `envsubst*` logic in non-test source code. These were also corroborated by OpenAI's own docs at https://developers.openai.com/codex/plugins/build which spell out: - "a repo marketplace at $REPO_ROOT/.agents/plugins/marketplace.json" - "source.path points to that plugin directory with a `./`-prefixed relative path" (example: `./plugins/my-plugin`) - "Only plugin.json belongs in .codex-plugin/" End-to-end verification with Codex CLI v0.130.0: $ codex plugin marketplace add /path/to/context-mode Added marketplace `context-mode`. No silent-drop warning emitted by the warn! path now that source.path resolves to a real plugin tree. Changes: 1. Add .agents/plugins/marketplace.json with canonical schema: { name, interface: { displayName }, plugins: [{ name, source: { source: "local", path: "./plugins/context-mode" }, policy, category }] } Matches the Rust serde shape at marketplace.rs:694-744 exactly. 2. Add plugins/context-mode symlink → repo root, so Codex's `resolve_local_plugin_source_path` lands on a directory that contains `.codex-plugin/plugin.json` (the per-plugin manifest path Codex's load_plugin_manifest expects). 3. Delete .codex-plugin/marketplace.json (dead — Codex never reads it, keeping it ships dead bytes and misleads contributors). 4. Remove .codex-plugin/marketplace.json from version-sync.mjs targets and from the `version` lifecycle git-add list. The Codex marketplace schema has no top-level `version` field per the Rust serde struct, so the new .agents/plugins/marketplace.json doesn't need syncing. Per-plugin version still flows through .codex-plugin/plugin.json which remains in the targets list. 5. Add tests/codex/marketplace-layout.test.ts (6 tests) that mirror Codex's exact discovery logic — strip_prefix("./"), non-empty check, plugin.json presence, placeholder absence — so future drift produces a deterministic local failure long before users hit it. 6. Update tests/plugins/codex-manifest.test.ts and tests/scripts/ version-sync.test.ts to reflect the deletion (with comments pointing at the Rust line numbers for future maintainers). Why we shipped our own fix instead of merging tedjy971's PR #525: Same end-state, more rigor — full Rust-source citations, mirror-the- deserializer tests, e2e verification with the v0.130.0 CLI. Their analysis pointed us at the right problem; this commit gives the project a durable test contract so a regression can't slip past CI silently like the original bug did. Credit: tedjy971's PR #525 surfaced the issue and the canonical layout. |
||
|
|
d3574d564e | merge: v119/bundle-assert-g3 — post-build invariant CI assert (G3 architectural guardrail) | ||
|
|
3a12609c41 |
fix(codex): repair .cursor-plugin drift + lockstep guard (extends PR #512 by @tedjy971)
`.cursor-plugin/plugin.json` had been stuck at v1.0.111 across seven releases (now v1.0.118) because it was missing from the npm `version` lifecycle `git add` list — version-sync rewrote it correctly each time, but the modification was never staged into the release commit. Slice 2 already plugged that hole; this slice replays version-sync to heal the actual drifted file and adds a per-manifest assertion so any future drift fails CI immediately rather than silently shipping. The new "shipped manifests are in lockstep with package.json" block covers every manifest in the version-sync targets[] — adding a new plugin surface forces an explicit test update, surfacing the "did you also wire it into version-sync and the `git add` list?" question at PR-review time. |
||
|
|
308a80f9a3 |
fix(codex): add .codex-plugin/* to version-sync targets (extends PR #512 by @tedjy971)
Without this, every release bump would drift `.codex-plugin/plugin.json` and `.codex-plugin/marketplace.json` further out of sync with the canonical `package.json:version`. Same hazard previously hit `.cursor-plugin/plugin.json` (stuck at v1.0.111 vs current v1.0.118) because it was missing from BOTH the targets[] in version-sync.mjs and the npm `version` lifecycle `git add` list. Two-part fix: - `scripts/version-sync.mjs` → append the two Codex manifests to `targets[]` (so the rewrite touches them). - `package.json` → extend the `version` script's `git add` list to include the two Codex manifests AND `.cursor-plugin/plugin.json` (the cursor manifest had the same defect; without it staged, the rewrite is silently discarded by the npm `version` commit). End-to-end test in tests/scripts/version-sync.test.ts copies all manifests into a scratch repo with a synthetic version, runs the script, and asserts every (version | metadata.version | plugins[].version) field gets rewritten — catches future targets[] drift automatically. |
||
|
|
0bcea04a27 |
chore(ci): slice 4 — regression guard against real production bundles
EXPECTED RED until the Issue #511 ESM sweep agent merges createRequire fixes for src/server.ts and src/cli.ts. The current server.bundle.mjs and cli.bundle.mjs ship with esbuild's throwing 'Dynamic require of ...' shim embedded — exactly the state that broke users in #511. This test is the *forcing function* that turns G3 from advisory into load-bearing. Skipping it would let the sweep land without proof the guardrail wired through to real bundles. Once the sweep merges and the next bundle.yml run rebuilds, this test flips green and the invariant is permanently locked in for every PR. |
||
|
|
30a39d6706 |
chore(ci): slice 3 — wire assert-bundle into npm build chain
Adds 'assert-bundle' npm script targeting all five produced bundles, and chains it from 'build' so every build that finishes successfully has guaranteed-clean ESM output. Pretest, prepublishOnly, and CI Build steps all inherit the assertion. Test pins both the script presence and the chain wiring so a future config refactor can't silently drop the guardrail. |
||
|
|
baf39ec219 |
chore(ci): slice 2 — assert-bundle exits 0 on clean createRequire fixture
Pin the negative-path behavior. A bundle that uses
createRequire(import.meta.url) at module top has no shim and no bare
require('node:...') strings — the script must accept it.
|
||
|
|
61f85398b1 |
chore(ci): slice 1 — assert-bundle script detects 'Dynamic require of' shim
G3 guardrail (Issue #511 class). Adds scripts/assert-bundle.mjs which scans bundle files for the esbuild throwing-require shim and exits 1 if any forbidden pattern is matched. Tracer-bullet RED→GREEN: fixture bundle containing the shim string is correctly rejected. |