@qodeca/xezar 0.13.0 → 0.14.0
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/README.md +72 -39
- package/dist/agent-config/account-identity.d.ts +2 -8
- package/dist/agent-config/account-identity.js +54 -10
- package/dist/agent-config/account-identity.js.map +1 -1
- package/dist/agent-config/catalog.d.ts +49 -7
- package/dist/agent-config/catalog.js +107 -0
- package/dist/agent-config/catalog.js.map +1 -1
- package/dist/agent-config/model-settings/pi.d.ts +12 -6
- package/dist/agent-config/model-settings/pi.js +12 -6
- package/dist/agent-config/model-settings/pi.js.map +1 -1
- package/dist/agent-config/seed.js +10 -0
- package/dist/agent-config/seed.js.map +1 -1
- package/dist/contract/github.d.ts +15 -0
- package/dist/contract/index.d.ts +9 -0
- package/dist/contract/index.js +1527 -895
- package/dist/contract/mcp-api-reference.d.ts +174 -0
- package/dist/contract/mcp-audit.d.ts +103 -0
- package/dist/contract/mcp-discovery.d.ts +257 -0
- package/dist/contract/mcp-event-catalog.d.ts +121 -0
- package/dist/contract/mcp-event-catalog.test.d.ts +1 -0
- package/dist/contract/mcp-idempotency.d.ts +200 -0
- package/dist/contract/mcp-journal.d.ts +268 -0
- package/dist/contract/mcp-leader.d.ts +88 -0
- package/dist/contract/mcp-ownership.d.ts +78 -0
- package/dist/contract/mcp-versioning.d.ts +139 -0
- package/dist/contract/runs.d.ts +81 -0
- package/dist/core/agent-profiles.d.ts +15 -4
- package/dist/core/agent-profiles.js +22 -8
- package/dist/core/agent-profiles.js.map +1 -1
- package/dist/core/opencode-server-runner.js +71 -11
- package/dist/core/opencode-server-runner.js.map +1 -1
- package/dist/core/secret-redaction.d.ts +7 -1
- package/dist/core/secret-redaction.js +44 -10
- package/dist/core/secret-redaction.js.map +1 -1
- package/dist/index.js +138 -27
- package/dist/index.js.map +1 -1
- package/dist/machine-identity.d.ts +66 -0
- package/dist/machine-identity.js +96 -0
- package/dist/machine-identity.js.map +1 -0
- package/dist/mcp/adapters/claude-code.d.ts +41 -0
- package/dist/mcp/adapters/claude-code.js +40 -0
- package/dist/mcp/adapters/claude-code.js.map +1 -0
- package/dist/mcp/adapters/codex.d.ts +140 -0
- package/dist/mcp/adapters/codex.js +323 -0
- package/dist/mcp/adapters/codex.js.map +1 -0
- package/dist/mcp/adapters/opencode.d.ts +148 -0
- package/dist/mcp/adapters/opencode.js +528 -0
- package/dist/mcp/adapters/opencode.js.map +1 -0
- package/dist/mcp/adapters/pi-link.d.ts +102 -0
- package/dist/mcp/adapters/pi-link.js +235 -0
- package/dist/mcp/adapters/pi-link.js.map +1 -0
- package/dist/mcp/adapters/pi.d.ts +188 -0
- package/dist/mcp/adapters/pi.js +418 -0
- package/dist/mcp/adapters/pi.js.map +1 -0
- package/dist/mcp/api-reference.d.ts +26 -0
- package/dist/mcp/api-reference.js +133 -0
- package/dist/mcp/api-reference.js.map +1 -0
- package/dist/mcp/audit-trail.d.ts +162 -0
- package/dist/mcp/audit-trail.js +306 -0
- package/dist/mcp/audit-trail.js.map +1 -0
- package/dist/mcp/bridge.d.ts +81 -0
- package/dist/mcp/bridge.js +407 -0
- package/dist/mcp/bridge.js.map +1 -0
- package/dist/mcp/connection-file.d.ts +49 -0
- package/dist/mcp/connection-file.js +63 -0
- package/dist/mcp/connection-file.js.map +1 -0
- package/dist/mcp/echo-guard.d.ts +94 -0
- package/dist/mcp/echo-guard.js +160 -0
- package/dist/mcp/echo-guard.js.map +1 -0
- package/dist/mcp/event-catalog.d.ts +120 -0
- package/dist/mcp/event-catalog.js +306 -0
- package/dist/mcp/event-catalog.js.map +1 -0
- package/dist/mcp/event-controller.d.ts +230 -0
- package/dist/mcp/event-controller.js +596 -0
- package/dist/mcp/event-controller.js.map +1 -0
- package/dist/mcp/event-journal.d.ts +57 -0
- package/dist/mcp/event-journal.js +423 -0
- package/dist/mcp/event-journal.js.map +1 -0
- package/dist/mcp/index.d.ts +63 -0
- package/dist/mcp/index.js +386 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/ipc.d.ts +202 -0
- package/dist/mcp/ipc.js +206 -0
- package/dist/mcp/ipc.js.map +1 -0
- package/dist/mcp/leader-delivery.d.ts +119 -0
- package/dist/mcp/leader-delivery.js +450 -0
- package/dist/mcp/leader-delivery.js.map +1 -0
- package/dist/mcp/operation-receipts.d.ts +210 -0
- package/dist/mcp/operation-receipts.js +723 -0
- package/dist/mcp/operation-receipts.js.map +1 -0
- package/dist/mcp/project-catalogs.d.ts +23 -0
- package/dist/mcp/project-catalogs.js +25 -0
- package/dist/mcp/project-catalogs.js.map +1 -0
- package/dist/mcp/project-leaders.d.ts +26 -0
- package/dist/mcp/project-leaders.js +17 -0
- package/dist/mcp/project-leaders.js.map +1 -0
- package/dist/mcp/protocol.d.ts +50 -0
- package/dist/mcp/protocol.js +47 -0
- package/dist/mcp/protocol.js.map +1 -0
- package/dist/mcp/reconnect.d.ts +297 -0
- package/dist/mcp/reconnect.js +465 -0
- package/dist/mcp/reconnect.js.map +1 -0
- package/dist/mcp/resource-ownership.d.ts +228 -0
- package/dist/mcp/resource-ownership.js +390 -0
- package/dist/mcp/resource-ownership.js.map +1 -0
- package/dist/mcp/service-adapter.d.ts +16488 -0
- package/dist/mcp/service-adapter.js +148 -0
- package/dist/mcp/service-adapter.js.map +1 -0
- package/dist/mcp/service.d.ts +83 -0
- package/dist/mcp/service.js +233 -0
- package/dist/mcp/service.js.map +1 -0
- package/dist/mcp/session-binding.d.ts +105 -0
- package/dist/mcp/session-binding.js +163 -0
- package/dist/mcp/session-binding.js.map +1 -0
- package/dist/mcp/stale-write.d.ts +145 -0
- package/dist/mcp/stale-write.js +219 -0
- package/dist/mcp/stale-write.js.map +1 -0
- package/dist/mcp/tool.d.ts +48 -0
- package/dist/mcp/tool.js +24 -0
- package/dist/mcp/tool.js.map +1 -0
- package/dist/mcp/tools/discovery.d.ts +40 -0
- package/dist/mcp/tools/discovery.js +232 -0
- package/dist/mcp/tools/discovery.js.map +1 -0
- package/dist/mcp/tools/execution-control.d.ts +171 -0
- package/dist/mcp/tools/execution-control.js +513 -0
- package/dist/mcp/tools/execution-control.js.map +1 -0
- package/dist/mcp/tools/handoff-git.d.ts +26 -0
- package/dist/mcp/tools/handoff-git.js +544 -0
- package/dist/mcp/tools/handoff-git.js.map +1 -0
- package/dist/mcp/tools/index.d.ts +2 -0
- package/dist/mcp/tools/index.js +23 -0
- package/dist/mcp/tools/index.js.map +1 -0
- package/dist/mcp/tools/leader-events.d.ts +46 -0
- package/dist/mcp/tools/leader-events.js +126 -0
- package/dist/mcp/tools/leader-events.js.map +1 -0
- package/dist/mcp/tools/local-handoff.d.ts +102 -0
- package/dist/mcp/tools/local-handoff.js +306 -0
- package/dist/mcp/tools/local-handoff.js.map +1 -0
- package/dist/mcp/tools/project-config.d.ts +340 -0
- package/dist/mcp/tools/project-config.js +1075 -0
- package/dist/mcp/tools/project-config.js.map +1 -0
- package/dist/mcp/tools/results-evidence.d.ts +112 -0
- package/dist/mcp/tools/results-evidence.js +1045 -0
- package/dist/mcp/tools/results-evidence.js.map +1 -0
- package/dist/mcp/tools/task-create.d.ts +70 -0
- package/dist/mcp/tools/task-create.js +473 -0
- package/dist/mcp/tools/task-create.js.map +1 -0
- package/dist/mcp/tools/task-reads.d.ts +102 -0
- package/dist/mcp/tools/task-reads.js +689 -0
- package/dist/mcp/tools/task-reads.js.map +1 -0
- package/dist/mcp/tools/work-organisation.d.ts +2 -0
- package/dist/mcp/tools/work-organisation.js +502 -0
- package/dist/mcp/tools/work-organisation.js.map +1 -0
- package/dist/paths.d.ts +17 -7
- package/dist/paths.js +18 -8
- package/dist/paths.js.map +1 -1
- package/dist/runs/project-writer.js +76 -11
- package/dist/runs/project-writer.js.map +1 -1
- package/dist/runs/retention.d.ts +25 -2
- package/dist/runs/retention.js +63 -2
- package/dist/runs/retention.js.map +1 -1
- package/dist/server/forge/github.js +44 -0
- package/dist/server/forge/github.js.map +1 -1
- package/dist/server/forge/types.d.ts +16 -0
- package/dist/server/project-context.d.ts +81 -8
- package/dist/server/project-context.js +143 -35
- package/dist/server/project-context.js.map +1 -1
- package/dist/server/server.d.ts +1050 -53
- package/dist/server/server.js +326 -120
- package/dist/server/server.js.map +1 -1
- package/dist/server/validators.d.ts +26 -6
- package/dist/server/validators.js +17 -0
- package/dist/server/validators.js.map +1 -1
- package/dist/skills-remote.js +64 -6
- package/dist/skills-remote.js.map +1 -1
- package/dist/workflows/run.d.ts +277 -12
- package/dist/workflows/run.js +599 -79
- package/dist/workflows/run.js.map +1 -1
- package/dist/workspace/agent-profiles.d.ts +30 -3
- package/dist/workspace/agent-profiles.js +39 -4
- package/dist/workspace/agent-profiles.js.map +1 -1
- package/dist/workspace/config.js +25 -4
- package/dist/workspace/config.js.map +1 -1
- package/dist/workspace/project-owner.d.ts +210 -0
- package/dist/workspace/project-owner.js +518 -0
- package/dist/workspace/project-owner.js.map +1 -0
- package/package.json +5 -3
- package/scripts/pi-leader-extension.ts +420 -0
- package/scripts/sync-readme.mjs +83 -2
- package/web/dist/assets/{alert-dialog-BVsVcbFy.js → alert-dialog-D6rP6d39.js} +1 -1
- package/web/dist/assets/arrow-down-DMOfFIzW.js +1 -0
- package/web/dist/assets/arrow-left-C4Ec2BUg.js +1 -0
- package/web/dist/assets/{centered-state-Djgly6Ni.js → centered-state-B0ZqrPaa.js} +12 -12
- package/web/dist/assets/chevron-right-mHJ-crvn.js +1 -0
- package/web/dist/assets/{collapsible-DZ4D-DHG.js → collapsible-DchEHgUM.js} +1 -1
- package/web/dist/assets/{commit-list-B-z1JL9G.js → commit-list-BpMim1Rv.js} +1 -1
- package/web/dist/assets/{compare-variants-CdS8r8E2.js → compare-variants-D5ItQbAq.js} +1 -1
- package/web/dist/assets/{diff-BKew9rQX.js → diff-HqqZ_WDR.js} +2 -2
- package/web/dist/assets/{diff-stat-D0mc5V6Z.js → diff-stat-NsU-3ecr.js} +1 -1
- package/web/dist/assets/{diff-view-xpIcuLIC.js → diff-view-B7c11Xux.js} +1 -1
- package/web/dist/assets/{dropdown-menu-CEQziH7X.js → dropdown-menu-CtTpnyod.js} +1 -1
- package/web/dist/assets/{editable-title-BCX3T2BY.js → editable-title-BXtuGQry.js} +1 -1
- package/web/dist/assets/{ellipsis-vertical-ByVdDc4z.js → ellipsis-vertical-MHtGZSH6.js} +1 -1
- package/web/dist/assets/{file-xBSB5hXO.js → file-DKLw5SOb.js} +1 -1
- package/web/dist/assets/{folder-CldgbZ5A.js → folder-Drw6-ufH.js} +1 -1
- package/web/dist/assets/{git-toolbar-BYlnf4qf.js → git-toolbar-BQH8bORV.js} +1 -1
- package/web/dist/assets/github-ynnpKFCJ.js +1 -0
- package/web/dist/assets/{image-preview-Cv5mZr0n.js → image-preview-DLhMN_R9.js} +1 -1
- package/web/dist/assets/index-BEsrNdXt.js +29 -0
- package/web/dist/assets/index-Da8DOBau.css +2 -0
- package/web/dist/assets/{markdown-CgEEE2ND.js → markdown-q365eN6Y.js} +1 -1
- package/web/dist/assets/{new-task-form-1r2bgsdD.js → new-task-form-CW-hNIWQ.js} +1 -1
- package/web/dist/assets/{pill-q-x22_ej.js → pill-B_TFulXa.js} +1 -1
- package/web/dist/assets/{project-router-VDN4jQM8.js → project-router-CLPc-ImZ.js} +1 -1
- package/web/dist/assets/{prompt-templates-BmZtCe-L.js → prompt-templates-CLc4RxyO.js} +1 -1
- package/web/dist/assets/{repo-git-C2On8SlI.js → repo-git-Is7R6-Tj.js} +1 -1
- package/web/dist/assets/{run-diff-vguEMJzh.js → run-diff-DHMvDlVo.js} +2 -2
- package/web/dist/assets/run-header-DvZJbQ8I.js +1 -0
- package/web/dist/assets/{search-x-QmhaAbg5.js → search-x-Ce2MMetu.js} +1 -1
- package/web/dist/assets/{skill-empty-hint-BrzUwByZ.js → skill-empty-hint-C_xCSvzB.js} +1 -1
- package/web/dist/assets/skills-CivqD4si.js +1 -0
- package/web/dist/assets/{sparkles-CP8QDOgX.js → sparkles-BCqPoj5g.js} +1 -1
- package/web/dist/assets/{square-terminal-CGypshSD.js → square-terminal-ikj5g-oK.js} +1 -1
- package/web/dist/assets/{tab-link-D4lFzzI1.js → tab-link-VsEjFV1Y.js} +1 -1
- package/web/dist/assets/{task-changes-6aopqAmV.js → task-changes-CH-E81-_.js} +1 -1
- package/web/dist/assets/{task-commits-u-dwaCyw.js → task-commits-DqfkMFxp.js} +1 -1
- package/web/dist/assets/{task-files-IMOZnIUK.js → task-files-D_nkHZ_a.js} +2 -2
- package/web/dist/assets/task-thread-bxQompxJ.js +9 -0
- package/web/dist/assets/{textarea-CyBCyKIk.js → textarea-xajuW1L_.js} +1 -1
- package/web/dist/assets/{thread-loading-Cuq2jchC.js → thread-loading-C3JQZbIE.js} +1 -1
- package/web/dist/assets/{trash-2-B6Dp3ZNs.js → trash-2-hJrBhpUD.js} +1 -1
- package/web/dist/assets/{triangle-alert-xBya8FUp.js → triangle-alert-D1u82y1a.js} +1 -1
- package/web/dist/assets/{upload-D6MXEB9p.js → upload-Bn6FBZ59.js} +1 -1
- package/web/dist/assets/{use-desktop-fLDKEJoB.js → use-desktop-DOIMfHfY.js} +1 -1
- package/web/dist/assets/{use-submit-shortcut-JTpNzU-u.js → use-submit-shortcut-Dj4DHTdE.js} +1 -1
- package/web/dist/assets/utils-YwBpOoRN.js +64 -0
- package/web/dist/assets/{workflows-BY3KTkFw.js → workflows-CTaAKz2m.js} +3 -3
- package/web/dist/assets/{zoomable-image-GTYwLtkS.js → zoomable-image-C1-6P3tg.js} +1 -1
- package/web/dist/index.html +23 -23
- package/web/dist/assets/arrow-down-CYgAlZtW.js +0 -1
- package/web/dist/assets/arrow-left-BOqE0a3k.js +0 -1
- package/web/dist/assets/chevron-right-Cc9BWnbN.js +0 -1
- package/web/dist/assets/github-CqGK62Lw.js +0 -1
- package/web/dist/assets/index-3nxVmdn5.css +0 -2
- package/web/dist/assets/index-qX7n11FX.js +0 -9
- package/web/dist/assets/refresh-cw-DcNCj2nu.js +0 -1
- package/web/dist/assets/run-header-DM7MiKj9.js +0 -1
- package/web/dist/assets/skills-Bj-uG_Hz.js +0 -1
- package/web/dist/assets/task-thread-CbMPmrWq.js +0 -9
- package/web/dist/assets/utils--z9hRtl3.js +0 -64
package/dist/paths.d.ts
CHANGED
|
@@ -102,8 +102,8 @@ export declare function serverLockPath(instance?: string): string;
|
|
|
102
102
|
* Where each coding agent keeps its per-user config, honouring the env vars the
|
|
103
103
|
* vendors document: `$CLAUDE_CONFIG_DIR` relocates Claude Code's home;
|
|
104
104
|
* `$CODEX_HOME` relocates Codex's; `$OPENCODE_CONFIG_DIR`, then `$XDG_CONFIG_HOME`,
|
|
105
|
-
* relocate OpenCode's config dir (falling back to `~/.config`)
|
|
106
|
-
* tests and ops can set env live.
|
|
105
|
+
* relocate OpenCode's config dir (falling back to `~/.config`); `$PI_CODING_AGENT_DIR`
|
|
106
|
+
* relocates pi's. Read per call so tests and ops can set env live.
|
|
107
107
|
*
|
|
108
108
|
* OpenCode's own variable is checked FIRST because it is the narrow one. Reaching
|
|
109
109
|
* for `XDG_CONFIG_HOME` to move one agent's config relocates every XDG-aware tool
|
|
@@ -115,11 +115,21 @@ export declare function serverLockPath(instance?: string): string;
|
|
|
115
115
|
* and does not reach here — this slot IS the config dir, nothing else, so the
|
|
116
116
|
* config-only variable is exactly the right precision for it.
|
|
117
117
|
*
|
|
118
|
-
* pi
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
118
|
+
* pi relocates through `$PI_CODING_AGENT_DIR` (#329, re-checked against pi 0.85.1
|
|
119
|
+
* on 2026-09-12). This repo said the opposite in five places on the strength of one
|
|
120
|
+
* check on 2026-09-10, so the correction is spelled out: `pi --help` lists
|
|
121
|
+
* `PI_CODING_AGENT_DIR - Config directory (default: ~/.pi/agent)` under "Environment
|
|
122
|
+
* Variables", the shipped package documents it in `docs/environment-variables.md`,
|
|
123
|
+
* and the binary was observed reading AND writing there — a corrupt `settings.json`
|
|
124
|
+
* inside the pinned dir produced `Warning: Invalid settings file <pinned>/settings.json`,
|
|
125
|
+
* and the run wrote `auth.json`, `models-store.json` and `sessions/` into it while
|
|
126
|
+
* reporting "No API key found" with a populated `~/.pi/agent/auth.json` on disk. So
|
|
127
|
+
* identity moves with the dir, which is what `src/core/agent-profiles.ts` needs.
|
|
128
|
+
* `PI_PACKAGE_DIR` remains unrelated — it names the npm package, not the agent home.
|
|
129
|
+
* `PI_CODING_AGENT_SESSION_DIR` is deliberately NOT read here: it moves session
|
|
130
|
+
* storage only, so honouring it would answer a dir that holds no credentials.
|
|
131
|
+
*
|
|
132
|
+
* Deriving pi's home here rather than from `homedir()` at the call site is what keeps
|
|
123
133
|
* a test or a container off a real home — the reason this helper exists at all.
|
|
124
134
|
*
|
|
125
135
|
* These are the DEFAULT profile's dirs. A second login of the same CLI is an
|
package/dist/paths.js
CHANGED
|
@@ -141,8 +141,8 @@ export function serverLockPath(instance = DEFAULT_SERVER_INSTANCE) {
|
|
|
141
141
|
* Where each coding agent keeps its per-user config, honouring the env vars the
|
|
142
142
|
* vendors document: `$CLAUDE_CONFIG_DIR` relocates Claude Code's home;
|
|
143
143
|
* `$CODEX_HOME` relocates Codex's; `$OPENCODE_CONFIG_DIR`, then `$XDG_CONFIG_HOME`,
|
|
144
|
-
* relocate OpenCode's config dir (falling back to `~/.config`)
|
|
145
|
-
* tests and ops can set env live.
|
|
144
|
+
* relocate OpenCode's config dir (falling back to `~/.config`); `$PI_CODING_AGENT_DIR`
|
|
145
|
+
* relocates pi's. Read per call so tests and ops can set env live.
|
|
146
146
|
*
|
|
147
147
|
* OpenCode's own variable is checked FIRST because it is the narrow one. Reaching
|
|
148
148
|
* for `XDG_CONFIG_HOME` to move one agent's config relocates every XDG-aware tool
|
|
@@ -154,11 +154,21 @@ export function serverLockPath(instance = DEFAULT_SERVER_INSTANCE) {
|
|
|
154
154
|
* and does not reach here — this slot IS the config dir, nothing else, so the
|
|
155
155
|
* config-only variable is exactly the right precision for it.
|
|
156
156
|
*
|
|
157
|
-
* pi
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
157
|
+
* pi relocates through `$PI_CODING_AGENT_DIR` (#329, re-checked against pi 0.85.1
|
|
158
|
+
* on 2026-09-12). This repo said the opposite in five places on the strength of one
|
|
159
|
+
* check on 2026-09-10, so the correction is spelled out: `pi --help` lists
|
|
160
|
+
* `PI_CODING_AGENT_DIR - Config directory (default: ~/.pi/agent)` under "Environment
|
|
161
|
+
* Variables", the shipped package documents it in `docs/environment-variables.md`,
|
|
162
|
+
* and the binary was observed reading AND writing there — a corrupt `settings.json`
|
|
163
|
+
* inside the pinned dir produced `Warning: Invalid settings file <pinned>/settings.json`,
|
|
164
|
+
* and the run wrote `auth.json`, `models-store.json` and `sessions/` into it while
|
|
165
|
+
* reporting "No API key found" with a populated `~/.pi/agent/auth.json` on disk. So
|
|
166
|
+
* identity moves with the dir, which is what `src/core/agent-profiles.ts` needs.
|
|
167
|
+
* `PI_PACKAGE_DIR` remains unrelated — it names the npm package, not the agent home.
|
|
168
|
+
* `PI_CODING_AGENT_SESSION_DIR` is deliberately NOT read here: it moves session
|
|
169
|
+
* storage only, so honouring it would answer a dir that holds no credentials.
|
|
170
|
+
*
|
|
171
|
+
* Deriving pi's home here rather than from `homedir()` at the call site is what keeps
|
|
162
172
|
* a test or a container off a real home — the reason this helper exists at all.
|
|
163
173
|
*
|
|
164
174
|
* These are the DEFAULT profile's dirs. A second login of the same CLI is an
|
|
@@ -173,7 +183,7 @@ export function agentHomePaths(env = process.env) {
|
|
|
173
183
|
claude: env.CLAUDE_CONFIG_DIR?.trim() || join(home, '.claude'),
|
|
174
184
|
codex: env.CODEX_HOME?.trim() || join(home, '.codex'),
|
|
175
185
|
opencodeConfig: env.OPENCODE_CONFIG_DIR?.trim() || join(xdgConfig, 'opencode'),
|
|
176
|
-
pi: join(home, '.pi', 'agent'),
|
|
186
|
+
pi: env.PI_CODING_AGENT_DIR?.trim() || join(home, '.pi', 'agent'),
|
|
177
187
|
};
|
|
178
188
|
}
|
|
179
189
|
/**
|
package/dist/paths.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { closeSync, openSync, mkdirSync, readFileSync, readdirSync, realpathSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { hostname } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
+
import { localMachineId, machineRelation } from '../machine-identity.js';
|
|
5
6
|
const owned = new Map();
|
|
6
7
|
export class ProjectWriterError extends Error {
|
|
7
8
|
dataDir;
|
|
@@ -11,6 +12,56 @@ export class ProjectWriterError extends Error {
|
|
|
11
12
|
this.name = 'ProjectWriterError';
|
|
12
13
|
}
|
|
13
14
|
}
|
|
15
|
+
/** The claim's exact bytes, for PUBLISHING only — the self-check (`claimIsOurs`) compares
|
|
16
|
+
* fields, not bytes, because the `host` recorded here is display-only and this helper reads
|
|
17
|
+
* `hostname()` live. `machine` is omitted (not written as null) when this host cannot identify
|
|
18
|
+
* itself, so an unidentifiable host keeps writing the pre-#199 shape byte for byte. */
|
|
19
|
+
function claimBody(machine) {
|
|
20
|
+
return JSON.stringify({ pid: process.pid, host: hostname(), ...(machine === null ? {} : { machine }) });
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Is the claim this process published still the one on disk?
|
|
24
|
+
*
|
|
25
|
+
* It compares the two facts that identify a writer — the PID and the MACHINE — and never the
|
|
26
|
+
* hostname, for the same reason the peer scan stopped trusting it: `claimBody` reads
|
|
27
|
+
* `hostname()` live, so a byte-for-byte comparison against a freshly built body turns a machine
|
|
28
|
+
* renaming itself into "the active claim changed". That is #199's lockout reproduced INSIDE one
|
|
29
|
+
* process, and it is reachable long after boot — `automations/coordinator.ts` calls
|
|
30
|
+
* `ownProjectData` lazily, so a laptop that changes network while `xez serve` is up hits it.
|
|
31
|
+
* `localMachineId()` is memoized, so the hostname is the only field that could ever have moved.
|
|
32
|
+
*
|
|
33
|
+
* Fails closed on anything it cannot read or parse: an unreadable, truncated or rewritten claim
|
|
34
|
+
* is not this process's claim. `unknown` (this host will not name itself, or the claim predates
|
|
35
|
+
* the field) passes, exactly as the peer scan treats it — there is no evidence of a different
|
|
36
|
+
* machine, and the file's path already carries this process's PID and a UUID only it generated.
|
|
37
|
+
*/
|
|
38
|
+
function claimIsOurs(path, machine) {
|
|
39
|
+
let parsed;
|
|
40
|
+
try {
|
|
41
|
+
parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (!parsed || typeof parsed !== 'object')
|
|
47
|
+
return false;
|
|
48
|
+
const claim = parsed;
|
|
49
|
+
return claim.pid === process.pid && machineRelation(claim.machine, machine) !== 'different';
|
|
50
|
+
}
|
|
51
|
+
/** Long hostnames and corrupt claims must not turn an error message into a wall of text. */
|
|
52
|
+
function short(value) {
|
|
53
|
+
return JSON.stringify(String(value).slice(0, 120));
|
|
54
|
+
}
|
|
55
|
+
/** Name the file, both hostnames and the PID, because the only repair is deleting one file and
|
|
56
|
+
* "foreign-host writer claim" pointed at nothing (#199). Says WHY the machines were judged
|
|
57
|
+
* different, so "a different machine" and "no identity to compare" never read the same. */
|
|
58
|
+
function foreignClaimDetail(path, recordedHost, pid, relation) {
|
|
59
|
+
const evidence = relation === 'different'
|
|
60
|
+
? 'It also records a different machine identity.'
|
|
61
|
+
: 'It records no machine identity to compare, so the hostname was the only evidence.';
|
|
62
|
+
return `foreign-host writer claim: ${path} names host ${short(recordedHost)} (PID ${pid}); this host is ${short(hostname())}.`
|
|
63
|
+
+ ` ${evidence} If that machine is no longer using this directory, delete that claim file.`;
|
|
64
|
+
}
|
|
14
65
|
/** One process owns a project's mutable state, independent of port and XEZ_HOME.
|
|
15
66
|
* Publish a unique claim BEFORE scanning. Two simultaneous contenders may both refuse,
|
|
16
67
|
* but cannot both see themselves alone. Dead claims have unique paths, so reclaiming one
|
|
@@ -20,19 +71,16 @@ export class ProjectWriterError extends Error {
|
|
|
20
71
|
export function ownProjectData(dataDir) {
|
|
21
72
|
mkdirSync(dataDir, { recursive: true });
|
|
22
73
|
const canonical = realpathSync(dataDir);
|
|
74
|
+
const machine = localMachineId();
|
|
23
75
|
const prior = owned.get(canonical);
|
|
24
76
|
if (prior) {
|
|
25
77
|
// Deleting active runtime state is not a supported way to release ownership.
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
intact = readFileSync(prior, 'utf8') === JSON.stringify({ pid: process.pid, host: hostname() });
|
|
29
|
-
}
|
|
30
|
-
catch { /* fail closed */ }
|
|
31
|
-
if (!intact) {
|
|
78
|
+
if (!claimIsOurs(prior, machine)) {
|
|
32
79
|
throw new ProjectWriterError(canonical, 'the active claim changed');
|
|
33
80
|
}
|
|
34
81
|
return;
|
|
35
82
|
}
|
|
83
|
+
const body = claimBody(machine);
|
|
36
84
|
const directory = join(canonical, 'writer-claims');
|
|
37
85
|
mkdirSync(directory, { recursive: true });
|
|
38
86
|
const claim = join(directory, `${process.pid}-${randomUUID()}.json`);
|
|
@@ -41,7 +89,7 @@ export function ownProjectData(dataDir) {
|
|
|
41
89
|
const descriptor = openSync(claim, 'wx', 0o600);
|
|
42
90
|
published = true; // Exclusive open established ownership even if the following write fails.
|
|
43
91
|
try {
|
|
44
|
-
writeFileSync(descriptor,
|
|
92
|
+
writeFileSync(descriptor, body);
|
|
45
93
|
}
|
|
46
94
|
finally {
|
|
47
95
|
closeSync(descriptor);
|
|
@@ -71,8 +119,22 @@ export function ownProjectData(dataDir) {
|
|
|
71
119
|
peer = JSON.parse(raw);
|
|
72
120
|
}
|
|
73
121
|
catch { /* A live incomplete claim still refuses below. */ }
|
|
74
|
-
|
|
75
|
-
|
|
122
|
+
// What the host check was load-bearing FOR: a PID number is only evidence on the machine
|
|
123
|
+
// that wrote it, so a foreign claim must never be reaped by probing that PID here. A
|
|
124
|
+
// hostname was standing in for the machine, and a laptop that changes networks changes its
|
|
125
|
+
// name (#199), so a recorded identity that MATCHES this machine settles the question and
|
|
126
|
+
// lets the hostname become display-only.
|
|
127
|
+
//
|
|
128
|
+
// Deliberately one-way. A mismatched or absent identity falls back to the hostname exactly
|
|
129
|
+
// as before, so no claim this guard used to refuse becomes reapable: a restarted container
|
|
130
|
+
// with a pinned hostname still recovers its own dead claim, and two hosts sharing storage
|
|
131
|
+
// are no more reapable by PID than they were. Only "provably the same machine" is new.
|
|
132
|
+
const relation = peer && typeof peer === 'object'
|
|
133
|
+
? machineRelation(peer.machine, machine)
|
|
134
|
+
: 'unknown';
|
|
135
|
+
const foreign = (recordedHost) => relation !== 'same' && recordedHost !== hostname();
|
|
136
|
+
if (peer && typeof peer === 'object' && 'host' in peer && foreign(peer.host)) {
|
|
137
|
+
throw new Error(foreignClaimDetail(path, peer.host, pid, relation));
|
|
76
138
|
}
|
|
77
139
|
let dead = false;
|
|
78
140
|
try {
|
|
@@ -93,10 +155,13 @@ export function ownProjectData(dataDir) {
|
|
|
93
155
|
}
|
|
94
156
|
continue;
|
|
95
157
|
}
|
|
158
|
+
// Same host rule as above, through the same helper: half the sites is half a fix, and a
|
|
159
|
+
// live writer on this machine under its earlier name must report itself as the live writer
|
|
160
|
+
// it is rather than as an invalid claim.
|
|
96
161
|
if (!peer || typeof peer !== 'object' || !('pid' in peer) || !('host' in peer)
|
|
97
162
|
|| !Number.isSafeInteger(peer.pid) || Number(peer.pid) <= 0
|
|
98
|
-
|| peer.host
|
|
99
|
-
throw new Error(
|
|
163
|
+
|| foreign(peer.host) || peer.pid !== pid) {
|
|
164
|
+
throw new Error(`invalid or foreign-host writer claim: ${path}`);
|
|
100
165
|
}
|
|
101
166
|
throw new Error(`live writer PID ${peer.pid}`);
|
|
102
167
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-writer.js","sourceRoot":"","sources":["../../src/runs/project-writer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7H,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"project-writer.js","sourceRoot":"","sources":["../../src/runs/project-writer.ts"],"names":[],"mappings":"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"}
|
package/dist/runs/retention.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type OwnershipScope } from '../mcp/resource-ownership.ts';
|
|
1
2
|
import type { RunRecord } from './store.ts';
|
|
2
3
|
/** A run is reclaimable when it is finished, still has a materialized worktree
|
|
3
4
|
* directory, and has not already been reclaimed. */
|
|
@@ -34,7 +35,7 @@ export interface RematerializeStore {
|
|
|
34
35
|
* run regains its isolated tree and becomes eligible for retention again;
|
|
35
36
|
* without it the run would keep a directory on disk while staying invisible to
|
|
36
37
|
* the enforcer forever (a leak). Returns true when it re-materialized.
|
|
37
|
-
* Best-effort: never throws
|
|
38
|
+
* Best-effort: never throws; the continuation caller refuses lost isolation before spawning.
|
|
38
39
|
*/
|
|
39
40
|
export declare function rematerializeReclaimedWorktree(repoRoot: string, store: RematerializeStore, runId: string): Promise<boolean>;
|
|
40
41
|
/**
|
|
@@ -47,7 +48,9 @@ export declare function rematerializeReclaimedWorktree(repoRoot: string, store:
|
|
|
47
48
|
* report failure, so a run is stamped only once its directory is confirmed gone
|
|
48
49
|
* — a locked/permission failure leaves the stamp unset so the next pass retries.
|
|
49
50
|
* Idempotent under races: `removeWorktree` is `--force` + `prune` and a repeated
|
|
50
|
-
* stamp is harmless.
|
|
51
|
+
* stamp is harmless. A caller with a lifecycle passes `shouldStop` and the sweep abandons the
|
|
52
|
+
* rest of the pass the moment that caller is done with the repository, returning the ids it had
|
|
53
|
+
* already reclaimed.
|
|
51
54
|
*/
|
|
52
55
|
export interface ReclaimOptions {
|
|
53
56
|
/** Timestamp source for the stamp — injectable for deterministic tests. */
|
|
@@ -56,5 +59,25 @@ export interface ReclaimOptions {
|
|
|
56
59
|
* Injectable so tests can exercise the "removal failed" branch without brittle
|
|
57
60
|
* filesystem-permission tricks. */
|
|
58
61
|
remove?: (repoRoot: string, worktreePath: string) => Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Asked before EVERY iteration: has the caller stopped owning this repository?
|
|
64
|
+
*
|
|
65
|
+
* A sweep is a loop of `git worktree remove` spawns with a `store.updateRun` stamp after each
|
|
66
|
+
* one, so a caller that checks "am I still alive" once before calling this stops the sweep
|
|
67
|
+
* before its FIRST removal and never again — and a five-worktree pass that straddles a
|
|
68
|
+
* `RunManager.dispose()` goes on writing into a data root its owner has finished with (#200).
|
|
69
|
+
*
|
|
70
|
+
* Absent means "never stop", which is what every one-shot caller (boot, the reclaim route)
|
|
71
|
+
* wants: they have no lifecycle to lose mid-sweep.
|
|
72
|
+
*/
|
|
73
|
+
shouldStop?: () => boolean;
|
|
59
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* A run of this project whose recorded worktree, if it names one, is provably this project's own
|
|
77
|
+
* (`ownRun` + `ownWorktree`) or already gone — the rule above, per run. The cockpit's run routes
|
|
78
|
+
* that delete, commit, push or run git in that worktree ask the same question through this one
|
|
79
|
+
* function (#316), so reclaim and those routes cannot come to disagree about what is ours.
|
|
80
|
+
* `null` when the run is unknown here or reaches anywhere else.
|
|
81
|
+
*/
|
|
82
|
+
export declare function ownRunAndWorktree(scope: OwnershipScope, runId: string): Promise<RunRecord | null>;
|
|
60
83
|
export declare function reclaimWorktrees(repoRoot: string, store: RetentionStore, keep: number, opts?: ReclaimOptions): Promise<string[]>;
|
package/dist/runs/retention.js
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
// recoverable) and the thin I/O enforcer that performs the reclaim. The selector
|
|
6
6
|
// is pure and unit-testable; the enforcer never throws (helper discipline).
|
|
7
7
|
import { existsSync } from 'node:fs';
|
|
8
|
+
import { lstat } from 'node:fs/promises';
|
|
8
9
|
import { createWorktree, removeWorktree } from '../git-worktree.js';
|
|
10
|
+
import { ownRun, ownWorktree, ownershipScope } from '../mcp/resource-ownership.js';
|
|
9
11
|
/** The "finished" status set — mirrors `RunStore.archiveFinished`. A run at the
|
|
10
12
|
* `review` gate is deliberately excluded: it still needs its worktree to render
|
|
11
13
|
* the diff and open a draft PR, so reclaiming it would break the gate. */
|
|
@@ -45,7 +47,7 @@ export function selectReclaimableWorktrees(runs, keep) {
|
|
|
45
47
|
* run regains its isolated tree and becomes eligible for retention again;
|
|
46
48
|
* without it the run would keep a directory on disk while staying invisible to
|
|
47
49
|
* the enforcer forever (a leak). Returns true when it re-materialized.
|
|
48
|
-
* Best-effort: never throws
|
|
50
|
+
* Best-effort: never throws; the continuation caller refuses lost isolation before spawning.
|
|
49
51
|
*/
|
|
50
52
|
export async function rematerializeReclaimedWorktree(repoRoot, store, runId) {
|
|
51
53
|
const run = store.getRun(runId);
|
|
@@ -60,13 +62,72 @@ export async function rematerializeReclaimedWorktree(repoRoot, store, runId) {
|
|
|
60
62
|
return false;
|
|
61
63
|
}
|
|
62
64
|
}
|
|
65
|
+
/** Reclaim looks up no automation, so that half of the ownership scope refuses everything. */
|
|
66
|
+
const NO_AUTOMATIONS = { get: () => undefined, latestReceipts: () => new Map() };
|
|
67
|
+
/**
|
|
68
|
+
* The reclaimable runs whose worktree is provably this project's own (#288). A record's
|
|
69
|
+
* `worktreePath` is data — a copied or hand-edited `.local/xezar` can name another project's
|
|
70
|
+
* worktree — and reclaim deletes that directory. So a candidate must name this project's
|
|
71
|
+
* `<root>/.local/xezar/worktrees/<id>` (`ownRun`), and a directory that exists there must be a
|
|
72
|
+
* real one reached through no symlink (`ownWorktree`). A directory that is already gone reaches
|
|
73
|
+
* nothing and stays a candidate, so its stamp is still written. Anything else is left out BEFORE
|
|
74
|
+
* selection: it neither counts against `keep` nor is touched. Per record rather than `ownSweep`'s
|
|
75
|
+
* whole-sweep refusal, because this also runs unattended at boot and at every task's end, where
|
|
76
|
+
* refusing the whole sweep would silently switch retention (#483) off for the project.
|
|
77
|
+
*/
|
|
78
|
+
async function ownedReclaimable(repoRoot, runs) {
|
|
79
|
+
const byId = new Map(runs.map((r) => [r.id, r]));
|
|
80
|
+
const scope = ownershipScope({
|
|
81
|
+
root: repoRoot,
|
|
82
|
+
store: { getRun: (id) => byId.get(id), listRuns: () => [...runs] },
|
|
83
|
+
automationStore: NO_AUTOMATIONS,
|
|
84
|
+
});
|
|
85
|
+
const owned = [];
|
|
86
|
+
for (const run of runs) {
|
|
87
|
+
if (isReclaimable(run) && (await ownRunAndWorktree(scope, run.id)))
|
|
88
|
+
owned.push(run);
|
|
89
|
+
}
|
|
90
|
+
return owned;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* A run of this project whose recorded worktree, if it names one, is provably this project's own
|
|
94
|
+
* (`ownRun` + `ownWorktree`) or already gone — the rule above, per run. The cockpit's run routes
|
|
95
|
+
* that delete, commit, push or run git in that worktree ask the same question through this one
|
|
96
|
+
* function (#316), so reclaim and those routes cannot come to disagree about what is ours.
|
|
97
|
+
* `null` when the run is unknown here or reaches anywhere else.
|
|
98
|
+
*/
|
|
99
|
+
export async function ownRunAndWorktree(scope, runId) {
|
|
100
|
+
const run = ownRun(scope, runId);
|
|
101
|
+
if (!run.ok)
|
|
102
|
+
return null;
|
|
103
|
+
const path = run.value.worktreePath;
|
|
104
|
+
if (path === undefined || (await ownWorktree(scope, runId)).ok || (await isAbsent(path)))
|
|
105
|
+
return run.value;
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
/** Nothing at `path`, not even a dangling link. Any other answer is not "absent" (fail closed). */
|
|
109
|
+
async function isAbsent(path) {
|
|
110
|
+
try {
|
|
111
|
+
await lstat(path);
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
return err.code === 'ENOENT';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
63
118
|
export async function reclaimWorktrees(repoRoot, store, keep, opts = {}) {
|
|
64
119
|
const now = opts.now ?? (() => new Date().toISOString());
|
|
65
120
|
const remove = opts.remove ?? ((root, path) => removeWorktree(root, path)); // branch kept
|
|
121
|
+
const shouldStop = opts.shouldStop ?? (() => false);
|
|
66
122
|
const runs = store.listRuns();
|
|
67
123
|
const byId = new Map(runs.map((r) => [r.id, r]));
|
|
68
124
|
const reclaimed = [];
|
|
69
|
-
for (const id of selectReclaimableWorktrees(runs, keep)) {
|
|
125
|
+
for (const id of selectReclaimableWorktrees(await ownedReclaimable(repoRoot, runs), keep)) {
|
|
126
|
+
// Per iteration, not once before the loop: each pass costs a git spawn and a record write,
|
|
127
|
+
// and the caller's ownership can end between any two of them. Returning what was already
|
|
128
|
+
// reclaimed is honest — those directories really are gone.
|
|
129
|
+
if (shouldStop())
|
|
130
|
+
break;
|
|
70
131
|
const run = byId.get(id);
|
|
71
132
|
if (!run?.worktreePath)
|
|
72
133
|
continue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retention.js","sourceRoot":"","sources":["../../src/runs/retention.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,oFAAoF;AACpF,+EAA+E;AAC/E,iFAAiF;AACjF,iFAAiF;AACjF,4EAA4E;AAC5E,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"retention.js","sourceRoot":"","sources":["../../src/runs/retention.ts"],"names":[],"mappings":"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"}
|
|
@@ -2482,6 +2482,49 @@ async function mergePullRequest(repoRoot, repoRef, number, input) {
|
|
|
2482
2482
|
export function mergePreflightAllowed(current, overrideRules = false) {
|
|
2483
2483
|
return current.canMerge || (overrideRules && current.canOverride);
|
|
2484
2484
|
}
|
|
2485
|
+
const readyInflight = new Set();
|
|
2486
|
+
/**
|
|
2487
|
+
* Mark a draft pull request ready for review (`gh pr ready`). The same preflight shape as the
|
|
2488
|
+
* merge: a FRESH read of the forge, the reviewed head as a compare-and-swap, and a refusal in
|
|
2489
|
+
* plain words for a pull request that is not an open draft. What counts as a quality reason not
|
|
2490
|
+
* to ready it is the caller's policy; this only does what the forge would do. Never throws.
|
|
2491
|
+
*/
|
|
2492
|
+
async function markPullRequestReady(repoRoot, repoRef, number, input) {
|
|
2493
|
+
const key = `${repoRoot}:${number}`;
|
|
2494
|
+
if (readyInflight.has(key))
|
|
2495
|
+
return { ready: false, status: 409, error: 'This pull request is already being marked ready.', code: 'concurrent' };
|
|
2496
|
+
readyInflight.add(key);
|
|
2497
|
+
try {
|
|
2498
|
+
const fresh = await fetchPrMergeState(repoRoot, repoRef, number, true);
|
|
2499
|
+
if (!fresh.available)
|
|
2500
|
+
return { ready: false, status: 502, error: fresh.reason };
|
|
2501
|
+
const current = fresh.mergeState;
|
|
2502
|
+
if (current.state !== 'open') {
|
|
2503
|
+
return { ready: false, status: 409, error: current.state === 'merged' ? 'This pull request is merged.' : 'This pull request is closed.', code: 'terminal', current };
|
|
2504
|
+
}
|
|
2505
|
+
if (!current.isDraft) {
|
|
2506
|
+
return { ready: false, status: 409, error: 'This pull request is already ready for review.', code: 'already-ready', current };
|
|
2507
|
+
}
|
|
2508
|
+
if (current.headSha !== input.expectedHeadSha) {
|
|
2509
|
+
return { ready: false, status: 409, error: 'The pull request head changed. Review the new commits before marking it ready.', code: 'stale-head', current };
|
|
2510
|
+
}
|
|
2511
|
+
if (process.env.XEZ_DRY_RUN === '1') {
|
|
2512
|
+
evictGithubProjectCaches(repoRoot);
|
|
2513
|
+
return { ready: true, number, url: current.url };
|
|
2514
|
+
}
|
|
2515
|
+
await gh(repoRoot, ['pr', 'ready', String(number)]);
|
|
2516
|
+
evictGithubProjectCaches(repoRoot);
|
|
2517
|
+
return { ready: true, number, url: current.url };
|
|
2518
|
+
}
|
|
2519
|
+
catch (error) {
|
|
2520
|
+
const message = firstLine(error instanceof Error ? error.message : String(error));
|
|
2521
|
+
const status = /403|permission|forbidden/i.test(message) ? 403 : /404|not found/i.test(message) ? 404 : 502;
|
|
2522
|
+
return { ready: false, status, error: status === 403 ? 'GitHub permission denied.' : status === 404 ? 'Pull request or repository not found.' : 'GitHub could not mark the pull request ready.' };
|
|
2523
|
+
}
|
|
2524
|
+
finally {
|
|
2525
|
+
readyInflight.delete(key);
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2485
2528
|
const GH_PR_STATES = {
|
|
2486
2529
|
MERGED: 'merged',
|
|
2487
2530
|
CLOSED: 'closed',
|
|
@@ -2520,6 +2563,7 @@ export function createGithubDriver(repoRoot, repoRef) {
|
|
|
2520
2563
|
},
|
|
2521
2564
|
prMergeState: (number, opts) => fetchPrMergeState(repoRoot, repoRef, number, opts?.refresh),
|
|
2522
2565
|
mergePR: (number, input) => mergePullRequest(repoRoot, repoRef, number, input),
|
|
2566
|
+
markReady: (number, input) => markPullRequestReady(repoRoot, repoRef, number, input),
|
|
2523
2567
|
viewUrl: (kind, ref) => {
|
|
2524
2568
|
if (!repoRef)
|
|
2525
2569
|
return null;
|