Skip to content

Commit 5d6a72b

Browse files
authored
Merge pull request #2 from vitry/feat/initial-implementation
chore: require Node 22.13 for the ZCode CLI
2 parents e825190 + 36d6652 commit 5d6a72b

19 files changed

Lines changed: 357 additions & 84 deletions

.github/workflows/ci.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ name: CI
22

33
on:
44
push:
5+
branches: [main]
56
pull_request:
67

78
permissions:
@@ -15,7 +16,7 @@ jobs:
1516
fail-fast: false
1617
matrix:
1718
os: [ubuntu-latest, macos-latest, windows-latest]
18-
node: [18.18.0, lts/*]
19+
node: [22.13.0, lts/*]
1920
steps:
2021
- uses: actions/checkout@v4
2122
- uses: actions/setup-node@v4
@@ -24,7 +25,7 @@ jobs:
2425
cache: npm
2526
- run: npm ci
2627
- run: npm run check
27-
- name: Clean packed production install and Node 18.18 native binding smoke
28+
- name: Clean packed production install and Node 22.13 native binding smoke
2829
run: node --test tests/integration/package-install.test.mjs
2930
- name: Production lockfile native binding smoke
3031
shell: bash

.github/workflows/publish-marketplace.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
persist-credentials: false
3434
- uses: actions/setup-node@v4
3535
with:
36-
node-version: 18.18.0
36+
node-version: 22.13.0
3737
cache: npm
3838
- name: Resolve and validate source provenance
3939
id: source
@@ -65,7 +65,7 @@ jobs:
6565
run: npm ci
6666
- name: Run complete quality gate
6767
run: npm run check
68-
- name: Re-run Node 18 packed native qualification
68+
- name: Re-run Node 22.13 packed native qualification
6969
run: node --test tests/integration/package-install.test.mjs
7070
- name: Build exact production marketplace snapshot
7171
shell: bash

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ ZCode for Codex is a native Codex marketplace plugin that delegates independent
88

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

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

README.zh-CN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ ZCode for Codex 是原生 Codex marketplace 插件:由 Codex 保持用户交
88

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

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

docs/adr/0009-cross-process-locking.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ status: accepted
44

55
# Use native advisory locks for cross-process state
66

7-
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.
7+
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.
88

99
## Rejected alternatives
1010

1111
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.
1212

1313
## Consequences
1414

15-
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.
15+
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.

docs/superpowers/plans/2026-08-03-zcode-plugin-codex-implementation.md

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
**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.
88

9-
**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.
9+
**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.
1010

1111
---
1212

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

73-
test("package requires Node 18.18 and only the pinned native lock dependency", () => {
73+
test("package requires Node 22.13 and the native lock dependency", () => {
7474
const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
7575
assert.equal(pkg.type, "module");
76-
assert.equal(pkg.engines.node, ">=18.18.0");
76+
assert.equal(pkg.engines.node, ">=22.13.0");
7777
assert.deepEqual(pkg.dependencies ?? {}, {
7878
"fs-native-extensions": "1.5.0",
7979
});
80-
assert.deepEqual(pkg.overrides ?? {}, {
81-
"bare-addon-resolve": "1.9.4",
82-
});
80+
assert.deepEqual(pkg.overrides ?? {}, {});
8381
assert.deepEqual(pkg.bundleDependencies ?? [], ["fs-native-extensions"]);
8482
const shrinkwrap = JSON.parse(fs.readFileSync(path.join(root, "npm-shrinkwrap.json"), "utf8"));
85-
assert.equal(shrinkwrap.packages["node_modules/require-addon/node_modules/bare-addon-resolve"].version, "1.9.4");
83+
assert.equal(shrinkwrap.packages["node_modules/bare-addon-resolve"].version, "1.10.1");
8684
assert.match(pkg.version, semverPattern);
8785
assert.equal(manifest.version, pkg.version);
8886
});
@@ -96,7 +94,7 @@ Expected: FAIL because `package.json` and `.codex-plugin/plugin.json` do not exi
9694
9795
- [ ] **Step 3: Add the minimal package and plugin files**
9896
99-
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.
97+
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.
10098

10199
Use these scripts:
102100

@@ -111,12 +109,12 @@ Use these scripts:
111109
}
112110
```
113111

114-
Use Node-18-compatible development dependencies:
112+
Use Node-22-compatible development dependencies:
115113

116114
```json
117115
{
118116
"@eslint/js": "^9.39.1",
119-
"@types/node": "^18.19.0",
117+
"@types/node": "^22.13.0",
120118
"eslint": "^9.39.1",
121119
"globals": "^16.5.0",
122120
"typescript": "^5.9.3"
@@ -462,9 +460,9 @@ Exercise every skill against fake Codex/ZCode peers, including the two-session i
462460
463461
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.
464462
465-
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`.
463+
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`.
466464
467-
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.
465+
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.
468466
469467
- [ ] **Step 5: Add opt-in real ZCode E2E**
470468

docs/superpowers/plans/2026-08-06-runtime-correctness-remediation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
**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.
88

9-
**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.
9+
**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.
1010

1111
---
1212

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Deduplicate CI Triggers Implementation Plan
2+
3+
> **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.
4+
5+
**Goal:** Ensure an open pull-request branch creates only one six-job CI matrix while merged commits continue to run CI on `main`.
6+
7+
**Architecture:** Preserve the existing matrix and steps. Narrow only the workflow event boundary, and enforce it through the existing release contract suite.
8+
9+
**Tech Stack:** GitHub Actions YAML, Node.js built-in test runner
10+
11+
---
12+
13+
### Task 1: Restrict branch push CI to main
14+
15+
**Files:**
16+
- Modify: `tests/release-contracts.test.mjs`
17+
- Modify: `.github/workflows/ci.yml`
18+
19+
- [ ] **Step 1: Write the failing trigger contract**
20+
21+
Add these assertions to the existing CI release-contract test:
22+
23+
```js
24+
assert.match(workflow, /^ push:\n branches: \[main\]$/m);
25+
assert.match(workflow, /^ pull_request:\s*$/m);
26+
```
27+
28+
- [ ] **Step 2: Verify the contract fails for the broad push trigger**
29+
30+
Run: `node --test --test-name-pattern='CI runs full' tests/release-contracts.test.mjs`
31+
32+
Expected: FAIL because `.github/workflows/ci.yml` has an unrestricted `push` trigger.
33+
34+
- [ ] **Step 3: Apply the minimal workflow change**
35+
36+
Change the event block to:
37+
38+
```yaml
39+
on:
40+
push:
41+
branches: [main]
42+
pull_request:
43+
```
44+
45+
- [ ] **Step 4: Verify the focused and complete suites**
46+
47+
Run: `node --test --test-name-pattern='CI runs full' tests/release-contracts.test.mjs`
48+
49+
Expected: PASS.
50+
51+
Run: `npm run check`
52+
53+
Expected: zero failures; only the two credential-gated real E2E tests may be skipped.
54+
55+
- [ ] **Step 5: Commit the implementation**
56+
57+
```bash
58+
git add .github/workflows/ci.yml tests/release-contracts.test.mjs
59+
git commit -m "ci: avoid duplicate pull request matrices"
60+
```
61+
62+
### Task 2: Stabilize intended timeout seams on Node 22.13
63+
64+
**Files:**
65+
- Modify: `tests/zcode-client.test.mjs`
66+
- Modify: `tests/fixtures/stop-gate-with-timeout.mjs`
67+
- Modify: `tests/hooks.test.mjs`
68+
69+
- [ ] **Step 1: Reproduce the request-budget failures under contention**
70+
71+
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`.
72+
73+
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.
74+
75+
- [ ] **Step 2: Give only the stderr-tail scenario a two-second request budget**
76+
77+
Pass this options object as the third `withClient` argument in the stderr-tail test:
78+
79+
```js
80+
{ requestTimeoutMs: 2_000 }
81+
```
82+
83+
Keep the helper's 500 ms default unchanged.
84+
85+
- [ ] **Step 3: Make the deliberate Stop completion timeout platform-neutral**
86+
87+
In `tests/fixtures/stop-gate-with-timeout.mjs`, use:
88+
89+
```js
90+
const timeoutMs = 2_000;
91+
```
92+
93+
This keeps the intended suppressed completion as the timeout source on every platform without changing production defaults.
94+
95+
- [ ] **Step 4: Prove the Stop timeout reaches the intended seam**
96+
97+
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.
98+
99+
- [ ] **Step 5: Verify focused stress and full suites**
100+
101+
Run the Node 22.13 targeted stress commands from the diagnosis, then run `npm run check` and `git diff --check`.
102+
103+
Expected: no targeted timeouts before the intended seam, zero suite failures, and only the two credential-gated real E2E skips.
104+
105+
- [ ] **Step 6: Commit**
106+
107+
```bash
108+
git add tests/zcode-client.test.mjs tests/fixtures/stop-gate-with-timeout.mjs tests/hooks.test.mjs
109+
git commit -m "test: stabilize Node 22 timeout scenarios"
110+
```
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Deduplicate CI Triggers
2+
3+
## Problem
4+
5+
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.
6+
7+
## Design
8+
9+
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.
10+
11+
## Verification
12+
13+
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.
14+
15+
## Node 22.13 test stability
16+
17+
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.

0 commit comments

Comments
 (0)