pi-crew 0.11.20 → 0.11.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-crew",
3
- "version": "0.11.20",
3
+ "version": "0.11.21",
4
4
  "description": "Pi extension for coordinated AI teams, workflows, worktrees, and async task orchestration",
5
5
  "author": "baphuongna",
6
6
  "license": "MIT",
@@ -1256,6 +1256,27 @@ When a tier fails, the recovery is usually quick. Match the symptom to the cause
1256
1256
  | `team action='schedule' interval=<ms>` always says `Invalid schedule "<N>ms". Use "5m", …` | handle-schedule builds `${interval}ms`; both interval parsers lacked an `ms` unit — every numeric interval died. Fixed 2026-09-21 | The suggested "5m" formats only work via `cron`/`once` params, not `interval` (interval is schema-typed as a plain ms number) |
1257
1257
  | Runs keep appearing under one scheduled job; children of YOUR pi process; machine thrashing | **Interval ≥ 2^31-1 ms (≥24.8 days) overflowed the Node timer to a 1ms hot loop** (fixed 2026-09-22 — clamped hops). Check `.crew/audit/prune.jsonl` for interleaved auto-prunes. Contain: remove job, then `pkill -f '[b]ackground-runner.ts --cwd <root>'` | Any new timer scheduling must clamp below MAX_TIMER_DELAY_MS — grep for `setInterval(`/`setTimeout(` with computed delays |
1258
1258
 
1259
+ ## Release gate — battery → push → GitHub Actions GREEN → publish (canonical, 2026-10-14)
1260
+
1261
+ Local `npm run ci` là LINUX-ONLY — không thay được matrix 3-OS của GitHub Actions.
1262
+ Live-proven 2026-10-14: **0.11.19 và 0.11.20 đều publish khi CI GitHub ĐỎ** (local ci + pre-push
1263
+ hook xanh hết) — windows fail do child-env thiếu `SYSTEMROOT` (node con không khởi tạo Winsock được,
1264
+ stdout rỗng); macOS fail do transient ECONNREFUSED khiến accepting-probe 1 lần coi holder SỐNG là
1265
+ corpse → xóa file socket + rebind (STEAL lock) — fix bằng 3-strike takeover (`process-lock.ts`).
1266
+
1267
+ **Quy trình BẮT BUỘC cho mọi release (theo thứ tự, không bỏ bước):**
1268
+
1269
+ 1. Cập nhật skill (wave entry) + CHANGELOG heading cho version sắp release — commit.
1270
+ 2. Chạy battery theo decision table (đủ tier cho paths đổi) — xanh hết.
1271
+ 3. `npm version patch` (tạo release commit + tag).
1272
+ 4. `git push origin main --follow-tags` — pre-push hook tự chạy full unit local.
1273
+ 5. **`gh run watch <runId> --exit-status` (hoặc poll `gh run list`) — CHỜ matrix 3-OS XANH TOÀN BỘ.**
1274
+ CI đỏ ⇒ KHÔNG publish — fix, push lại, chờ lại. (Check nhanh fail: `gh run view <id>` +
1275
+ `gh api repos/<o>/<r>/actions/jobs/<jobId>//logs` — KHÔNG grep "not ok" mù: tên test có thể
1276
+ chứa literal "not ok" ("malformed JSON is not ok with a message") — grep kèm assertion block.)
1277
+ 6. CHỈ KHI xanh: `npm publish` → poll `npm view <pkg> dist-tags.latest` → `gh release create`.
1278
+ 7. Post-release: knowledge.md entry SHIPPED + dọn branch/scratch.
1279
+
1259
1280
  ## Performance budget (per-tier soft limits)
1260
1281
 
1261
1282
  | Tier | Soft limit | Hard limit | What happens over hard limit |
@@ -105,8 +105,20 @@ function socketAddress(name: string): SocketAddress {
105
105
  return { address: fileSocketAddress(key), file: true };
106
106
  }
107
107
 
108
- /** True when a live server accepts connections on `address` (leftover-corpse probe). */
109
- function accepting(address: string): Promise<boolean> {
108
+ /**
109
+ * Probe whether a live server accepts connections on `address` (leftover-corpse
110
+ * check). A single refusal is NOT trusted on file-socket platforms: macOS can
111
+ * return ECONNREFUSED transiently against a LIVE listener (e.g. a momentarily
112
+ * full listen backlog under load — live-caught in CI 2026-10-14: a foreign
113
+ * child flipped a held lock to "corpse", DELETED the live socket file and
114
+ * rebound, stealing the lock). We require TAKEOVER_REFUSALS consecutive
115
+ * refusals, spaced TAKEOVER_RETRY_MS apart, before declaring a corpse.
116
+ */
117
+ const TAKEOVER_REFUSALS = 3;
118
+ const TAKEOVER_RETRY_MS = 150;
119
+
120
+ /** One connect-probe: true = somebody accepts; false = refused (corpse-ish). */
121
+ function acceptingOnce(address: string): Promise<boolean> {
110
122
  return new Promise((resolve) => {
111
123
  const socket = net.connect(address);
112
124
  socket.unref();
@@ -121,6 +133,14 @@ function accepting(address: string): Promise<boolean> {
121
133
  });
122
134
  }
123
135
 
136
+ async function accepting(address: string): Promise<boolean> {
137
+ for (let attempt = 0; attempt < TAKEOVER_REFUSALS; attempt++) {
138
+ if (attempt > 0) await new Promise((r) => setTimeout(r, TAKEOVER_RETRY_MS));
139
+ if (await acceptingOnce(address)) return true;
140
+ }
141
+ return false;
142
+ }
143
+
124
144
  /**
125
145
  * Acquire the process lock for `name`. Resolves with a handle once THIS process
126
146
  * owns the kernel socket; rejects with {@link ProcessLockHeldError}