Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ name: CI

on:
push:
branches: [main]
pull_request:

permissions:
Expand All @@ -15,7 +16,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [18.18.0, lts/*]
node: [22.13.0, lts/*]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
Expand All @@ -24,7 +25,7 @@ jobs:
cache: npm
- run: npm ci
- run: npm run check
- name: Clean packed production install and Node 18.18 native binding smoke
- name: Clean packed production install and Node 22.13 native binding smoke
run: node --test tests/integration/package-install.test.mjs
- name: Production lockfile native binding smoke
shell: bash
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/publish-marketplace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: 18.18.0
node-version: 22.13.0
cache: npm
- name: Resolve and validate source provenance
id: source
Expand Down Expand Up @@ -65,7 +65,7 @@ jobs:
run: npm ci
- name: Run complete quality gate
run: npm run check
- name: Re-run Node 18 packed native qualification
- name: Re-run Node 22.13 packed native qualification
run: node --test tests/integration/package-install.test.mjs
- name: Build exact production marketplace snapshot
shell: bash
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ ZCode for Codex is a native Codex marketplace plugin that delegates independent

- Codex with native plugins and hooks enabled.
- ZCode CLI `>=0.16.1`, installed and authenticated for at least one model.
- Node.js `>=18.18.0` (the plugin packages its production native lock dependency).
- Node.js `>=22.13.0` (the plugin packages its production native lock dependency).

Install from the production snapshot published on this repository's `marketplace` branch; the source-code root on `main` is not itself a marketplace catalog:

Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ ZCode for Codex 是原生 Codex marketplace 插件:由 Codex 保持用户交

- 支持原生插件与 hooks 的 Codex。
- 已安装 ZCode CLI `>=0.16.1`,并至少为一个模型完成认证。
- Node.js `>=18.18.0`;插件包内自带生产环境原生锁依赖。
- Node.js `>=22.13.0`;插件包内自带生产环境原生锁依赖。

从本仓库 `marketplace` 分支发布的生产快照安装;`main` 上的源码根目录本身不是 marketplace catalog:

Expand Down
4 changes: 2 additions & 2 deletions docs/adr/0009-cross-process-locking.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ status: accepted

# Use native advisory locks for cross-process state

Node.js 18 has no built-in cross-platform advisory file-lock API or filesystem compare-and-delete primitive. The plugin must nevertheless serialize job transitions and authorization consumption across processes on local macOS, Linux, and Windows workspaces. It therefore keeps one persistent lockfile for each lock scope, opens that file for the lifetime of a critical section, and uses the exactly pinned `fs-native-extensions@1.5.0` fd lock. The operating system grants ownership atomically and releases it when the fd closes or the process exits; the plugin never renames or unlinks the active lockfile.
Node.js 22.13 still has no built-in cross-platform advisory file-lock API or filesystem compare-and-delete primitive. The plugin must nevertheless serialize job transitions and authorization consumption across processes on local macOS, Linux, and Windows workspaces. It therefore keeps one persistent lockfile for each lock scope, opens that file for the lifetime of a critical section, and uses the exactly pinned `fs-native-extensions@1.5.0` fd lock. The operating system grants ownership atomically and releases it when the fd closes or the process exits; the plugin never renames or unlinks the active lockfile.

## Rejected alternatives

Directory leases with heartbeat timestamps were rejected because stale takeover and release require a conditional rename or delete that portable Node filesystem APIs cannot provide. A precheck followed by rename has an ABA window in which an old holder can move a new owner's lock and permit parallel critical sections. Adding hostnames and PIDs does not close that window, and PID reuse can make a dead owner appear permanently alive unless platform-specific process-incarnation data is also maintained. More metadata would increase recovery complexity without creating the missing atomic primitive.

## Consequences

Every state and authorization participant must open and lock the same persistent lockfile; advisory locking cannot protect against a participant that ignores the protocol. The runtime carries native prebuilds for supported macOS, Linux, and Windows architectures. Because a consuming install does not apply a dependency package's root-level npm override, the published tarball includes `npm-shrinkwrap.json` and bundles the `fs-native-extensions` dependency tree; that tree pins `bare-addon-resolve` to exactly `1.9.4` so binding resolution remains compatible with Node 18.18. Clean production installation, packed-plugin installation, bundled resolver inspection, binding loading, and lock acquisition/release must be tested in CI on all three operating systems. Release review must also audit the dependency's Apache-2.0 license, shipped prebuilds, package integrity, and security advisories.
Every state and authorization participant must open and lock the same persistent lockfile; advisory locking cannot protect against a participant that ignores the protocol. The runtime carries native prebuilds for supported macOS, Linux, and Windows architectures. The published tarball includes `npm-shrinkwrap.json` and bundles the `fs-native-extensions` dependency tree; Node 22.13 supports the current `bare-addon-resolve` release, so no legacy resolver compatibility override is needed, while the shrinkwrap records the resolved `1.10.1` resolver. Clean production installation, packed-plugin installation, bundled resolver inspection, binding loading, and lock acquisition/release must be tested in CI on all three operating systems. Release review must also audit the dependency's Apache-2.0 license, shipped prebuilds, package integrity, and security advisories.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

**Architecture:** Thin Codex skills and lifecycle hooks call one Node.js companion runtime. The companion owns argument contracts, caller/job authorization, workspace-scoped state, prompts, rendering, and two isolated adapters: a bounded Codex app-server client and a long-lived ZCode protocol broker.

**Tech Stack:** Node.js 18.18+ ESM, Node built-in test runner, JSDoc checked by TypeScript, ESLint, `fs-native-extensions` advisory locks, Codex plugin manifest/hooks/skills, JSONL app-server protocols.
**Tech Stack:** Node.js 22.13+ ESM, Node built-in test runner, JSDoc checked by TypeScript, ESLint, `fs-native-extensions` advisory locks, Codex plugin manifest/hooks/skills, JSONL app-server protocols.

---

Expand Down Expand Up @@ -70,19 +70,17 @@ test("plugin manifest identifies the vitry ZCode plugin", () => {
assert.equal("hooks" in manifest, false);
});

test("package requires Node 18.18 and only the pinned native lock dependency", () => {
test("package requires Node 22.13 and the native lock dependency", () => {
const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
assert.equal(pkg.type, "module");
assert.equal(pkg.engines.node, ">=18.18.0");
assert.equal(pkg.engines.node, ">=22.13.0");
assert.deepEqual(pkg.dependencies ?? {}, {
"fs-native-extensions": "1.5.0",
});
assert.deepEqual(pkg.overrides ?? {}, {
"bare-addon-resolve": "1.9.4",
});
assert.deepEqual(pkg.overrides ?? {}, {});
assert.deepEqual(pkg.bundleDependencies ?? [], ["fs-native-extensions"]);
const shrinkwrap = JSON.parse(fs.readFileSync(path.join(root, "npm-shrinkwrap.json"), "utf8"));
assert.equal(shrinkwrap.packages["node_modules/require-addon/node_modules/bare-addon-resolve"].version, "1.9.4");
assert.equal(shrinkwrap.packages["node_modules/bare-addon-resolve"].version, "1.10.1");
assert.match(pkg.version, semverPattern);
assert.equal(manifest.version, pkg.version);
});
Expand All @@ -96,7 +94,7 @@ Expected: FAIL because `package.json` and `.codex-plugin/plugin.json` do not exi

- [ ] **Step 3: Add the minimal package and plugin files**

Use version `0.1.0`, repository `https://github.com/vitry/zcode-plugin-codex`, Node `>=18.18.0`, and Apache-2.0. The only runtime dependency is the exact pin `fs-native-extensions@1.5.0`, which provides process-owned advisory file locks on macOS, Linux, and Windows. Pin the `bare-addon-resolve` override to `1.9.4` because later releases use JavaScript unavailable in Node 18.18. Publish `npm-shrinkwrap.json` and bundle the `fs-native-extensions` tree, because a consuming install does not apply this package's root override. Contract tests must compare the complete dependency, override, and bundle objects and verify the shrinkwrapped resolver version so additional runtime packages cannot be added implicitly.
Use version `0.1.0`, repository `https://github.com/vitry/zcode-plugin-codex`, Node `>=22.13.0`, and Apache-2.0. The only runtime dependency is the exact pin `fs-native-extensions@1.5.0`, which provides process-owned advisory file locks on macOS, Linux, and Windows. Node 22.13 supports the current `bare-addon-resolve` release, so no legacy resolver compatibility override is required; the published `npm-shrinkwrap.json` still records the resolved dependency tree. Publish `npm-shrinkwrap.json` and bundle the `fs-native-extensions` tree so consuming installs receive the native lock implementation. Contract tests must compare the complete dependency and bundle objects and verify the shrinkwrapped resolver version so additional runtime packages cannot be added implicitly.

Use these scripts:

Expand All @@ -111,12 +109,12 @@ Use these scripts:
}
```

Use Node-18-compatible development dependencies:
Use Node-22-compatible development dependencies:

```json
{
"@eslint/js": "^9.39.1",
"@types/node": "^18.19.0",
"@types/node": "^22.13.0",
"eslint": "^9.39.1",
"globals": "^16.5.0",
"typescript": "^5.9.3"
Expand Down Expand Up @@ -462,9 +460,9 @@ Exercise every skill against fake Codex/ZCode peers, including the two-session i

Document installation through a Codex marketplace, ZCode `>=0.16.1`, macOS bundled discovery, model aliases, all eight commands, permission limits, job storage, review gate, troubleshooting, Linux/Windows qualification status, and Apache-2.0 provenance.

Add a package-install integration test that packs the plugin, installs that tarball into an empty temporary consumer with production dependencies only, asserts the bundled plugin-local `bare-addon-resolve` is exactly `1.9.4`, loads the installed `fs-native-extensions` binding on Node 18.18, and acquires/releases a lock through the installed companion runtime. The test must prove that the native dependency is installed beside the plugin rather than relying on the repository's development `node_modules`.
Add a package-install integration test that packs the plugin, installs that tarball into an empty temporary consumer with production dependencies only, asserts the bundled plugin-local `bare-addon-resolve` is exactly `1.10.1`, loads the installed `fs-native-extensions` binding on Node 22.13, and acquires/releases a lock through the installed companion runtime. The test must prove that the native dependency is installed beside the plugin rather than relying on the repository's development `node_modules`.

CI runs the fake-protocol suite on current macOS, Ubuntu, and Windows. Each platform job must start with `npm ci`, run `npm run check`, perform the clean packed-plugin production install, and execute a binding-load plus lock smoke test. Include Node 18.18 coverage for the pinned override in addition to the current Node LTS matrix. A platform job is not successful if it skips the native binding smoke.
CI runs the fake-protocol suite on current macOS, Ubuntu, and Windows. Each platform job must start with `npm ci`, run `npm run check`, perform the clean packed-plugin production install, and execute a binding-load plus lock smoke test. Include Node 22.13 coverage for the production dependency tree in addition to the current Node LTS matrix. A platform job is not successful if it skips the native binding smoke.

- [ ] **Step 5: Add opt-in real ZCode E2E**

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

**Architecture:** Installed Skills call a constant direct-companion command over ordinary stdio. Native hooks persist exact thread/turn/workspace input, while the companion resolves it from runtime-observed `CODEX_THREAD_ID`; private capabilities remain inside production Node. Durable job turn boundaries drive locked broker reconciliation, and workspace-private configuration supplies model defaults and aliases.

**Tech Stack:** Node.js 18 ESM, native Codex hooks and Skills, Codex app-server JSONL, ZCode Protocol JSONL, `node:test`, `fs-native-extensions` advisory locks.
**Tech Stack:** Node.js 22.13+ ESM, native Codex hooks and Skills, Codex app-server JSONL, ZCode Protocol JSONL, `node:test`, `fs-native-extensions` advisory locks.

---

Expand Down
110 changes: 110 additions & 0 deletions docs/superpowers/plans/2026-08-07-deduplicate-ci-triggers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Deduplicate CI Triggers Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Ensure an open pull-request branch creates only one six-job CI matrix while merged commits continue to run CI on `main`.

**Architecture:** Preserve the existing matrix and steps. Narrow only the workflow event boundary, and enforce it through the existing release contract suite.

**Tech Stack:** GitHub Actions YAML, Node.js built-in test runner

---

### Task 1: Restrict branch push CI to main

**Files:**
- Modify: `tests/release-contracts.test.mjs`
- Modify: `.github/workflows/ci.yml`

- [ ] **Step 1: Write the failing trigger contract**

Add these assertions to the existing CI release-contract test:

```js
assert.match(workflow, /^ push:\n branches: \[main\]$/m);
assert.match(workflow, /^ pull_request:\s*$/m);
```

- [ ] **Step 2: Verify the contract fails for the broad push trigger**

Run: `node --test --test-name-pattern='CI runs full' tests/release-contracts.test.mjs`

Expected: FAIL because `.github/workflows/ci.yml` has an unrestricted `push` trigger.

- [ ] **Step 3: Apply the minimal workflow change**

Change the event block to:

```yaml
on:
push:
branches: [main]
pull_request:
```

- [ ] **Step 4: Verify the focused and complete suites**

Run: `node --test --test-name-pattern='CI runs full' tests/release-contracts.test.mjs`

Expected: PASS.

Run: `npm run check`

Expected: zero failures; only the two credential-gated real E2E tests may be skipped.

- [ ] **Step 5: Commit the implementation**

```bash
git add .github/workflows/ci.yml tests/release-contracts.test.mjs
git commit -m "ci: avoid duplicate pull request matrices"
```

### Task 2: Stabilize intended timeout seams on Node 22.13

**Files:**
- Modify: `tests/zcode-client.test.mjs`
- Modify: `tests/fixtures/stop-gate-with-timeout.mjs`
- Modify: `tests/hooks.test.mjs`

- [ ] **Step 1: Reproduce the request-budget failures under contention**

Run the stderr-tail test repeatedly under Node 22.13 with parallel workers and confirm at least one `ZCODE_REQUEST_TIMEOUT` occurs before `session/list`.

Run the Stop-gate timeout group repeatedly under Node 22.13 with parallel workers and confirm at least one result has no `decision` because only `session/create` was recorded.

- [ ] **Step 2: Give only the stderr-tail scenario a two-second request budget**

Pass this options object as the third `withClient` argument in the stderr-tail test:

```js
{ requestTimeoutMs: 2_000 }
```

Keep the helper's 500 ms default unchanged.

- [ ] **Step 3: Make the deliberate Stop completion timeout platform-neutral**

In `tests/fixtures/stop-gate-with-timeout.mjs`, use:

```js
const timeoutMs = 2_000;
```

This keeps the intended suppressed completion as the timeout source on every platform without changing production defaults.

- [ ] **Step 4: Prove the Stop timeout reaches the intended seam**

For the timeout case in `tests/hooks.test.mjs`, assert the recorded calls include `session/send` before accepting the conservative block result. Retain the existing `session/stop` assertion.

- [ ] **Step 5: Verify focused stress and full suites**

Run the Node 22.13 targeted stress commands from the diagnosis, then run `npm run check` and `git diff --check`.

Expected: no targeted timeouts before the intended seam, zero suite failures, and only the two credential-gated real E2E skips.

- [ ] **Step 6: Commit**

```bash
git add tests/zcode-client.test.mjs tests/fixtures/stop-gate-with-timeout.mjs tests/hooks.test.mjs
git commit -m "test: stabilize Node 22 timeout scenarios"
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Deduplicate CI Triggers

## Problem

The CI workflow currently runs on every `push` and every `pull_request`. A push to an open pull-request branch therefore starts two identical six-job matrices for the same commit. The duplicated check runs compete for hosted runners, clutter the pull-request check rollup, and leave cancelled or failed duplicates when GitHub Actions cannot provision every job.

## Design

Run the full matrix for pull requests, and run it for pushes to `main` only. A feature-branch commit then produces one pull-request matrix; the merged commit still receives post-merge validation on `main`. Keep the existing operating-system and Node.js matrix, `fail-fast: false`, and every test and native-binding smoke step unchanged.

## Verification

Extend the release contract test to require an unfiltered `pull_request` trigger and a `push` trigger restricted to `main`. Verify the test fails against the current broad push trigger, passes after the workflow change, and then run the complete project check.

## Node 22.13 test stability

The first non-duplicated matrix exposed two test-only timing budgets that expire before their intended assertions under Node 22.13 contention. The stderr-tail test must give session startup a two-second request budget. The Stop-gate timeout fixture must use the same two-second allowance on every platform and prove that `session/send` occurred before accepting the expected conservative block. Production request/completion defaults and fail-open behavior remain unchanged.
Loading
Loading