opencode-ship 0.2.1 → 0.4.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/CHANGELOG.md +69 -16
- package/README.md +26 -12
- package/THIRD_PARTY_NOTICES.md +12 -0
- package/dist/cli.js +1014 -469
- package/dist/core.js +1 -1
- package/dist/plugin.js +104 -10
- package/docs/adr/0001-opencode-ship-redesign.md +1 -1
- package/package.json +6 -1
- package/schema/project-adapter.schema.json +1 -1
- package/schema/ship-config.schema.json +6 -1
- package/schema/ship-lock.schema.json +7 -3
package/dist/core.js
CHANGED
package/dist/plugin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// opencode-ship v0.
|
|
1
|
+
// opencode-ship v0.4.0
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
4
|
var __esm = (fn, res) => function __init() {
|
|
@@ -40,20 +40,37 @@ var init_hash = __esm({
|
|
|
40
40
|
}
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
+
// src/profile.js
|
|
44
|
+
function isValidProfile(name) {
|
|
45
|
+
return typeof name === "string" && PROFILES.includes(name);
|
|
46
|
+
}
|
|
47
|
+
var PROFILES;
|
|
48
|
+
var init_profile = __esm({
|
|
49
|
+
"src/profile.js"() {
|
|
50
|
+
PROFILES = Object.freeze(["core", "engineering"]);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
43
54
|
// src/installer/lock.js
|
|
44
55
|
var lock_exports = {};
|
|
45
56
|
__export(lock_exports, {
|
|
46
57
|
CURRENT_LOCK_SCHEMA: () => CURRENT_LOCK_SCHEMA,
|
|
47
58
|
computeIntegrity: () => computeIntegrity,
|
|
48
59
|
lockPath: () => lockPath,
|
|
60
|
+
lockSchemaRevision: () => lockSchemaRevision,
|
|
49
61
|
migrateLegacyLock: () => migrateLegacyLock,
|
|
50
62
|
readLock: () => readLock2,
|
|
63
|
+
readValidatedLock: () => readValidatedLock,
|
|
51
64
|
validateIntegrity: () => validateIntegrity,
|
|
65
|
+
validateLock: () => validateLock,
|
|
52
66
|
writeLock: () => writeLock
|
|
53
67
|
});
|
|
54
68
|
import { readFile as readFile4, writeFile as writeFile4, rename as rename4, mkdir as mkdir4 } from "node:fs/promises";
|
|
55
69
|
import { existsSync as existsSync4 } from "node:fs";
|
|
56
70
|
import { dirname as dirname4, resolve as resolve8 } from "node:path";
|
|
71
|
+
function lockSchemaRevision() {
|
|
72
|
+
return CURRENT_LOCK_SCHEMA;
|
|
73
|
+
}
|
|
57
74
|
function lockPath(repoRoot) {
|
|
58
75
|
return resolve8(repoRoot, ".opencode", "ship.lock.json");
|
|
59
76
|
}
|
|
@@ -91,6 +108,71 @@ async function validateIntegrity(lock) {
|
|
|
91
108
|
const expected = computeIntegrity(lock).lockSha256;
|
|
92
109
|
return expected === lock.integrity.lockSha256;
|
|
93
110
|
}
|
|
111
|
+
function validateLock(rawLock) {
|
|
112
|
+
if (rawLock === null || rawLock === void 0) {
|
|
113
|
+
return { ok: true, kind: "missing", issues: [] };
|
|
114
|
+
}
|
|
115
|
+
if (typeof rawLock !== "object" || Array.isArray(rawLock)) {
|
|
116
|
+
return { ok: false, kind: "shape", issues: ["lock root must be an object"] };
|
|
117
|
+
}
|
|
118
|
+
const issues = [];
|
|
119
|
+
let kind = "ok";
|
|
120
|
+
if (rawLock.contractVersion !== CURRENT_LOCK_SCHEMA && rawLock.contractVersion !== 1) {
|
|
121
|
+
issues.push(`unsupported contractVersion: ${JSON.stringify(rawLock.contractVersion)} (expected ${CURRENT_LOCK_SCHEMA} or 1)`);
|
|
122
|
+
kind = "schema";
|
|
123
|
+
}
|
|
124
|
+
const manager = rawLock.manager;
|
|
125
|
+
if (manager === void 0) {
|
|
126
|
+
issues.push("manager section missing");
|
|
127
|
+
kind = kind === "ok" ? "shape" : kind;
|
|
128
|
+
} else if (typeof manager !== "object" || manager === null) {
|
|
129
|
+
issues.push("manager section must be an object");
|
|
130
|
+
kind = kind === "ok" ? "shape" : kind;
|
|
131
|
+
} else if (manager.schemaVersion !== CURRENT_LOCK_SCHEMA && manager.schemaVersion !== 1) {
|
|
132
|
+
issues.push(`unsupported manager.schemaVersion: ${JSON.stringify(manager.schemaVersion)} (expected ${CURRENT_LOCK_SCHEMA} or 1)`);
|
|
133
|
+
kind = "schema";
|
|
134
|
+
} else if (manager.name !== "opencode-ship") {
|
|
135
|
+
issues.push(`unknown manager.name: ${JSON.stringify(manager.name)}`);
|
|
136
|
+
kind = "shape";
|
|
137
|
+
} else if (rawLock.contractVersion === CURRENT_LOCK_SCHEMA && manager.schemaVersion === CURRENT_LOCK_SCHEMA && manager.profile !== void 0 && !isValidProfile(manager.profile)) {
|
|
138
|
+
issues.push(`invalid manager.profile: ${JSON.stringify(manager.profile)} (expected one of: core, engineering)`);
|
|
139
|
+
kind = "shape";
|
|
140
|
+
}
|
|
141
|
+
if (!rawLock.files || !Array.isArray(rawLock.files)) {
|
|
142
|
+
issues.push("files must be an array");
|
|
143
|
+
kind = kind === "ok" ? "shape" : kind;
|
|
144
|
+
}
|
|
145
|
+
if (!rawLock.integrity || typeof rawLock.integrity !== "object") {
|
|
146
|
+
issues.push("integrity section missing");
|
|
147
|
+
kind = kind === "ok" ? "shape" : kind;
|
|
148
|
+
} else {
|
|
149
|
+
const expected = computeIntegrity(rawLock).lockSha256;
|
|
150
|
+
if (expected !== rawLock.integrity.lockSha256) {
|
|
151
|
+
issues.push(`integrity mismatch: stored ${rawLock.integrity.lockSha256} != computed ${expected}`);
|
|
152
|
+
kind = "integrity";
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { ok: issues.length === 0, kind, issues };
|
|
156
|
+
}
|
|
157
|
+
async function readValidatedLock(repoRoot) {
|
|
158
|
+
const path = lockPath(repoRoot);
|
|
159
|
+
if (!existsSync4(path)) {
|
|
160
|
+
return { kind: "missing", lock: null, issues: [] };
|
|
161
|
+
}
|
|
162
|
+
let raw;
|
|
163
|
+
try {
|
|
164
|
+
const text = await readFile4(path, "utf8");
|
|
165
|
+
raw = JSON.parse(text);
|
|
166
|
+
} catch (e) {
|
|
167
|
+
return {
|
|
168
|
+
kind: "integrity",
|
|
169
|
+
lock: null,
|
|
170
|
+
issues: [`unable to parse lock JSON: ${e?.message ?? String(e)}`]
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
const validation = validateLock(raw);
|
|
174
|
+
return { kind: validation.kind, lock: validation.ok ? raw : null, issues: validation.issues };
|
|
175
|
+
}
|
|
94
176
|
async function migrateLegacyLock(repoRoot) {
|
|
95
177
|
const legacy = resolve8(repoRoot, ".opencode", "delivery.lock.json");
|
|
96
178
|
if (!existsSync4(legacy)) return null;
|
|
@@ -113,7 +195,8 @@ var init_lock = __esm({
|
|
|
113
195
|
"src/installer/lock.js"() {
|
|
114
196
|
init_hash();
|
|
115
197
|
init_json_pointer();
|
|
116
|
-
|
|
198
|
+
init_profile();
|
|
199
|
+
CURRENT_LOCK_SCHEMA = 2;
|
|
117
200
|
}
|
|
118
201
|
});
|
|
119
202
|
|
|
@@ -12540,7 +12623,7 @@ function tool(input) {
|
|
|
12540
12623
|
tool.schema = external_exports;
|
|
12541
12624
|
|
|
12542
12625
|
// src/plugin.js
|
|
12543
|
-
import { resolve as
|
|
12626
|
+
import { resolve as resolve12 } from "node:path";
|
|
12544
12627
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
12545
12628
|
|
|
12546
12629
|
// src/adapter.js
|
|
@@ -12765,7 +12848,7 @@ function parseRepoSlug(slug) {
|
|
|
12765
12848
|
|
|
12766
12849
|
// src/drivers/gh-cli.js
|
|
12767
12850
|
function defaultRunner(cwd, env) {
|
|
12768
|
-
return (args) => new Promise((
|
|
12851
|
+
return (args) => new Promise((resolve13, reject2) => {
|
|
12769
12852
|
const proc = spawn("gh", args, {
|
|
12770
12853
|
cwd,
|
|
12771
12854
|
env,
|
|
@@ -12777,7 +12860,7 @@ function defaultRunner(cwd, env) {
|
|
|
12777
12860
|
proc.stdout.on("data", (d) => stdout += d.toString());
|
|
12778
12861
|
proc.stderr.on("data", (d) => stderr += d.toString());
|
|
12779
12862
|
proc.on("error", reject2);
|
|
12780
|
-
proc.on("close", (status) =>
|
|
12863
|
+
proc.on("close", (status) => resolve13({ status: status ?? -1, stdout, stderr }));
|
|
12781
12864
|
});
|
|
12782
12865
|
}
|
|
12783
12866
|
function viewFields() {
|
|
@@ -14207,13 +14290,18 @@ import { dirname as dirname3, resolve as resolve7 } from "node:path";
|
|
|
14207
14290
|
// schema/ship-config.schema.json
|
|
14208
14291
|
var ship_config_schema_default = {
|
|
14209
14292
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
14210
|
-
$id: "https://github.com/Viktorxyz/opencode-
|
|
14293
|
+
$id: "https://github.com/Viktorxyz/opencode-ship/schema/ship-config.schema.json",
|
|
14211
14294
|
title: "opencode-ship user config",
|
|
14212
14295
|
type: "object",
|
|
14213
14296
|
required: ["schemaVersion"],
|
|
14214
14297
|
additionalProperties: false,
|
|
14215
14298
|
properties: {
|
|
14216
14299
|
schemaVersion: { const: 1 },
|
|
14300
|
+
profile: {
|
|
14301
|
+
type: "string",
|
|
14302
|
+
enum: ["core", "engineering"],
|
|
14303
|
+
description: "Active profile (precedence layer 2: ship.config > lock > core default)."
|
|
14304
|
+
},
|
|
14217
14305
|
owner: {
|
|
14218
14306
|
type: "string",
|
|
14219
14307
|
description: "Optional override for the issue/manifest owner field. Defaults to the agent's local user.name."
|
|
@@ -14805,8 +14893,14 @@ function flattenShipConfig(ship) {
|
|
|
14805
14893
|
return adapter;
|
|
14806
14894
|
}
|
|
14807
14895
|
|
|
14896
|
+
// src/version.js
|
|
14897
|
+
import { readFileSync as readFileSync2, existsSync as existsSync6 } from "node:fs";
|
|
14898
|
+
import { dirname as dirname5, resolve as resolve11 } from "node:path";
|
|
14899
|
+
import { fileURLToPath } from "node:url";
|
|
14900
|
+
var PACKAGE_VERSION = "0.4.0";
|
|
14901
|
+
var TEMPLATE_SET = `v${PACKAGE_VERSION}`;
|
|
14902
|
+
|
|
14808
14903
|
// src/plugin.js
|
|
14809
|
-
var VERSION = "0.2.1";
|
|
14810
14904
|
var toolDefs = [
|
|
14811
14905
|
["delivery_inspect", "Inspect a manifest and a project-local doctor report.", "inspect"],
|
|
14812
14906
|
["delivery_issue", "Find or create the issue for a delivery task.", "issue"],
|
|
@@ -14843,7 +14937,7 @@ async function resolveRepoSlug(repoRoot, detection, config2) {
|
|
|
14843
14937
|
const fromConfig = config2?.value?.project?.repository;
|
|
14844
14938
|
if (typeof fromConfig === "string" && fromConfig.includes("/")) return fromConfig;
|
|
14845
14939
|
if (detection?.repository) return detection.repository;
|
|
14846
|
-
const gitConfig = await readFile5(
|
|
14940
|
+
const gitConfig = await readFile5(resolve12(repoRoot, ".git/config"), "utf8").catch(() => null);
|
|
14847
14941
|
if (gitConfig) {
|
|
14848
14942
|
const m = gitConfig.match(/url\s*=\s*.*?github\.com[:/]([^/]+)\/([^/\s]+?)(?:\.git)?\b/);
|
|
14849
14943
|
if (m) return `${m[1]}/${m[2]}`;
|
|
@@ -14875,7 +14969,7 @@ async function bestEffortCleanupQueue(repoRoot, adapter) {
|
|
|
14875
14969
|
return { pending, manifestTasks: tasks.map((t) => t.taskId), ...out };
|
|
14876
14970
|
}
|
|
14877
14971
|
async function buildRuntime(worktree) {
|
|
14878
|
-
const repoRootAbs =
|
|
14972
|
+
const repoRootAbs = resolve12(worktree ?? process.cwd());
|
|
14879
14973
|
const detection = detectProject(repoRootAbs);
|
|
14880
14974
|
const legacyAdapter = await loadAdapter(repoRootAbs);
|
|
14881
14975
|
const config2 = await loadConfig(repoRootAbs);
|
|
@@ -14898,7 +14992,7 @@ async function buildRuntime(worktree) {
|
|
|
14898
14992
|
repoSlug: repoSlug ?? "owner/repo",
|
|
14899
14993
|
owner,
|
|
14900
14994
|
driver,
|
|
14901
|
-
packageVersion:
|
|
14995
|
+
packageVersion: PACKAGE_VERSION,
|
|
14902
14996
|
lastTaskId: null,
|
|
14903
14997
|
cleanupQueueOnStartup: cleanup,
|
|
14904
14998
|
recover: () => recoverManifestAfterCrash
|
|
@@ -40,7 +40,7 @@ with three surfaces:
|
|
|
40
40
|
|
|
41
41
|
Five output classes are installer-managed and recorded in the lock:
|
|
42
42
|
|
|
43
|
-
- `.opencode/
|
|
43
|
+
- `.opencode/plugins/opencode-ship.js` — the bundled plugin.
|
|
44
44
|
- `.opencode/agents/delivery-reviewer.md`,
|
|
45
45
|
`.opencode/agents/delivery-verifier.md`.
|
|
46
46
|
- `.opencode/skills/delivery-workflow/SKILL.md`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-ship",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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",
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
"node": ">=22.6.0",
|
|
18
18
|
"opencode": ">=1.15.5"
|
|
19
19
|
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public",
|
|
22
|
+
"provenance": true
|
|
23
|
+
},
|
|
20
24
|
"peerDependencies": {
|
|
21
25
|
"@opencode-ai/plugin": ">=1.15.5 <2"
|
|
22
26
|
},
|
|
@@ -43,6 +47,7 @@
|
|
|
43
47
|
"assets",
|
|
44
48
|
"schema",
|
|
45
49
|
"docs",
|
|
50
|
+
"THIRD_PARTY_NOTICES.md",
|
|
46
51
|
"README.md",
|
|
47
52
|
"CHANGELOG.md",
|
|
48
53
|
"LICENSE"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/Viktorxyz/opencode-
|
|
3
|
+
"$id": "https://github.com/Viktorxyz/opencode-ship/schema/project-adapter.schema.json",
|
|
4
4
|
"title": "opencode-ship project adapter",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"required": ["contractVersion"],
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/Viktorxyz/opencode-
|
|
3
|
+
"$id": "https://github.com/Viktorxyz/opencode-ship/schema/ship-config.schema.json",
|
|
4
4
|
"title": "opencode-ship user config",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"required": ["schemaVersion"],
|
|
7
7
|
"additionalProperties": false,
|
|
8
8
|
"properties": {
|
|
9
9
|
"schemaVersion": { "const": 1 },
|
|
10
|
+
"profile": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"enum": ["core", "engineering"],
|
|
13
|
+
"description": "Active profile (precedence layer 2: ship.config > lock > core default)."
|
|
14
|
+
},
|
|
10
15
|
"owner": {
|
|
11
16
|
"type": "string",
|
|
12
17
|
"description": "Optional override for the issue/manifest owner field. Defaults to the agent's local user.name."
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://github.com/Viktorxyz/opencode-
|
|
3
|
+
"$id": "https://github.com/Viktorxyz/opencode-ship/schema/ship-lock.schema.json",
|
|
4
4
|
"title": "opencode-ship install lock",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"required": ["contractVersion", "manager", "files", "integrity"],
|
|
7
7
|
"additionalProperties": false,
|
|
8
8
|
"properties": {
|
|
9
|
-
"contractVersion": { "
|
|
9
|
+
"contractVersion": { "enum": [1, 2] },
|
|
10
10
|
"manager": {
|
|
11
11
|
"type": "object",
|
|
12
12
|
"required": ["schemaVersion", "name", "version", "appliedAt", "config"],
|
|
13
13
|
"additionalProperties": false,
|
|
14
14
|
"properties": {
|
|
15
|
-
"schemaVersion": { "
|
|
15
|
+
"schemaVersion": { "enum": [1, 2] },
|
|
16
16
|
"name": { "type": "string", "pattern": "^opencode-ship$" },
|
|
17
17
|
"version": { "type": "string", "minLength": 1 },
|
|
18
18
|
"templateSet": { "type": "string", "minLength": 1 },
|
|
19
|
+
"profile": {
|
|
20
|
+
"enum": ["core", "engineering"],
|
|
21
|
+
"description": "Profile the lock was applied under. Required on locks written at schema 2; absent on legacy schema-1 locks (resolved to core by the profile precedence layer)."
|
|
22
|
+
},
|
|
19
23
|
"appliedAt": { "type": "string", "format": "date-time" },
|
|
20
24
|
"config": {
|
|
21
25
|
"type": "object",
|