opencode-ship 1.1.4 → 1.1.7
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/CHANGELOG.md +48 -0
- package/README.md +12 -12
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/assets/agents/ship-plan.md +18 -14
- package/dist/cli.js +14 -14
- package/dist/core.d.ts +583 -1
- package/dist/core.js +46 -36
- package/dist/plugin.js +124 -69
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,54 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `opencode-ship` are recorded here.
|
|
4
4
|
|
|
5
|
+
## 1.1.7 — Stabilization and self-hosting (unreleased)
|
|
6
|
+
|
|
7
|
+
> Baseline: https://github.com/Viktorxyz/opencode-ship/issues/70.
|
|
8
|
+
> Corrective package: https://github.com/Viktorxyz/opencode-ship/issues/72.
|
|
9
|
+
> Authoritative plan: `docs/release/1.1.6-correction-plan.md`.
|
|
10
|
+
|
|
11
|
+
- **Truthful baseline before implementation.** `1.1.5` remains the current
|
|
12
|
+
npm `latest`, and `1.1.7` is the active unpublished correction. The immutable
|
|
13
|
+
tag `1.1.6` failed in `pack` before npm publish or GitHub Release. This entry
|
|
14
|
+
does not claim the `1.1.7` fixes have shipped or qualified, and `main` does
|
|
15
|
+
not yet contain its own installed `.opencode` control layer.
|
|
16
|
+
- **Bounded follow-up slices.** Runtime and release corrections, exact-version
|
|
17
|
+
self-host bootstrap, and automated exact-version update proposals will ship
|
|
18
|
+
as separate issue/worktree/PR lifecycles. Every automated install or update
|
|
19
|
+
pins `opencode-ship@<X.Y.Z>` and never resolves `@latest`.
|
|
20
|
+
- **Convergent merge and cleanup recovery.** A Ready manifest now reconciles a
|
|
21
|
+
PR that a maintainer already merged in GitHub instead of attempting a second
|
|
22
|
+
merge. Cleanup retries resume from their persisted stage, tolerate an
|
|
23
|
+
already-removed worktree, delete the branch with the existing CAS guard,
|
|
24
|
+
seal the manifest, and clear the Git-common-dir pending record.
|
|
25
|
+
- **Portable locks and bounded permissions.** Managed lock templates are
|
|
26
|
+
package-relative rather than npm-cache absolute paths. Build no longer
|
|
27
|
+
installs a broad `bash/* = allow` rule; destructive command denies remain
|
|
28
|
+
explicit while ordinary built-ins stay consumer-owned.
|
|
29
|
+
- **Release and package hardening.** The GitHub Release job checks out the
|
|
30
|
+
resolved release ref, `prepack` fails without installing missing tools,
|
|
31
|
+
`opencode-ship/core` publishes real declarations and the canonical package
|
|
32
|
+
version, and the stale `0.1.3` source literal is removed.
|
|
33
|
+
- **Release claims stay evidence-bound.** This entry becomes a released
|
|
34
|
+
changelog only after local verification, npm provenance, the GitHub Release,
|
|
35
|
+
qualification hashes, and a real self-host lifecycle all agree.
|
|
36
|
+
|
|
37
|
+
## 1.1.6 — Qualification failed before publication
|
|
38
|
+
|
|
39
|
+
> Release workflow run: https://github.com/Viktorxyz/opencode-ship/actions/runs/33495352417.
|
|
40
|
+
|
|
41
|
+
- The immutable `1.1.6` tag failed in `pack` before npm publish or GitHub
|
|
42
|
+
Release. No npm package or GitHub Release was created.
|
|
43
|
+
|
|
44
|
+
## 1.1.5 — `ship-plan` is product-only
|
|
45
|
+
|
|
46
|
+
> Branch: `feat/1.1.5-ship-plan-prompt`.
|
|
47
|
+
|
|
48
|
+
- **`ship-plan` prompt is product-only.** `1.1.4` shipped the `ship-plan` primary agent with a generic "ask clarifying questions when tradeoffs need the user's call" prompt and a "tell the user the file path and ask for confirmation before any edits" step. Both leaked workflow concerns into the chat: the model asked "open GitHub issues 0–6 or first Task 0 (commit /final)", "use this Tab (Build is for implementation)", and surfaced permission-glob internals ("`docs/superpowers/plans/` is deny; allowlist is `.opencode/plans/*.md`"). The Plan tab is the **product** half of the workflow; the user is there to talk about the product, not be taught the workflow. The rewrite replaces those lines with a strict contract: the prompt is allowed to ask only product questions (scope, UX, who it is for, what done looks like) and one final product question ("does the plan match the product you want?") before stopping. The prompt never asks how to run the work (issues vs Task N, subagent vs inline, Tab / Build, `ship-deliver`, "what next"), never mentions permission globs / deny lists / allowlists, never creates GitHub issues, never claims OpenCode's native `plan_exit`, and never offers to implement.
|
|
49
|
+
- **Handoff is a file, not a chat menu.** The Plan tab writes `.opencode/plans/<filename>.md` and stops. A later Build session is told `implement this plan <path>` (or `implement issue N` for the Issue path). The Plan tab does not offer "which approach?" or "subagent vs inline" — those are upstream Superpowers choices that ship does not copy. If a PR needs `Closes #N`, that issue is created by Build (`delivery_issue`), not by Plan.
|
|
50
|
+
- **Subagent dispatch is internal.** `ship-plan` keeps its `task: explore` / `general` allowlist so it can dispatch read-only investigators and write scratch notes, but the prompt no longer asks the user whether to use subagents. The decision is made by the planning agent itself from the size of the request.
|
|
51
|
+
- **`tests/package/neutral-consumer.test.mjs` regression.** The post-init probe now asserts the new prompt lines are present and the old workflow-leaking lines are absent. The 1.1.3 stale-record removal path is unchanged, so `opencode-ship update` from prior 1.1.x installs still cleans the leftover `*` wildcards the same way it did before this release.
|
|
52
|
+
|
|
5
53
|
## 1.1.4 — `ship-plan` primary planning agent
|
|
6
54
|
|
|
7
55
|
> Branch: `feat/1.1.4-ship-plan`.
|
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
> npm-distributed OpenCode installer and delivery plugin: a single command materialises the lifecycle plugin, reviewer/verifier agents, and skills into any consumer repository, with a recoverable lock and never silently overwrites managed files.
|
|
4
4
|
>
|
|
5
|
-
> **Status:** `1.1.
|
|
5
|
+
> **Status:** `1.1.5` is the current `npm dist-tag latest`. It makes the `ship-plan` primary planning agent product-only: the agent asks about the product, writes `.opencode/plans/*.md`, and stops without leaking delivery-workflow choices into the planning conversation. The immutable tag `1.1.6` failed in `pack` before npm publish or GitHub Release; no npm package or GitHub Release was created. `1.1.7` is the active unpublished correction, tracked by the authoritative [`docs/release/1.1.6-correction-plan.md`](docs/release/1.1.6-correction-plan.md).
|
|
6
6
|
>
|
|
7
|
-
>
|
|
7
|
+
> Use an exact package version for reproducible installs and all self-hosting work. Until `1.1.7` is published and qualified, the verified stable pin is `opencode-ship@1.1.5`; never resolve `@latest` inside an automated release or self-host update.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
14
|
# 1. Install managed files. No flags required.
|
|
15
|
-
pnpm dlx opencode-ship@
|
|
15
|
+
pnpm dlx opencode-ship@1.1.5 init
|
|
16
16
|
|
|
17
17
|
# 2. Restart OpenCode in this repo.
|
|
18
18
|
|
|
@@ -70,11 +70,11 @@ The only source tree that ships in the npm tarball is `assets/`. Anything copied
|
|
|
70
70
|
Once `opencode-ship` is published, consumers install with `pnpm dlx` (or `npx`) and never edit the file by hand:
|
|
71
71
|
|
|
72
72
|
```
|
|
73
|
-
pnpm dlx opencode-ship@
|
|
74
|
-
pnpm dlx opencode-ship@
|
|
75
|
-
pnpm dlx opencode-ship@
|
|
76
|
-
pnpm dlx opencode-ship@
|
|
77
|
-
pnpm dlx opencode-ship@
|
|
73
|
+
pnpm dlx opencode-ship@1.1.5 init # install managed files
|
|
74
|
+
pnpm dlx opencode-ship@1.1.5 update # apply a packaged upgrade
|
|
75
|
+
pnpm dlx opencode-ship@1.1.5 diff # preview what would change
|
|
76
|
+
pnpm dlx opencode-ship@1.1.5 doctor # environment and lock audit
|
|
77
|
+
pnpm dlx opencode-ship@1.1.5 uninstall # remove only the files still matching the lock
|
|
78
78
|
```
|
|
79
79
|
|
|
80
80
|
If you want to try a pre-release tarball locally without publishing to npm:
|
|
@@ -109,13 +109,13 @@ These JSON Schemas are published and discoverable through the `exports` map:
|
|
|
109
109
|
|
|
110
110
|
### Legacy migration
|
|
111
111
|
|
|
112
|
-
Existing consumers of `opencode-delivery@0.1.x` (commit-pinned shim) can run `pnpm dlx opencode-ship@
|
|
112
|
+
Existing consumers of `opencode-delivery@0.1.x` (commit-pinned shim) can run `pnpm dlx opencode-ship@1.1.5 init` from the same checkout. Migration recognises `.opencode/delivery.json`, `.opencode/delivery.lock.json`, the two canonical agents, and the generic plugin `.opencode/plugin/delivery.ts`, and adopts them when their bytes match. Legacy artifacts are preserved on disk so a downgrade remains possible; the installer does NOT modify Leo or any other consumer.
|
|
113
113
|
|
|
114
114
|
`diff` is now strictly read-only; it reports every change but never writes to disk, even when the migration phase has a candidate seed-config to plant.
|
|
115
115
|
|
|
116
116
|
## Lifecycle
|
|
117
117
|
|
|
118
|
-
1. Begin a Build task: the delivery plugin immediately runs any queued post-merge cleanups. Failed cleanups are recorded
|
|
118
|
+
1. Begin a Build task: the delivery plugin immediately runs any queued post-merge cleanups. Failed cleanups are recorded under `<git-common-dir>/opencode-ship/cleanup-pending.json` and retried at the next delivery task or plugin startup.
|
|
119
119
|
2. Optional Deep Research checkpoint for non-trivial plans.
|
|
120
120
|
3. Find or create exactly one issue per PR.
|
|
121
121
|
4. Discover the default branch and fetch it.
|
|
@@ -130,7 +130,7 @@ Existing consumers of `opencode-delivery@0.1.x` (commit-pinned shim) can run `pn
|
|
|
130
130
|
13. Push and wait for required remote CI checks.
|
|
131
131
|
14. Mark the PR Ready and stop.
|
|
132
132
|
15. Explicit "merge it" re-runs the freshness checks and performs the squash merge.
|
|
133
|
-
16. The plugin immediately invokes `delivery_cleanup`; failures
|
|
133
|
+
16. The plugin immediately invokes `delivery_cleanup`; failures persist their next cleanup stage in the Git common dir for the next session.
|
|
134
134
|
|
|
135
135
|
## Exit codes
|
|
136
136
|
|
|
@@ -158,7 +158,7 @@ The shipped artifact is built by esbuild (`scripts/build.mjs`); self-contained `
|
|
|
158
158
|
## Status and licensing
|
|
159
159
|
|
|
160
160
|
- **License:** MIT. See `LICENSE`.
|
|
161
|
-
- **Versioning:** SemVer. v0.2.0 is the first npm-distributed release. v0.3.0 is the installer foundation. v0.4.0 adds the profile-aware installer foundation (`--profile` flag, lock schema v2, profile precedence) that issue #18 requires. v0.5.0 ships the engineering profile content (triage + grill-with-docs SKILL.md placeholders) required by issue #20. v0.6.0 ships the durable plan artifact + Plan Mode permission integration required by issue #21. v0.7.0 ships the M3 task loop contract (run store, task brief, Spec/Quality verdicts, 3-round breaker, commit binding, compaction context) required by issue #22. v0.8.0 ships the Ready gate contract (parallel Standards/Spec + verifier + CI on one HEAD) required by issue #23. v0.9.0 ships the transition matrix smoke (engineering installs all assets, lock tracks the active profile) required by issue #24. v1.0.0 promotes the dogfooded 0.10.0 to stable. v1.1.0 removes the legacy core profile and ships the one-liner init + setup-ship-workflow skill. v1.1.1
|
|
161
|
+
- **Versioning:** SemVer. v0.2.0 is the first npm-distributed release. v0.3.0 is the installer foundation. v0.4.0 adds the profile-aware installer foundation (`--profile` flag, lock schema v2, profile precedence) that issue #18 requires. v0.5.0 ships the engineering profile content (triage + grill-with-docs SKILL.md placeholders) required by issue #20. v0.6.0 ships the durable plan artifact + Plan Mode permission integration required by issue #21. v0.7.0 ships the M3 task loop contract (run store, task brief, Spec/Quality verdicts, 3-round breaker, commit binding, compaction context) required by issue #22. v0.8.0 ships the Ready gate contract (parallel Standards/Spec + verifier + CI on one HEAD) required by issue #23. v0.9.0 ships the transition matrix smoke (engineering installs all assets, lock tracks the active profile) required by issue #24. v1.0.0 promotes the dogfooded 0.10.0 to stable. v1.1.0 removes the legacy core profile and ships the one-liner init + setup-ship-workflow skill. v1.1.1 began the self-hosting correction line; the immutable `1.1.6` failed before publication and `1.1.7` is the planned unpublished correction (see `docs/release/1.1.6-correction-plan.md`).
|
|
162
162
|
- **Compatibility:** the bundled plugin targets `@opencode-ai/plugin >= 1.15.5 < 2` and OpenCode `>= 1.15.5`.
|
|
163
163
|
|
|
164
164
|
## FAQ
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Third-Party Notices
|
|
2
2
|
|
|
3
|
-
`opencode-ship@1.1.
|
|
3
|
+
`opencode-ship@1.1.7` includes the complete Matt Pocock and Superpowers
|
|
4
4
|
methodology under the MIT license, plus the Ship-owned installer,
|
|
5
5
|
plugin, agents, and skills. The complete immutable pin list is in
|
|
6
6
|
`vendor/sources.json`; the upstream snapshots are under
|
|
@@ -35,32 +35,36 @@ permission:
|
|
|
35
35
|
|
|
36
36
|
# Ship Plan
|
|
37
37
|
|
|
38
|
-
You are the primary planning agent
|
|
38
|
+
You are the primary planning agent. You talk about the product. You never talk about the workflow.
|
|
39
39
|
|
|
40
40
|
## What you do
|
|
41
41
|
|
|
42
|
-
1. Read the user's request and any context
|
|
43
|
-
2. Explore
|
|
44
|
-
3. Ask
|
|
45
|
-
4. Write
|
|
46
|
-
5. Tell the user the file path
|
|
42
|
+
1. Read the user's request and any `@file` context they cite.
|
|
43
|
+
2. Explore with read-only bash (git / ls / cat / gh issue view / gh pr view) and `task explore` / `task general`. Never `cat` secrets. You decide when to dispatch those subagents; never ask the user whether to use them.
|
|
44
|
+
3. Ask product questions only, one per turn via `question`: scope, UX, who it is for, what done looks like. When you must choose, recommend in one sentence.
|
|
45
|
+
4. Write the plan to `.opencode/plans/<filename>.md`. Filename: lowercase, dashes, no spaces, ends in `.md` (e.g. `2026-08-20-customer-final-ux.md`).
|
|
46
|
+
5. Tell the user the file path. Ask one question: whether the plan matches the product they want. Then stop.
|
|
47
47
|
|
|
48
48
|
## What you never do
|
|
49
49
|
|
|
50
|
-
- Never edit
|
|
50
|
+
- Never edit anything outside `.opencode/plans/*.md`.
|
|
51
51
|
- Never run mutating bash (`git commit`, `git push`, `npm install`, `rm`, `pnpm add`, …).
|
|
52
|
-
- Never
|
|
53
|
-
- Never
|
|
52
|
+
- Never ask how to run the work: no GitHub issues vs Task N, no Tab / Build, no subagent vs inline, no `ship-deliver`, no "what next".
|
|
53
|
+
- Never mention permission globs, deny lists, allowlists, or why a path was denied.
|
|
54
|
+
- Never create GitHub issues. A later Build session does that if a PR needs `Closes #N`.
|
|
55
|
+
- Never claim OpenCode's native `plan_exit`. This is a replacement for the read-only native Plan Tab, not the experimental one.
|
|
56
|
+
- Never invoke `task plan` or `task build`. Build runs the plan, in a different session.
|
|
57
|
+
- Never offer to implement.
|
|
54
58
|
|
|
55
59
|
## Style
|
|
56
60
|
|
|
57
|
-
- Write for
|
|
61
|
+
- Write for a later session that will be told `implement this plan <path>`. Be concrete: file paths, function names, the exact commit messages.
|
|
58
62
|
- Prefer small, vertical slices over big-bang rewrites.
|
|
59
|
-
- Quote the source lines you
|
|
60
|
-
-
|
|
63
|
+
- Quote the source lines you will touch.
|
|
64
|
+
- Pick one approach in the file; do not enumerate alternatives.
|
|
61
65
|
|
|
62
66
|
## Hard rules
|
|
63
67
|
|
|
64
|
-
- One question per turn via `question`.
|
|
68
|
+
- One question per turn via `question`. Product only.
|
|
65
69
|
- If a write fails, surface the error verbatim. Do not retry silently.
|
|
66
|
-
-
|
|
70
|
+
- Do not claim a file is written until `bash` confirms it.
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// opencode-ship CLI v1.1.
|
|
2
|
+
// opencode-ship CLI v1.1.7
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __esm = (fn, res) => function __init() {
|
|
@@ -110,7 +110,7 @@ import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
|
110
110
|
var PACKAGE_VERSION, TEMPLATE_SET;
|
|
111
111
|
var init_version = __esm({
|
|
112
112
|
"src/version.js"() {
|
|
113
|
-
PACKAGE_VERSION = "1.1.
|
|
113
|
+
PACKAGE_VERSION = "1.1.7";
|
|
114
114
|
TEMPLATE_SET = `v${PACKAGE_VERSION}`;
|
|
115
115
|
}
|
|
116
116
|
});
|
|
@@ -119,6 +119,7 @@ var init_version = __esm({
|
|
|
119
119
|
var catalog_exports = {};
|
|
120
120
|
__export(catalog_exports, {
|
|
121
121
|
CATALOG: () => CATALOG,
|
|
122
|
+
PACKAGE_ROOT: () => PACKAGE_ROOT,
|
|
122
123
|
PACKAGE_VERSION: () => PACKAGE_VERSION,
|
|
123
124
|
TEMPLATE_SET_ID: () => TEMPLATE_SET_ID,
|
|
124
125
|
filterCatalogByProfile: () => filterCatalogByProfile,
|
|
@@ -205,7 +206,7 @@ function validateCatalog({ catalog = CATALOG } = {}) {
|
|
|
205
206
|
}
|
|
206
207
|
return catalog;
|
|
207
208
|
}
|
|
208
|
-
var TEMPLATE_SET_ID, packageRoot, MATT_SKILLS, SUPER_SKILLS, ENGINEERING_AGENTS, ENGINEERING_COMMANDS, CATALOG, ALLOWED_KINDS;
|
|
209
|
+
var TEMPLATE_SET_ID, packageRoot, PACKAGE_ROOT, MATT_SKILLS, SUPER_SKILLS, ENGINEERING_AGENTS, ENGINEERING_COMMANDS, CATALOG, ALLOWED_KINDS;
|
|
209
210
|
var init_catalog = __esm({
|
|
210
211
|
"src/installer/catalog.js"() {
|
|
211
212
|
init_package_root();
|
|
@@ -213,6 +214,7 @@ var init_catalog = __esm({
|
|
|
213
214
|
init_profile();
|
|
214
215
|
TEMPLATE_SET_ID = TEMPLATE_SET;
|
|
215
216
|
packageRoot = resolvePackageRoot(import.meta.url);
|
|
217
|
+
PACKAGE_ROOT = packageRoot;
|
|
216
218
|
MATT_SKILLS = [
|
|
217
219
|
"engineering-workflow",
|
|
218
220
|
"grilling",
|
|
@@ -1731,7 +1733,7 @@ init_catalog();
|
|
|
1731
1733
|
init_version();
|
|
1732
1734
|
import { existsSync as existsSync13 } from "node:fs";
|
|
1733
1735
|
import { mkdir as mkdir6, readFile as readFile9, rename as rename5, unlink as unlink3, writeFile as writeFile6 } from "node:fs/promises";
|
|
1734
|
-
import { dirname as dirname8, resolve as resolve12 } from "node:path";
|
|
1736
|
+
import { dirname as dirname8, relative as relative2, resolve as resolve12, sep as sep2 } from "node:path";
|
|
1735
1737
|
|
|
1736
1738
|
// src/installer/planner.js
|
|
1737
1739
|
init_catalog();
|
|
@@ -1867,8 +1869,8 @@ var FORBIDDEN_BASH_GLOBS_CONTROLLER = [
|
|
|
1867
1869
|
"rm -rf *",
|
|
1868
1870
|
"rm -rf /*"
|
|
1869
1871
|
];
|
|
1870
|
-
function denyMap(globs) {
|
|
1871
|
-
const out = { "*": "allow" };
|
|
1872
|
+
function denyMap(globs, allowByDefault = true) {
|
|
1873
|
+
const out = allowByDefault ? { "*": "allow" } : {};
|
|
1872
1874
|
for (const g of globs) out[g] = "deny";
|
|
1873
1875
|
return out;
|
|
1874
1876
|
}
|
|
@@ -1893,7 +1895,7 @@ function rootPermissionMatrix() {
|
|
|
1893
1895
|
"delivery-verifier": "allow"
|
|
1894
1896
|
},
|
|
1895
1897
|
bash: {
|
|
1896
|
-
...denyMap(FORBIDDEN_BASH_GLOBS),
|
|
1898
|
+
...denyMap(FORBIDDEN_BASH_GLOBS, false),
|
|
1897
1899
|
"rm *": "ask"
|
|
1898
1900
|
}
|
|
1899
1901
|
},
|
|
@@ -2734,11 +2736,11 @@ function parseTree(text, errors = [], options = ParseOptions.DEFAULT) {
|
|
|
2734
2736
|
onValue({ type: getNodeType(value), offset, length, parent: currentParent, value });
|
|
2735
2737
|
ensurePropertyComplete(offset + length);
|
|
2736
2738
|
},
|
|
2737
|
-
onSeparator: (
|
|
2739
|
+
onSeparator: (sep3, offset, length) => {
|
|
2738
2740
|
if (currentParent.type === "property") {
|
|
2739
|
-
if (
|
|
2741
|
+
if (sep3 === ":") {
|
|
2740
2742
|
currentParent.colonOffset = offset;
|
|
2741
|
-
} else if (
|
|
2743
|
+
} else if (sep3 === ",") {
|
|
2742
2744
|
ensurePropertyComplete(offset);
|
|
2743
2745
|
}
|
|
2744
2746
|
}
|
|
@@ -5249,7 +5251,7 @@ async function assembleLock({ repoRoot, plan, lock, configPlan, rootPlan, profil
|
|
|
5249
5251
|
manager: {
|
|
5250
5252
|
schemaVersion: CURRENT_LOCK_SCHEMA,
|
|
5251
5253
|
name: "opencode-ship",
|
|
5252
|
-
version: "1.1.
|
|
5254
|
+
version: "1.1.7",
|
|
5253
5255
|
templateSet: TEMPLATE_SET_ID,
|
|
5254
5256
|
profile: resolvedProfile,
|
|
5255
5257
|
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -5390,9 +5392,7 @@ function serializePlan(plan) {
|
|
|
5390
5392
|
}
|
|
5391
5393
|
function relativeTemplate(source) {
|
|
5392
5394
|
if (typeof source !== "string") return source;
|
|
5393
|
-
|
|
5394
|
-
if (source.startsWith(prefix)) return source.slice(prefix.length);
|
|
5395
|
-
return source;
|
|
5395
|
+
return relative2(PACKAGE_ROOT, source).split(sep2).join("/");
|
|
5396
5396
|
}
|
|
5397
5397
|
|
|
5398
5398
|
// src/installer/commands/doctor.js
|
package/dist/core.d.ts
CHANGED
|
@@ -1 +1,583 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Public type declarations for the opencode-ship package.
|
|
3
|
+
*
|
|
4
|
+
* The package ships plain JavaScript ESM sources; this declaration
|
|
5
|
+
* file gives TypeScript consumers the surface they need to typecheck
|
|
6
|
+
* `import` statements without falling back to `any`. Every public
|
|
7
|
+
* value export from `src/index.js` is declared here; the consumer
|
|
8
|
+
* fixture under `tests/fixtures/consumer.ts` enforces that.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Primitives
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
export type Sha = string;
|
|
16
|
+
export type Branch = string;
|
|
17
|
+
export type RepoSlug = `${string}/${string}`;
|
|
18
|
+
export type IssueNumber = number;
|
|
19
|
+
export type PullRequestNumber = number;
|
|
20
|
+
|
|
21
|
+
export type ContractVersion = 1;
|
|
22
|
+
|
|
23
|
+
export interface AdapterLock {
|
|
24
|
+
contractVersion: ContractVersion;
|
|
25
|
+
adapterSha256: Sha;
|
|
26
|
+
writtenAt: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Lifecycle
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
export type LifecycleState =
|
|
34
|
+
| "issue-linked"
|
|
35
|
+
| "worktree-created"
|
|
36
|
+
| "draft-open"
|
|
37
|
+
| "validating"
|
|
38
|
+
| "ready"
|
|
39
|
+
| "merged"
|
|
40
|
+
| "cleanup-pending"
|
|
41
|
+
| "cleaned"
|
|
42
|
+
| "failed"
|
|
43
|
+
| "aborted";
|
|
44
|
+
|
|
45
|
+
export interface LifecycleTransition {
|
|
46
|
+
from: LifecycleState;
|
|
47
|
+
to: LifecycleState;
|
|
48
|
+
at: number;
|
|
49
|
+
reason?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface Manifest {
|
|
53
|
+
schemaVersion: 2;
|
|
54
|
+
taskId: string;
|
|
55
|
+
repoIdentity: RepoSlug;
|
|
56
|
+
issueNumber: IssueNumber | null;
|
|
57
|
+
prNumber: PullRequestNumber | null;
|
|
58
|
+
baseBranch: Branch;
|
|
59
|
+
baseSha: Sha;
|
|
60
|
+
branch: Branch;
|
|
61
|
+
worktreePath: string | null;
|
|
62
|
+
lastPrHeadSha: Sha | null;
|
|
63
|
+
lastReviewerSha: Sha | null;
|
|
64
|
+
lastVerifierSha: Sha | null;
|
|
65
|
+
workflowId: string | null;
|
|
66
|
+
owner: string;
|
|
67
|
+
state: LifecycleState;
|
|
68
|
+
transitionLog: LifecycleTransition[];
|
|
69
|
+
fatalReason?: string;
|
|
70
|
+
createdAt: string;
|
|
71
|
+
updatedAt: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface CreateManifestInput {
|
|
75
|
+
taskId: string;
|
|
76
|
+
repoIdentity: RepoSlug;
|
|
77
|
+
issueNumber: IssueNumber | null;
|
|
78
|
+
baseBranch: Branch;
|
|
79
|
+
baseSha: Sha;
|
|
80
|
+
branch: Branch;
|
|
81
|
+
owner: string;
|
|
82
|
+
prNumber?: PullRequestNumber | null;
|
|
83
|
+
lastPrHeadSha?: Sha | null;
|
|
84
|
+
workflowId?: string | null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface IssueSummary {
|
|
88
|
+
number: IssueNumber;
|
|
89
|
+
url: string;
|
|
90
|
+
state: "OPEN" | "CLOSED";
|
|
91
|
+
pullRequest: PullRequestSummary | null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface PullRequestSummary {
|
|
95
|
+
number: PullRequestNumber;
|
|
96
|
+
url: string;
|
|
97
|
+
baseRefName: Branch;
|
|
98
|
+
headRefName: Branch;
|
|
99
|
+
headSha: Sha;
|
|
100
|
+
draft: boolean;
|
|
101
|
+
mergeable: "MERGEABLE" | "CONFLICTING" | "UNKNOWN";
|
|
102
|
+
mergeStateStatus: string;
|
|
103
|
+
merged: boolean;
|
|
104
|
+
mergedAt: string | null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface CheckSummary {
|
|
108
|
+
name: string;
|
|
109
|
+
state: "success" | "failure" | "pending" | "neutral" | "skipped" | "cancelled" | "timed_out" | "action_required" | "stale" | "queued" | "in_progress" | "requested";
|
|
110
|
+
bucket: "pass" | "fail" | "pending" | "skip" | "neutral";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface EnsureIssueResult {
|
|
114
|
+
summary: IssueSummary;
|
|
115
|
+
created: boolean;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface GithubDriver {
|
|
119
|
+
ensureIssue(args: { repo: RepoSlug; title: string; body: string; labels: readonly string[] }): Promise<EnsureIssueResult>;
|
|
120
|
+
openDraftPullRequest(args: { repo: RepoSlug; head: Branch; base: Branch; title: string; body: string; issueNumber: IssueNumber }): Promise<PullRequestSummary>;
|
|
121
|
+
updatePullRequestBody(args: { repo: RepoSlug; number: PullRequestNumber; body: string }): Promise<void>;
|
|
122
|
+
markReady(args: { repo: RepoSlug; number: PullRequestNumber }): Promise<void>;
|
|
123
|
+
mergePullRequest(args: { repo: RepoSlug; number: PullRequestNumber; subject: string }): Promise<PullRequestSummary>;
|
|
124
|
+
readPullRequest(args: { repo: RepoSlug; number: PullRequestNumber }): Promise<PullRequestSummary>;
|
|
125
|
+
readChecks(args: { repo: RepoSlug; sha?: Sha; number?: PullRequestNumber; branch?: Branch; required: readonly string[] }): Promise<CheckSummary[]>;
|
|
126
|
+
comment(args: { repo: RepoSlug; number: PullRequestNumber; body: string }): Promise<void>;
|
|
127
|
+
refreshHead(args: { repo: RepoSlug; number: PullRequestNumber }): Promise<Sha>;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface CreateGhDriverOptions {
|
|
131
|
+
runner?: (args: readonly string[]) => Promise<{ status: number; stdout: string; stderr: string }>;
|
|
132
|
+
cwd?: string;
|
|
133
|
+
env?: NodeJS.ProcessEnv;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export type GhRunner = (args: readonly string[]) => Promise<{ status: number; stdout: string; stderr: string }>;
|
|
137
|
+
|
|
138
|
+
export interface GhStubHandle {
|
|
139
|
+
driver: GithubDriver;
|
|
140
|
+
queue: Array<{ match: (args: readonly string[]) => boolean; status?: number; stdout?: string; stderr?: string }>;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface RepoRef {
|
|
144
|
+
owner: string;
|
|
145
|
+
name: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// Adapter
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
export interface Adapter {
|
|
153
|
+
contractVersion: 1;
|
|
154
|
+
repository: {
|
|
155
|
+
remote: string;
|
|
156
|
+
defaultBranch?: { discover?: boolean; name?: Branch };
|
|
157
|
+
};
|
|
158
|
+
forge?: {
|
|
159
|
+
driver: "github";
|
|
160
|
+
issueRequired?: boolean;
|
|
161
|
+
draftAfterFirstCommit?: boolean;
|
|
162
|
+
issueClosingSyntax?: boolean;
|
|
163
|
+
};
|
|
164
|
+
worktree?: {
|
|
165
|
+
root: string;
|
|
166
|
+
branchTemplate: string;
|
|
167
|
+
bootstrap?: readonly (readonly string[])[];
|
|
168
|
+
};
|
|
169
|
+
verification?: {
|
|
170
|
+
commands: readonly { id: string; argv: readonly string[]; timeoutMs?: number }[];
|
|
171
|
+
requireCleanDiffAfter?: boolean;
|
|
172
|
+
invalidateOnHeadChange?: boolean;
|
|
173
|
+
};
|
|
174
|
+
review?: {
|
|
175
|
+
agent: string;
|
|
176
|
+
required: boolean;
|
|
177
|
+
invalidateOnHeadChange: boolean;
|
|
178
|
+
};
|
|
179
|
+
ci?: {
|
|
180
|
+
driver: "github-status-checks";
|
|
181
|
+
requiredChecks: readonly string[];
|
|
182
|
+
wait: boolean;
|
|
183
|
+
flakyRetry: 0 | 1;
|
|
184
|
+
};
|
|
185
|
+
ready?: {
|
|
186
|
+
requires: readonly ("review" | "local-verification" | "remote-ci")[];
|
|
187
|
+
stopAfterReady: boolean;
|
|
188
|
+
};
|
|
189
|
+
merge?: {
|
|
190
|
+
strategy: "squash";
|
|
191
|
+
policy: "explicit-user-request-only";
|
|
192
|
+
requireFreshGates: boolean;
|
|
193
|
+
};
|
|
194
|
+
cleanup?: {
|
|
195
|
+
when: "next-task";
|
|
196
|
+
requires: readonly ("pr-merged" | "worktree-clean" | "no-unpublished-commits")[];
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export type AdapterLoadResult =
|
|
201
|
+
| { ok: true; adapter: Adapter; path: string; sha256: Sha }
|
|
202
|
+
| { ok: false; error: { kind: "missing" | "parse" | "contract"; path: string; message?: string; issues?: readonly string[] } };
|
|
203
|
+
|
|
204
|
+
export interface DoctorCheck {
|
|
205
|
+
name: string;
|
|
206
|
+
ok: boolean;
|
|
207
|
+
detail: string;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export interface DoctorReport {
|
|
211
|
+
contractVersion: 1;
|
|
212
|
+
adapterPath: string | null;
|
|
213
|
+
adapterSha256: Sha | null;
|
|
214
|
+
lockPath: string | null;
|
|
215
|
+
lockSha256: Sha | null;
|
|
216
|
+
packageVersion: string | null;
|
|
217
|
+
nodeVersion: string;
|
|
218
|
+
ghVersion: string | null;
|
|
219
|
+
gitVersion: string | null;
|
|
220
|
+
checks: DoctorCheck[];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export type TransitionResult =
|
|
224
|
+
| { ok: true; from: LifecycleState; to: LifecycleState; at: number; reason?: string }
|
|
225
|
+
| { ok: false; from: LifecycleState; attempted: LifecycleState; reason: string };
|
|
226
|
+
|
|
227
|
+
export interface GateSnapshot {
|
|
228
|
+
prHead: Sha | null;
|
|
229
|
+
reviewerSha: Sha | null;
|
|
230
|
+
verifierSha: Sha | null;
|
|
231
|
+
checks: readonly CheckSummary[];
|
|
232
|
+
missingChecks: readonly string[];
|
|
233
|
+
failingChecks: readonly string[];
|
|
234
|
+
pendingChecks: readonly string[];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export interface GateCheck {
|
|
238
|
+
ok: boolean;
|
|
239
|
+
reason?: string;
|
|
240
|
+
snapshot: GateSnapshot;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export interface RecoveryReport {
|
|
244
|
+
total: number;
|
|
245
|
+
pendingCleanup: number;
|
|
246
|
+
orphanWorktrees: number;
|
|
247
|
+
cleaned: number;
|
|
248
|
+
notes: string[];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export interface WorktreeRecordShape {
|
|
252
|
+
path: string;
|
|
253
|
+
branch: string;
|
|
254
|
+
head: Sha;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export type RunCommandResult =
|
|
258
|
+
| { ok: true; status: number; stdout: string; stderr: string }
|
|
259
|
+
| { ok: false; error: Error };
|
|
260
|
+
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
// Tool factory deps shapes
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
|
|
265
|
+
export interface BaseToolDeps {
|
|
266
|
+
driver: GithubDriver;
|
|
267
|
+
repoRoot: string;
|
|
268
|
+
repoSlug: RepoSlug;
|
|
269
|
+
owner: string;
|
|
270
|
+
adapter: Adapter;
|
|
271
|
+
remote?: string;
|
|
272
|
+
packageVersion?: string;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export interface InspectToolDeps {
|
|
276
|
+
driver?: GithubDriver;
|
|
277
|
+
repoRoot: string;
|
|
278
|
+
repoSlug: RepoSlug;
|
|
279
|
+
owner?: string;
|
|
280
|
+
adapter?: Adapter | null;
|
|
281
|
+
packageVersion: string;
|
|
282
|
+
remote?: string;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export interface IssueToolInput {
|
|
286
|
+
taskId: string;
|
|
287
|
+
title: string;
|
|
288
|
+
body?: string;
|
|
289
|
+
baseBranch: string;
|
|
290
|
+
baseSha?: string;
|
|
291
|
+
branch: string;
|
|
292
|
+
labels?: readonly string[];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export interface WorktreeToolInput {
|
|
296
|
+
taskId: string;
|
|
297
|
+
branch: string;
|
|
298
|
+
worktreeRelativePath: string;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export interface VerifyToolInput {
|
|
302
|
+
taskId: string;
|
|
303
|
+
commandId?: string;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export interface ReviewToolInput {
|
|
307
|
+
taskId: string;
|
|
308
|
+
status: "pass" | "fail" | "blocked" | "partial";
|
|
309
|
+
headSha?: Sha;
|
|
310
|
+
findings?: unknown;
|
|
311
|
+
envelope?: unknown;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export interface PrToolInput {
|
|
315
|
+
taskId: string;
|
|
316
|
+
title: string;
|
|
317
|
+
body: string;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export interface ReadyToolInput {
|
|
321
|
+
taskId: string;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export interface MergeToolInput {
|
|
325
|
+
taskId: string;
|
|
326
|
+
subject: string;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export interface CleanupToolInput {
|
|
330
|
+
taskId: string;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export type Envelope<TKind extends string, TData> = { kind: TKind } & TData;
|
|
334
|
+
|
|
335
|
+
// ---------------------------------------------------------------------------
|
|
336
|
+
// Envelope shapes (mirror the runtime tool returns)
|
|
337
|
+
// ---------------------------------------------------------------------------
|
|
338
|
+
|
|
339
|
+
export type IssueEnvelope = Envelope<
|
|
340
|
+
"issue",
|
|
341
|
+
{
|
|
342
|
+
contractVersion: 1;
|
|
343
|
+
created: boolean;
|
|
344
|
+
issueNumber: IssueNumber;
|
|
345
|
+
issueUrl: string;
|
|
346
|
+
manifestPath: string;
|
|
347
|
+
}
|
|
348
|
+
>;
|
|
349
|
+
|
|
350
|
+
export type MissingManifestEnvelope = Envelope<"missing-manifest", { taskId: string }>;
|
|
351
|
+
export type ManifestStateEnvelope = Envelope<"manifest-state", { state: LifecycleState }>;
|
|
352
|
+
export type MissingWorktreePathEnvelope = Envelope<"missing-worktree-path", Record<string, never>>;
|
|
353
|
+
export type MissingPrEnvelope = Envelope<"missing-pr", Record<string, never>>;
|
|
354
|
+
export type WorktreeExistsEnvelope = Envelope<"worktree-exists", Record<string, never>>;
|
|
355
|
+
export type BranchExistsLocallyEnvelope = Envelope<"branch-exists-locally", { branch: Branch }>;
|
|
356
|
+
export type BranchExistsRemotelyEnvelope = Envelope<"branch-exists-remotely", { branch: Branch }>;
|
|
357
|
+
export type RemoteFetchEnvelope = Envelope<"remote-fetch", { stderr: string }>;
|
|
358
|
+
export type CreateFailedEnvelope = Envelope<"create-failed", { stderr: string }>;
|
|
359
|
+
export type HeadChangedAfterVerifierEnvelope = Envelope<"head-changed-after-verifier", { headSha: Sha; verifierSha: Sha }>;
|
|
360
|
+
export type HeadChangedAfterReviewEnvelope = Envelope<"head-changed-after-review", { headSha: Sha; reviewSha: Sha }>;
|
|
361
|
+
export type MissingGateEnvelope = Envelope<"missing-gate", { gate: "review" | "local-verification" | "remote-ci" }>;
|
|
362
|
+
export type CiFailingEnvelope = Envelope<"ci-failing", { failing: readonly string[] }>;
|
|
363
|
+
export type CiPendingEnvelope = Envelope<"ci-pending", { pending: readonly string[] }>;
|
|
364
|
+
export type NotReadyEnvelope = Envelope<"not-ready", { state: LifecycleState }>;
|
|
365
|
+
export type WrongBaseEnvelope = Envelope<"wrong-base", { base: Branch }>;
|
|
366
|
+
export type HeadChangedEnvelope = Envelope<"head-changed", { headSha: Sha; manifestSha: Sha }>;
|
|
367
|
+
export type NotMergeableEnvelope = Envelope<"not-mergeable", { reason: string }>;
|
|
368
|
+
export type DirtyWorktreeEnvelope = Envelope<"dirty-worktree", Record<string, never>>;
|
|
369
|
+
export type RebaseInProgressEnvelope = Envelope<"rebase-in-progress", Record<string, never>>;
|
|
370
|
+
export type HeadMismatchEnvelope = Envelope<"head-mismatch", { headSha: Sha; manifestSha: Sha }>;
|
|
371
|
+
export type CurrentCheckoutEnvelope = Envelope<"current-checkout", { worktreePath: string }>;
|
|
372
|
+
export type UnmergedEnvelope = Envelope<"unmerged", { headSha: Sha; manifestSha: Sha }>;
|
|
373
|
+
export type BaseMismatchEnvelope = Envelope<"base-mismatch", { manifestBase: Branch; prBase: Branch }>;
|
|
374
|
+
export type HasUnpublishedCommitsEnvelope = Envelope<"has-unpublished-commits", { ahead: number }>;
|
|
375
|
+
export type RemoveFailedEnvelope = Envelope<"remove-failed", { stderr: string }>;
|
|
376
|
+
export type UnsafeCleanupEnvelope = Envelope<"unsafe-cleanup", { signals: readonly string[] }>;
|
|
377
|
+
export type VerifyFailedEnvelope = Envelope<"verify-failed", { commandId: string; status: number; headSha: Sha | null }>;
|
|
378
|
+
export type PathEscapeEnvelope = Envelope<"path-escape", { resolvedPath: string }>;
|
|
379
|
+
export type BootstrapFailedEnvelope = Envelope<"bootstrap-failed", { stderr: string; argv: readonly string[] }>;
|
|
380
|
+
|
|
381
|
+
// ---------------------------------------------------------------------------
|
|
382
|
+
// Value export declarations. The runtime values live in plain JS modules;
|
|
383
|
+
// these declarations are the contract that consumers rely on.
|
|
384
|
+
// ---------------------------------------------------------------------------
|
|
385
|
+
|
|
386
|
+
export declare const ADAPTER_CONTRACT_VERSION: ContractVersion;
|
|
387
|
+
export declare const ADAPTER_FILENAME: string;
|
|
388
|
+
export declare const LOCK_FILENAME: string;
|
|
389
|
+
export declare const PACKAGE_VERSION: string;
|
|
390
|
+
export declare const STATES: readonly LifecycleState[];
|
|
391
|
+
|
|
392
|
+
export declare const WorktreeRecord: WorktreeRecordShape;
|
|
393
|
+
|
|
394
|
+
export declare function validateAdapter(value: unknown): {
|
|
395
|
+
ok: boolean;
|
|
396
|
+
issues?: readonly string[];
|
|
397
|
+
adapter?: Adapter;
|
|
398
|
+
};
|
|
399
|
+
export declare function loadAdapter(repoRoot: string): Promise<AdapterLoadResult>;
|
|
400
|
+
export declare function writeLock(repoRoot: string, adapterSha256: Sha): Promise<string>;
|
|
401
|
+
export declare function readLock(repoRoot: string): Promise<AdapterLock | null>;
|
|
402
|
+
export declare function findOpencodeDir(start: string): string | null;
|
|
403
|
+
|
|
404
|
+
export declare function createManifest(input: CreateManifestInput): Manifest;
|
|
405
|
+
export declare function transition(
|
|
406
|
+
manifest: Manifest,
|
|
407
|
+
to: LifecycleState,
|
|
408
|
+
opts?: { reason?: string; now?: () => Date },
|
|
409
|
+
): TransitionResult;
|
|
410
|
+
export declare function canTransition(from: LifecycleState, to: LifecycleState): boolean;
|
|
411
|
+
export declare function isTerminal(state: LifecycleState): boolean;
|
|
412
|
+
export declare function mustRerunReview(previousSha: Sha | null, currentSha: Sha): boolean;
|
|
413
|
+
export declare function mustRerunVerifier(previousSha: Sha | null, currentSha: Sha): boolean;
|
|
414
|
+
|
|
415
|
+
export declare function writeManifest(repoRoot: string, manifest: Manifest): Promise<string>;
|
|
416
|
+
export declare function readManifest(repoRoot: string, taskId: string): Promise<Manifest | null>;
|
|
417
|
+
export declare function listManifests(repoRoot: string): Promise<Manifest[]>;
|
|
418
|
+
export declare function deleteManifest(repoRoot: string, taskId: string): Promise<void>;
|
|
419
|
+
|
|
420
|
+
export declare function isInsideWorktree(cwd: string): boolean;
|
|
421
|
+
export declare function isMainCheckout(cwd: string): boolean;
|
|
422
|
+
export declare function listWorktrees(cwd: string): WorktreeRecordShape[];
|
|
423
|
+
export declare function isWorktreeClean(cwd: string): boolean;
|
|
424
|
+
export declare function isRebaseInProgress(cwd: string): boolean;
|
|
425
|
+
export declare function currentBranch(cwd: string): Branch | null;
|
|
426
|
+
export declare function revParse(ref: string, cwd: string): Sha | null;
|
|
427
|
+
export declare function fetchBranch(remote: string, branch: Branch, cwd: string): { status: number; stderr: string };
|
|
428
|
+
export declare function remoteExists(remote: string, cwd: string): boolean;
|
|
429
|
+
export declare function createWorktree(opts: { cwd: string; branch: Branch; worktreePath: string; base: string }): { status: number; stderr: string };
|
|
430
|
+
export declare function createWorktreeFromLocal(opts: { cwd: string; branch: Branch; worktreePath: string; base: string }): { status: number; stderr: string };
|
|
431
|
+
export declare function worktreeExists(cwd: string, path: string): boolean;
|
|
432
|
+
export declare function branchExistsLocally(branch: Branch, cwd: string): boolean;
|
|
433
|
+
export declare function branchExistsRemotely(remote: string, branch: Branch, cwd: string): boolean;
|
|
434
|
+
export declare function mergeIntoFeature(branch: Branch, base: Branch, cwd: string): { status: number; stderr: string };
|
|
435
|
+
export declare function currentHead(cwd: string): Sha | null;
|
|
436
|
+
export declare function push(remote: string, branch: Branch, cwd: string): { status: number; stderr: string };
|
|
437
|
+
export declare function pushForceDisabled(remote: string, branch: Branch, cwd: string): { status: number; stderr: string };
|
|
438
|
+
export declare function defaultBranch(cwd: string): Branch | null;
|
|
439
|
+
export declare function mergeBaseRemoteHead(remote: string, branch: Branch, cwd: string): Sha | null;
|
|
440
|
+
|
|
441
|
+
export declare function createGhDriver(opts?: CreateGhDriverOptions): GithubDriver;
|
|
442
|
+
export declare function createGhStub(
|
|
443
|
+
responses: Array<{
|
|
444
|
+
match: (args: readonly string[]) => boolean;
|
|
445
|
+
status?: number;
|
|
446
|
+
stdout?: string;
|
|
447
|
+
stderr?: string;
|
|
448
|
+
}>,
|
|
449
|
+
): GhStubHandle;
|
|
450
|
+
export declare function parseRepoSlug(slug: string): RepoRef | null;
|
|
451
|
+
|
|
452
|
+
export declare function scanRecovery(repoRoot: string): Promise<RecoveryReport>;
|
|
453
|
+
export declare function wouldCleanupBeSafe(args: {
|
|
454
|
+
prMerged: boolean;
|
|
455
|
+
worktreeClean: boolean;
|
|
456
|
+
rebaseInProgress: boolean;
|
|
457
|
+
headMatchesPr: boolean;
|
|
458
|
+
baseMatches: boolean;
|
|
459
|
+
}): boolean;
|
|
460
|
+
export declare function removeManifestIfSafe(repoRoot: string, taskId: string): Promise<boolean>;
|
|
461
|
+
export declare function recoverManifestAfterCrash(manifest: Manifest): Manifest;
|
|
462
|
+
|
|
463
|
+
export declare function doctor(repoRoot: string, packageVersion: string | null, adapter?: Adapter | null): Promise<DoctorReport>;
|
|
464
|
+
|
|
465
|
+
export declare function bucketFor(check: CheckSummary | undefined | null): "pass" | "fail" | "pending" | "skip" | "neutral";
|
|
466
|
+
export declare function gateSnapshot(args: {
|
|
467
|
+
manifest: { adapter?: Adapter | null; lastReviewerSha?: Sha | null; lastVerifierSha?: Sha | null };
|
|
468
|
+
prHead: Sha | null;
|
|
469
|
+
checks: readonly CheckSummary[];
|
|
470
|
+
}): GateSnapshot;
|
|
471
|
+
export declare function checkGates(args: {
|
|
472
|
+
manifest: { adapter?: Adapter | null; lastReviewerSha?: Sha | null; lastVerifierSha?: Sha | null };
|
|
473
|
+
prHead: Sha | null;
|
|
474
|
+
checks: readonly CheckSummary[];
|
|
475
|
+
requires: readonly ("review" | "local-verification" | "remote-ci")[];
|
|
476
|
+
}): GateCheck;
|
|
477
|
+
export declare function gateFailureEnvelope(result: { reason: string; snapshot: GateSnapshot }):
|
|
478
|
+
| MissingGateEnvelope
|
|
479
|
+
| HeadChangedAfterReviewEnvelope
|
|
480
|
+
| HeadChangedAfterVerifierEnvelope
|
|
481
|
+
| Envelope<"ci-missing", { missing: readonly string[] }>
|
|
482
|
+
| Envelope<"ci-failing", { failing: readonly string[] }>
|
|
483
|
+
| Envelope<"ci-pending", { pending: readonly string[] }>
|
|
484
|
+
| Envelope<"gate-failed", { reason: string }>;
|
|
485
|
+
|
|
486
|
+
export declare function createInspectTool(deps: InspectToolDeps): (input: { taskId: string }) => Promise<{
|
|
487
|
+
contractVersion: 1;
|
|
488
|
+
manifest: Manifest | null;
|
|
489
|
+
doctor: DoctorReport;
|
|
490
|
+
}>;
|
|
491
|
+
|
|
492
|
+
export declare function createIssueTool(deps: BaseToolDeps): (input: IssueToolInput) => Promise<IssueEnvelope | Envelope<"missing-input", { field: string }> | Envelope<"lifecycle", { reason: string }>>;
|
|
493
|
+
|
|
494
|
+
export declare function createWorktreeTool(deps: BaseToolDeps): (input: WorktreeToolInput) => Promise<
|
|
495
|
+
| Envelope<"worktree", { contractVersion: 1; branch: Branch; worktreePath: string; headSha: Sha; manifestPath: string }>
|
|
496
|
+
| MissingManifestEnvelope
|
|
497
|
+
| ManifestStateEnvelope
|
|
498
|
+
| Envelope<"missing-input", { field: string }>
|
|
499
|
+
| RemoteFetchEnvelope
|
|
500
|
+
| BranchExistsLocallyEnvelope
|
|
501
|
+
| BranchExistsRemotelyEnvelope
|
|
502
|
+
| WorktreeExistsEnvelope
|
|
503
|
+
| CreateFailedEnvelope
|
|
504
|
+
| Envelope<"missing-base-sha", Record<string, never>>
|
|
505
|
+
| Envelope<"bootstrap-invalid", { bootstrap: unknown }>
|
|
506
|
+
| BootstrapFailedEnvelope
|
|
507
|
+
| PathEscapeEnvelope
|
|
508
|
+
| Envelope<"lifecycle", { reason: string }>
|
|
509
|
+
>;
|
|
510
|
+
|
|
511
|
+
export declare function createVerifyTool(deps: BaseToolDeps): (input: VerifyToolInput) => Promise<
|
|
512
|
+
| Envelope<"verify", { contractVersion: 1; commandId: string; status: 0; stdoutTail: string; stderrTail: string; headSha: Sha; manifestPath: string }>
|
|
513
|
+
| MissingManifestEnvelope
|
|
514
|
+
| ManifestStateEnvelope
|
|
515
|
+
| Envelope<"no-commands", Record<string, never>>
|
|
516
|
+
| Envelope<"command-not-found", { commandId: string }>
|
|
517
|
+
| Envelope<"worktree-dirty", Record<string, never>>
|
|
518
|
+
| Envelope<"no-head", Record<string, never>>
|
|
519
|
+
| VerifyFailedEnvelope
|
|
520
|
+
| Envelope<"lifecycle", { reason: string }>
|
|
521
|
+
>;
|
|
522
|
+
|
|
523
|
+
export declare function createReviewTool(deps: BaseToolDeps): (input: ReviewToolInput) => Promise<
|
|
524
|
+
| Envelope<"review", { contractVersion: 1; pr: PullRequestNumber; reviewerSha: Sha; manifestPath: string }>
|
|
525
|
+
| MissingManifestEnvelope
|
|
526
|
+
| MissingPrEnvelope
|
|
527
|
+
| ManifestStateEnvelope
|
|
528
|
+
| Envelope<"review-not-pass", { status: string; headSha: Sha; recordedReviewerSha: Sha | null }>
|
|
529
|
+
| HeadMismatchEnvelope
|
|
530
|
+
>;
|
|
531
|
+
|
|
532
|
+
export declare function createPrTool(deps: BaseToolDeps): (input: PrToolInput) => Promise<
|
|
533
|
+
| Envelope<"pr", { contractVersion: 1; pr: PullRequestSummary; manifestPath: string }>
|
|
534
|
+
| MissingManifestEnvelope
|
|
535
|
+
| ManifestStateEnvelope
|
|
536
|
+
| Envelope<"lifecycle", { reason: string }>
|
|
537
|
+
>;
|
|
538
|
+
|
|
539
|
+
export declare function createReadyTool(deps: BaseToolDeps): (input: ReadyToolInput) => Promise<
|
|
540
|
+
| Envelope<"ready", { contractVersion: 1; manifestPath: string; pr: PullRequestNumber }>
|
|
541
|
+
| MissingManifestEnvelope
|
|
542
|
+
| MissingPrEnvelope
|
|
543
|
+
| MissingGateEnvelope
|
|
544
|
+
| HeadChangedAfterReviewEnvelope
|
|
545
|
+
| HeadChangedAfterVerifierEnvelope
|
|
546
|
+
| Envelope<"ci-missing", { missing: readonly string[] }>
|
|
547
|
+
| Envelope<"ci-failing", { failing: readonly string[] }>
|
|
548
|
+
| Envelope<"ci-pending", { pending: readonly string[] }>
|
|
549
|
+
>;
|
|
550
|
+
|
|
551
|
+
export declare function createMergeTool(deps: BaseToolDeps): (input: MergeToolInput) => Promise<
|
|
552
|
+
| Envelope<"merge", { contractVersion: 1; manifestPath: string; pr: PullRequestNumber }>
|
|
553
|
+
| MissingManifestEnvelope
|
|
554
|
+
| MissingPrEnvelope
|
|
555
|
+
| NotReadyEnvelope
|
|
556
|
+
| WrongBaseEnvelope
|
|
557
|
+
| HeadChangedEnvelope
|
|
558
|
+
| NotMergeableEnvelope
|
|
559
|
+
| MissingGateEnvelope
|
|
560
|
+
| HeadChangedAfterReviewEnvelope
|
|
561
|
+
| HeadChangedAfterVerifierEnvelope
|
|
562
|
+
| Envelope<"ci-missing", { missing: readonly string[] }>
|
|
563
|
+
| Envelope<"ci-failing", { failing: readonly string[] }>
|
|
564
|
+
| Envelope<"ci-pending", { pending: readonly string[] }>
|
|
565
|
+
>;
|
|
566
|
+
|
|
567
|
+
export declare function createCleanupTool(deps: BaseToolDeps): (input: CleanupToolInput) => Promise<
|
|
568
|
+
| Envelope<"cleanup", { contractVersion: 1; manifestPath: string | null; removedPath: string }>
|
|
569
|
+
| MissingManifestEnvelope
|
|
570
|
+
| ManifestStateEnvelope
|
|
571
|
+
| MissingWorktreePathEnvelope
|
|
572
|
+
| MissingPrEnvelope
|
|
573
|
+
| CurrentCheckoutEnvelope
|
|
574
|
+
| DirtyWorktreeEnvelope
|
|
575
|
+
| RebaseInProgressEnvelope
|
|
576
|
+
| HeadMismatchEnvelope
|
|
577
|
+
| UnmergedEnvelope
|
|
578
|
+
| BaseMismatchEnvelope
|
|
579
|
+
| HasUnpublishedCommitsEnvelope
|
|
580
|
+
| RemoveFailedEnvelope
|
|
581
|
+
| Envelope<"branch-delete-failed", { stderr: string }>
|
|
582
|
+
| Envelope<"unsafe-cleanup", { signals: readonly string[] }>
|
|
583
|
+
>;
|
package/dist/core.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// opencode-ship/core v1.1.
|
|
1
|
+
// opencode-ship/core v1.1.7
|
|
2
2
|
|
|
3
3
|
// src/adapter.js
|
|
4
4
|
import { readFile, writeFile, mkdir, rename } from "node:fs/promises";
|
|
@@ -851,7 +851,7 @@ function validateGhArgv(argv) {
|
|
|
851
851
|
|
|
852
852
|
// src/drivers/gh-cli.js
|
|
853
853
|
function defaultRunner(cwd, env) {
|
|
854
|
-
return (args) => new Promise((
|
|
854
|
+
return (args) => new Promise((resolve10, reject) => {
|
|
855
855
|
const proc = spawn2("gh", args, {
|
|
856
856
|
cwd,
|
|
857
857
|
env,
|
|
@@ -863,7 +863,7 @@ function defaultRunner(cwd, env) {
|
|
|
863
863
|
proc.stdout.on("data", (d) => stdout += d.toString());
|
|
864
864
|
proc.stderr.on("data", (d) => stderr += d.toString());
|
|
865
865
|
proc.on("error", reject);
|
|
866
|
-
proc.on("close", (status) =>
|
|
866
|
+
proc.on("close", (status) => resolve10({ status: status ?? -1, stdout, stderr }));
|
|
867
867
|
});
|
|
868
868
|
}
|
|
869
869
|
function viewFields() {
|
|
@@ -2527,6 +2527,42 @@ function createMergeTool(deps) {
|
|
|
2527
2527
|
} else if (m.schemaVersion >= 2) {
|
|
2528
2528
|
return { kind: "missing-workflow-link", taskId: m.taskId };
|
|
2529
2529
|
}
|
|
2530
|
+
if (pr.headSha !== (m.lastPrHeadSha ?? pr.headSha)) {
|
|
2531
|
+
return {
|
|
2532
|
+
kind: "head-changed",
|
|
2533
|
+
headSha: pr.headSha,
|
|
2534
|
+
manifestSha: m.lastPrHeadSha ?? ""
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
async function recordMerged(merged2, reason) {
|
|
2538
|
+
const t = transition(
|
|
2539
|
+
{ ...m, lastPrHeadSha: merged2.headSha },
|
|
2540
|
+
"merged",
|
|
2541
|
+
{ reason }
|
|
2542
|
+
);
|
|
2543
|
+
if (!t.ok) return { kind: "lifecycle", reason: t.reason };
|
|
2544
|
+
const next = {
|
|
2545
|
+
...m,
|
|
2546
|
+
lastPrHeadSha: merged2.headSha,
|
|
2547
|
+
state: t.to,
|
|
2548
|
+
transitionLog: [
|
|
2549
|
+
...m.transitionLog,
|
|
2550
|
+
{ from: t.from, to: t.to, at: t.at, reason: t.reason }
|
|
2551
|
+
],
|
|
2552
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2553
|
+
};
|
|
2554
|
+
const path = await writeManifest(deps.repoRoot, next);
|
|
2555
|
+
if (finalEvidence && m.workflowId) {
|
|
2556
|
+
await appendRunEvent(deps.repoRoot, m.workflowId, finalEvidence.runState, {
|
|
2557
|
+
kind: RUN_EVENT_KINDS.MERGE,
|
|
2558
|
+
data: { mergeSha: merged2.mergeSha ?? merged2.mergeCommitSha ?? merged2.headSha }
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
return { kind: "merge", contractVersion: 1, manifestPath: path, pr: m.prNumber, taskId: m.taskId };
|
|
2562
|
+
}
|
|
2563
|
+
if (pr.merged) {
|
|
2564
|
+
return recordMerged(pr, `reconciled external merge as ${input.subject}`);
|
|
2565
|
+
}
|
|
2530
2566
|
if (freshGates) {
|
|
2531
2567
|
const required = deps.adapter?.ready?.requires ?? [
|
|
2532
2568
|
"review",
|
|
@@ -2553,13 +2589,6 @@ function createMergeTool(deps) {
|
|
|
2553
2589
|
});
|
|
2554
2590
|
if (!result.ok) return gateFailureEnvelope(result);
|
|
2555
2591
|
}
|
|
2556
|
-
if (pr.headSha !== (m.lastPrHeadSha ?? pr.headSha)) {
|
|
2557
|
-
return {
|
|
2558
|
-
kind: "head-changed",
|
|
2559
|
-
headSha: pr.headSha,
|
|
2560
|
-
manifestSha: m.lastPrHeadSha ?? ""
|
|
2561
|
-
};
|
|
2562
|
-
}
|
|
2563
2592
|
if (pr.draft) return { kind: "not-mergeable", reason: "PR is still draft" };
|
|
2564
2593
|
if (pr.mergeable !== "MERGEABLE") {
|
|
2565
2594
|
return { kind: "not-mergeable", reason: `mergeable=${pr.mergeable}` };
|
|
@@ -2569,30 +2598,7 @@ function createMergeTool(deps) {
|
|
|
2569
2598
|
number: m.prNumber,
|
|
2570
2599
|
subject: input.subject
|
|
2571
2600
|
});
|
|
2572
|
-
|
|
2573
|
-
{ ...m, lastPrHeadSha: merged.headSha },
|
|
2574
|
-
"merged",
|
|
2575
|
-
{ reason: `squash merged as ${input.subject}` }
|
|
2576
|
-
);
|
|
2577
|
-
if (!t.ok) return { kind: "lifecycle", reason: t.reason };
|
|
2578
|
-
const next = {
|
|
2579
|
-
...m,
|
|
2580
|
-
lastPrHeadSha: merged.headSha,
|
|
2581
|
-
state: t.to,
|
|
2582
|
-
transitionLog: [
|
|
2583
|
-
...m.transitionLog,
|
|
2584
|
-
{ from: t.from, to: t.to, at: t.at, reason: t.reason }
|
|
2585
|
-
],
|
|
2586
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2587
|
-
};
|
|
2588
|
-
const path = await writeManifest(deps.repoRoot, next);
|
|
2589
|
-
if (finalEvidence && m.workflowId) {
|
|
2590
|
-
await appendRunEvent(deps.repoRoot, m.workflowId, finalEvidence.runState, {
|
|
2591
|
-
kind: RUN_EVENT_KINDS.MERGE,
|
|
2592
|
-
data: { mergeSha: merged.mergeSha ?? merged.mergeCommitSha ?? merged.headSha }
|
|
2593
|
-
});
|
|
2594
|
-
}
|
|
2595
|
-
return { kind: "merge", contractVersion: 1, manifestPath: path, pr: m.prNumber, taskId: m.taskId };
|
|
2601
|
+
return recordMerged(merged, `squash merged as ${input.subject}`);
|
|
2596
2602
|
};
|
|
2597
2603
|
}
|
|
2598
2604
|
|
|
@@ -2769,8 +2775,12 @@ function createCleanupTool(deps) {
|
|
|
2769
2775
|
};
|
|
2770
2776
|
}
|
|
2771
2777
|
|
|
2772
|
-
// src/
|
|
2773
|
-
|
|
2778
|
+
// src/version.js
|
|
2779
|
+
import { readFileSync, existsSync as existsSync7 } from "node:fs";
|
|
2780
|
+
import { dirname as dirname3, resolve as resolve9 } from "node:path";
|
|
2781
|
+
import { fileURLToPath } from "node:url";
|
|
2782
|
+
var PACKAGE_VERSION = "1.1.7";
|
|
2783
|
+
var TEMPLATE_SET = `v${PACKAGE_VERSION}`;
|
|
2774
2784
|
export {
|
|
2775
2785
|
ADAPTER_CONTRACT_VERSION,
|
|
2776
2786
|
ADAPTER_FILENAME,
|
package/dist/plugin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// opencode-ship v1.1.
|
|
1
|
+
// opencode-ship v1.1.7
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
4
|
var __esm = (fn, res) => function __init() {
|
|
@@ -14925,6 +14925,42 @@ function createMergeTool(deps) {
|
|
|
14925
14925
|
} else if (m.schemaVersion >= 2) {
|
|
14926
14926
|
return { kind: "missing-workflow-link", taskId: m.taskId };
|
|
14927
14927
|
}
|
|
14928
|
+
if (pr.headSha !== (m.lastPrHeadSha ?? pr.headSha)) {
|
|
14929
|
+
return {
|
|
14930
|
+
kind: "head-changed",
|
|
14931
|
+
headSha: pr.headSha,
|
|
14932
|
+
manifestSha: m.lastPrHeadSha ?? ""
|
|
14933
|
+
};
|
|
14934
|
+
}
|
|
14935
|
+
async function recordMerged(merged2, reason) {
|
|
14936
|
+
const t = transition(
|
|
14937
|
+
{ ...m, lastPrHeadSha: merged2.headSha },
|
|
14938
|
+
"merged",
|
|
14939
|
+
{ reason }
|
|
14940
|
+
);
|
|
14941
|
+
if (!t.ok) return { kind: "lifecycle", reason: t.reason };
|
|
14942
|
+
const next = {
|
|
14943
|
+
...m,
|
|
14944
|
+
lastPrHeadSha: merged2.headSha,
|
|
14945
|
+
state: t.to,
|
|
14946
|
+
transitionLog: [
|
|
14947
|
+
...m.transitionLog,
|
|
14948
|
+
{ from: t.from, to: t.to, at: t.at, reason: t.reason }
|
|
14949
|
+
],
|
|
14950
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
14951
|
+
};
|
|
14952
|
+
const path = await writeManifest(deps.repoRoot, next);
|
|
14953
|
+
if (finalEvidence && m.workflowId) {
|
|
14954
|
+
await appendRunEvent(deps.repoRoot, m.workflowId, finalEvidence.runState, {
|
|
14955
|
+
kind: RUN_EVENT_KINDS.MERGE,
|
|
14956
|
+
data: { mergeSha: merged2.mergeSha ?? merged2.mergeCommitSha ?? merged2.headSha }
|
|
14957
|
+
});
|
|
14958
|
+
}
|
|
14959
|
+
return { kind: "merge", contractVersion: 1, manifestPath: path, pr: m.prNumber, taskId: m.taskId };
|
|
14960
|
+
}
|
|
14961
|
+
if (pr.merged) {
|
|
14962
|
+
return recordMerged(pr, `reconciled external merge as ${input.subject}`);
|
|
14963
|
+
}
|
|
14928
14964
|
if (freshGates) {
|
|
14929
14965
|
const required2 = deps.adapter?.ready?.requires ?? [
|
|
14930
14966
|
"review",
|
|
@@ -14951,13 +14987,6 @@ function createMergeTool(deps) {
|
|
|
14951
14987
|
});
|
|
14952
14988
|
if (!result.ok) return gateFailureEnvelope(result);
|
|
14953
14989
|
}
|
|
14954
|
-
if (pr.headSha !== (m.lastPrHeadSha ?? pr.headSha)) {
|
|
14955
|
-
return {
|
|
14956
|
-
kind: "head-changed",
|
|
14957
|
-
headSha: pr.headSha,
|
|
14958
|
-
manifestSha: m.lastPrHeadSha ?? ""
|
|
14959
|
-
};
|
|
14960
|
-
}
|
|
14961
14990
|
if (pr.draft) return { kind: "not-mergeable", reason: "PR is still draft" };
|
|
14962
14991
|
if (pr.mergeable !== "MERGEABLE") {
|
|
14963
14992
|
return { kind: "not-mergeable", reason: `mergeable=${pr.mergeable}` };
|
|
@@ -14967,30 +14996,7 @@ function createMergeTool(deps) {
|
|
|
14967
14996
|
number: m.prNumber,
|
|
14968
14997
|
subject: input.subject
|
|
14969
14998
|
});
|
|
14970
|
-
|
|
14971
|
-
{ ...m, lastPrHeadSha: merged.headSha },
|
|
14972
|
-
"merged",
|
|
14973
|
-
{ reason: `squash merged as ${input.subject}` }
|
|
14974
|
-
);
|
|
14975
|
-
if (!t.ok) return { kind: "lifecycle", reason: t.reason };
|
|
14976
|
-
const next = {
|
|
14977
|
-
...m,
|
|
14978
|
-
lastPrHeadSha: merged.headSha,
|
|
14979
|
-
state: t.to,
|
|
14980
|
-
transitionLog: [
|
|
14981
|
-
...m.transitionLog,
|
|
14982
|
-
{ from: t.from, to: t.to, at: t.at, reason: t.reason }
|
|
14983
|
-
],
|
|
14984
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
14985
|
-
};
|
|
14986
|
-
const path = await writeManifest(deps.repoRoot, next);
|
|
14987
|
-
if (finalEvidence && m.workflowId) {
|
|
14988
|
-
await appendRunEvent(deps.repoRoot, m.workflowId, finalEvidence.runState, {
|
|
14989
|
-
kind: RUN_EVENT_KINDS.MERGE,
|
|
14990
|
-
data: { mergeSha: merged.mergeSha ?? merged.mergeCommitSha ?? merged.headSha }
|
|
14991
|
-
});
|
|
14992
|
-
}
|
|
14993
|
-
return { kind: "merge", contractVersion: 1, manifestPath: path, pr: m.prNumber, taskId: m.taskId };
|
|
14999
|
+
return recordMerged(merged, `squash merged as ${input.subject}`);
|
|
14994
15000
|
};
|
|
14995
15001
|
}
|
|
14996
15002
|
|
|
@@ -19158,13 +19164,11 @@ function spawn8(repoRoot, args) {
|
|
|
19158
19164
|
return { status: r.status ?? -1, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
|
|
19159
19165
|
}
|
|
19160
19166
|
function casDeleteBranch2(repoRoot, branch, expectedSha) {
|
|
19161
|
-
|
|
19162
|
-
|
|
19163
|
-
|
|
19164
|
-
|
|
19165
|
-
|
|
19166
|
-
}
|
|
19167
|
-
return spawn8(repoRoot, argv).status ?? -1;
|
|
19167
|
+
if (!isValidExpectedSha(expectedSha)) return -1;
|
|
19168
|
+
return spawn8(repoRoot, ["update-ref", "-d", `refs/heads/${branch}`, expectedSha]).status ?? -1;
|
|
19169
|
+
}
|
|
19170
|
+
function isValidExpectedSha(value) {
|
|
19171
|
+
return typeof value === "string" && /^[0-9a-f]{7,}$/i.test(value);
|
|
19168
19172
|
}
|
|
19169
19173
|
function safeRemoveWorktree2(repoRoot, target) {
|
|
19170
19174
|
const r = spawn8(repoRoot, ["worktree", "remove", target]);
|
|
@@ -19195,15 +19199,12 @@ async function saveCleanupPending(repoRoot, entries) {
|
|
|
19195
19199
|
writeFileSync2(path, JSON.stringify(dedupePending(entries), null, 2) + "\n", "utf8");
|
|
19196
19200
|
}
|
|
19197
19201
|
function dedupePending(entries) {
|
|
19198
|
-
const
|
|
19199
|
-
const out = [];
|
|
19202
|
+
const byTask = /* @__PURE__ */ new Map();
|
|
19200
19203
|
for (const e of entries) {
|
|
19201
19204
|
if (!e || !e.taskId) continue;
|
|
19202
|
-
|
|
19203
|
-
seen.add(e.taskId);
|
|
19204
|
-
out.push(e);
|
|
19205
|
+
byTask.set(e.taskId, e);
|
|
19205
19206
|
}
|
|
19206
|
-
return
|
|
19207
|
+
return [...byTask.values()];
|
|
19207
19208
|
}
|
|
19208
19209
|
async function appendCleanupPending(repoRoot, entry) {
|
|
19209
19210
|
const current = await loadCleanupPending(repoRoot);
|
|
@@ -19211,13 +19212,28 @@ async function appendCleanupPending(repoRoot, entry) {
|
|
|
19211
19212
|
await saveCleanupPending(repoRoot, next);
|
|
19212
19213
|
return next;
|
|
19213
19214
|
}
|
|
19215
|
+
async function clearCleanupPending(repoRoot, taskId) {
|
|
19216
|
+
const current = await loadCleanupPending(repoRoot);
|
|
19217
|
+
await saveCleanupPending(repoRoot, current.filter((entry) => entry.taskId !== taskId));
|
|
19218
|
+
}
|
|
19214
19219
|
function reject(reason, extra = {}) {
|
|
19215
19220
|
return { ok: false, reason, ...extra };
|
|
19216
19221
|
}
|
|
19217
19222
|
async function tryImmediateCleanup({ repoRoot, taskId, adapter }) {
|
|
19218
19223
|
if (!repoRoot || !taskId) return reject("missing-args");
|
|
19224
|
+
const pending = (await loadCleanupPending(repoRoot)).find((entry) => entry.taskId === taskId);
|
|
19225
|
+
const stage = pending?.stage ?? "worktree-remove";
|
|
19226
|
+
if (!["worktree-remove", "branch-delete", "manifest-seal"].includes(stage)) {
|
|
19227
|
+
return reject("cleanup-stage", { stage });
|
|
19228
|
+
}
|
|
19219
19229
|
const m = await readManifest(repoRoot, taskId);
|
|
19220
|
-
if (!m)
|
|
19230
|
+
if (!m) {
|
|
19231
|
+
if (pending && stage === "manifest-seal") {
|
|
19232
|
+
await clearCleanupPending(repoRoot, taskId);
|
|
19233
|
+
return { ok: true, removedPath: null, sealed: true };
|
|
19234
|
+
}
|
|
19235
|
+
return reject("missing-manifest");
|
|
19236
|
+
}
|
|
19221
19237
|
if (m.state !== "merged" && m.state !== "cleanup-pending") {
|
|
19222
19238
|
return reject("manifest-state", { state: m.state });
|
|
19223
19239
|
}
|
|
@@ -19229,36 +19245,68 @@ async function tryImmediateCleanup({ repoRoot, taskId, adapter }) {
|
|
|
19229
19245
|
if (!wtPath.startsWith(rootAbs + "/")) {
|
|
19230
19246
|
return reject("worktree-out-of-root", { expected: rootAbs, got: wtPath });
|
|
19231
19247
|
}
|
|
19232
|
-
|
|
19233
|
-
if (
|
|
19234
|
-
|
|
19235
|
-
|
|
19236
|
-
|
|
19237
|
-
|
|
19238
|
-
|
|
19239
|
-
|
|
19240
|
-
|
|
19241
|
-
|
|
19242
|
-
|
|
19243
|
-
|
|
19248
|
+
let headSha = isValidExpectedSha(pending?.expectedHeadSha) ? pending.expectedHeadSha : isValidExpectedSha(m.lastPrHeadSha) ? m.lastPrHeadSha : "";
|
|
19249
|
+
if (stage === "worktree-remove") {
|
|
19250
|
+
const status = spawn8(wtPath, ["status", "--porcelain"]);
|
|
19251
|
+
if (status.status === 0) {
|
|
19252
|
+
if (status.stdout.trim().length > 0) return reject("dirty-worktree");
|
|
19253
|
+
const rebase = spawn8(wtPath, ["rev-parse", "--verify", "--quiet", "REBASE_HEAD"]);
|
|
19254
|
+
if (rebase.status === 0) return reject("rebase-in-progress");
|
|
19255
|
+
const head = spawn8(wtPath, ["rev-parse", "HEAD"]);
|
|
19256
|
+
if (head.status !== 0) return reject("no-head");
|
|
19257
|
+
headSha = head.stdout.trim();
|
|
19258
|
+
if (m.lastPrHeadSha && headSha !== m.lastPrHeadSha) {
|
|
19259
|
+
return reject("head-mismatch", { expected: m.lastPrHeadSha, actual: headSha });
|
|
19260
|
+
}
|
|
19261
|
+
await appendCleanupPending(repoRoot, {
|
|
19262
|
+
taskId,
|
|
19263
|
+
failedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19264
|
+
stage: "worktree-remove",
|
|
19265
|
+
expectedHeadSha: headSha,
|
|
19266
|
+
reason: "worktree head validated"
|
|
19267
|
+
});
|
|
19268
|
+
const removed = safeRemoveWorktree2(repoRoot, wtPath);
|
|
19269
|
+
if (removed.status !== 0) {
|
|
19270
|
+
await appendCleanupPending(repoRoot, {
|
|
19271
|
+
taskId,
|
|
19272
|
+
failedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19273
|
+
stage: "worktree-remove",
|
|
19274
|
+
expectedHeadSha: headSha,
|
|
19275
|
+
reason: removed.stderr ?? "non-zero exit"
|
|
19276
|
+
});
|
|
19277
|
+
return reject("remove-failed", { detail: removed.stderr });
|
|
19278
|
+
}
|
|
19279
|
+
} else if (!headSha) {
|
|
19280
|
+
return reject("no-head");
|
|
19281
|
+
}
|
|
19244
19282
|
await appendCleanupPending(repoRoot, {
|
|
19245
19283
|
taskId,
|
|
19246
19284
|
failedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19247
|
-
stage: "
|
|
19248
|
-
|
|
19285
|
+
stage: "branch-delete",
|
|
19286
|
+
expectedHeadSha: headSha,
|
|
19287
|
+
reason: "resume cleanup"
|
|
19249
19288
|
});
|
|
19250
|
-
return reject("remove-failed", { detail: removed.stderr });
|
|
19251
19289
|
}
|
|
19252
|
-
|
|
19253
|
-
|
|
19254
|
-
|
|
19290
|
+
if (stage !== "manifest-seal") {
|
|
19291
|
+
if (!isValidExpectedSha(headSha)) return reject("no-head");
|
|
19292
|
+
const branchDelete = casDeleteBranch2(repoRoot, m.branch, headSha);
|
|
19293
|
+
const branchStillThere = spawn8(repoRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${m.branch}`]);
|
|
19294
|
+
if (branchDelete !== 0 && branchStillThere.status === 0) {
|
|
19295
|
+
await appendCleanupPending(repoRoot, {
|
|
19296
|
+
taskId,
|
|
19297
|
+
failedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19298
|
+
stage: "branch-delete",
|
|
19299
|
+
expectedHeadSha: headSha,
|
|
19300
|
+
reason: "git update-ref failed"
|
|
19301
|
+
});
|
|
19302
|
+
return reject("branch-delete-failed");
|
|
19303
|
+
}
|
|
19255
19304
|
await appendCleanupPending(repoRoot, {
|
|
19256
19305
|
taskId,
|
|
19257
19306
|
failedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19258
|
-
stage: "
|
|
19259
|
-
reason: "
|
|
19307
|
+
stage: "manifest-seal",
|
|
19308
|
+
reason: "resume cleanup"
|
|
19260
19309
|
});
|
|
19261
|
-
return reject("branch-delete-failed");
|
|
19262
19310
|
}
|
|
19263
19311
|
const next = {
|
|
19264
19312
|
...m,
|
|
@@ -19271,11 +19319,18 @@ async function tryImmediateCleanup({ repoRoot, taskId, adapter }) {
|
|
|
19271
19319
|
};
|
|
19272
19320
|
await writeManifest(repoRoot, next).catch(() => null);
|
|
19273
19321
|
await deleteManifest(repoRoot, taskId);
|
|
19322
|
+
await clearCleanupPending(repoRoot, taskId);
|
|
19274
19323
|
return { ok: true, removedPath: wtPath, sealed: true };
|
|
19275
19324
|
}
|
|
19276
19325
|
async function listPending(repoRoot) {
|
|
19277
19326
|
const all = await listManifests(repoRoot).catch(() => []);
|
|
19278
|
-
|
|
19327
|
+
const manifests = all.filter((m) => m.state === "merged" || m.state === "cleanup-pending");
|
|
19328
|
+
const queued = await loadCleanupPending(repoRoot);
|
|
19329
|
+
const byTask = new Map(manifests.map((manifest) => [manifest.taskId, manifest]));
|
|
19330
|
+
for (const entry of queued) {
|
|
19331
|
+
if (!byTask.has(entry.taskId)) byTask.set(entry.taskId, entry);
|
|
19332
|
+
}
|
|
19333
|
+
return [...byTask.values()];
|
|
19279
19334
|
}
|
|
19280
19335
|
|
|
19281
19336
|
// src/installer/ship-adapter.js
|
|
@@ -19328,7 +19383,7 @@ function selectRuntimeAdapter({ config: config2, shipAdapter, legacyAdapter }) {
|
|
|
19328
19383
|
import { readFileSync as readFileSync4, existsSync as existsSync27 } from "node:fs";
|
|
19329
19384
|
import { dirname as dirname12, resolve as resolve21 } from "node:path";
|
|
19330
19385
|
import { fileURLToPath } from "node:url";
|
|
19331
|
-
var PACKAGE_VERSION = "1.1.
|
|
19386
|
+
var PACKAGE_VERSION = "1.1.7";
|
|
19332
19387
|
var TEMPLATE_SET = `v${PACKAGE_VERSION}`;
|
|
19333
19388
|
|
|
19334
19389
|
// src/plugin.js
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-ship",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "npm-distributed OpenCode installer that materializes the delivery plugin, reviewer/verifier agents, skills, ship config and lock into any consumer repository.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"import": "./dist/plugin.js"
|
|
33
33
|
},
|
|
34
34
|
"./core": {
|
|
35
|
+
"types": "./dist/core.d.ts",
|
|
35
36
|
"import": "./dist/core.js"
|
|
36
37
|
},
|
|
37
38
|
"./schema/project-adapter.schema.json": "./schema/project-adapter.schema.json",
|