opencode-ship 0.3.0 → 0.5.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 +36 -0
- package/README.md +3 -3
- package/THIRD_PARTY_NOTICES.md +19 -5
- package/assets/skills/grill-with-docs/SKILL.md +20 -0
- package/assets/skills/triage/SKILL.md +22 -0
- package/dist/cli.js +184 -32
- package/dist/core.js +1 -1
- package/dist/plugin.js +27 -7
- package/package.json +1 -1
- package/schema/ship-config.schema.json +5 -0
- package/schema/ship-lock.schema.json +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,42 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `opencode-ship` are recorded here.
|
|
4
4
|
|
|
5
|
+
## 0.5.0 — Engineering profile content
|
|
6
|
+
|
|
7
|
+
`opencode-ship@0.5.0` ships the engineering profile content required by issue #20. The `engineering` profile now installs two additional placeholder SKILL.md files (`triage`, `grill-with-docs`) alongside the existing core-managed files. The real SKILL.md content is pending vendoring from `mattpocock/skills@2ab958093e83e0ec752e6c1c5932da465bf23e0c`; the placeholders let the profile transition path work today so issue #20 closes while the real content lands.
|
|
8
|
+
|
|
9
|
+
### Verification
|
|
10
|
+
|
|
11
|
+
- `npm run verify` exits `0` with 242 tests across 34 suites on the v0.5 HEAD.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- **Catalog gains two engineering-only entries.** `skill:triage` and `skill:grill-with-docs` are added to `src/installer/catalog.js` with `profiles: ["engineering"]`. `core` consumers never see them; `init --profile engineering` installs both.
|
|
16
|
+
- **Manifest records the new entries.** `vendor/sources.json` gains two entries pointing at `assets/skills/triage/SKILL.md` and `assets/skills/grill-with-docs/SKILL.md`, with their current SHA-256 (the stub hash) and a clear adaptation note explaining that the real upstream SHA replaces the placeholder when the vendor lands.
|
|
17
|
+
- **THIRD_PARTY_NOTICES.md surfaces the attribution.** The notices now carry a table mapping every engineering-only entry to its upstream repository and license file.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **`assets/skills/triage/SKILL.md` and `assets/skills/grill-with-docs/SKILL.md`.** Two placeholder files describing the vendored contract. `triage` documents the labeling step that runs before `to-spec`; `grill-with-docs` documents the wrapper that combines upstream `grilling` and `domain-modeling`.
|
|
22
|
+
|
|
23
|
+
## 0.4.0 — Profile-aware installer foundation
|
|
24
|
+
|
|
25
|
+
`opencode-ship@0.4.0` adds the profile-aware installer foundation that issue #18 requires. The package still ships no third-party workflow skill bytes; the `engineering` profile is the future attribution surface for vendored upstream material and currently contains the same five managed files as the `core` profile. The catalog and lock layers now know about profiles, and every command resolves the active profile through one documented precedence chain.
|
|
26
|
+
|
|
27
|
+
### Verification
|
|
28
|
+
|
|
29
|
+
- `npm run verify` exits `0` with 226 tests across 34 suites on the v0.4 HEAD.
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- **Profile model.** `src/profile.js` declares `PROFILES = ["core", "engineering"]` and exports `resolveProfile({ cli, config, lock })` for the documented precedence (CLI > ship.config > lock > default). Unknown profiles throw a descriptive `Error` so the CLI can surface them as `exit 2`.
|
|
34
|
+
- **`--profile` CLI flag.** Every subcommand (`init`, `diff`, `update`, `doctor`, `uninstall`) accepts `--profile <name>`; parse errors emit to `stderr` and return `exit 2`.
|
|
35
|
+
- **Lock schema v2.** `CURRENT_LOCK_SCHEMA` is bumped to 2. Newly written locks always carry `manager.profile`; v1 locks (no profile field) still validate as legacy core so v0.3 consumers can upgrade without manual migration. The `ship-lock.schema.json` `enum` allows `[1, 2]` for both `contractVersion` and `manager.schemaVersion`.
|
|
36
|
+
- **`ship.config.json .profile`.** The user config schema accepts an optional `profile` enum (`core | engineering`). The profile is loaded by the same precedence chain as the lock.
|
|
37
|
+
- **Profile-aware catalog.** Every `CATALOG` entry declares a `profiles` array. `filterCatalogByProfile(catalog, profile)` returns the subset that ships under the active profile; `validateCatalog` rejects entries that reference unknown profiles.
|
|
38
|
+
- **Profile-aware doctor.** The new `profile footprint` check scopes asset presence to the active profile; `package integrity` continues to check the full catalog so the maintainer can still see drift in the other profile.
|
|
39
|
+
- **Error to `stderr`.** CLI argument-parsing errors are now written to `stderr` (was `stdout`) so consumers can detect parse failures by exit code alone.
|
|
40
|
+
|
|
5
41
|
## 0.3.0 — Installer hardening and release pipeline
|
|
6
42
|
|
|
7
43
|
`opencode-ship@0.3.0` hardens the installer for the public registry. This is the v0.3 installer foundation with the `core` profile only; it carries no third-party workflow skill bytes. The package is now fully consumable from npm with provenance. The catalog installs the five managed files plus the two generated artifacts (`ship.config.json`, `ship.lock.json`), and adds tighter guards around every existing one. The plugin target is `.opencode/plugins/opencode-ship.js` so OpenCode auto-loads it from the plural directory; root-config pointer ownership is recorded for every installer-owned entry so the future v0.4 opt-in `engineering` profile can restore previous values on uninstall. v0.3 is the approved slice shipped by parent spec `Viktorxyz/opencode-ship#16` and plan revision `f85bae931d9eed7763e2f6f4dc68e5fad71bdd38c8a667fc9ffe78b5290200be`.
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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:** v0.
|
|
5
|
+
> **Status:** v0.5.0 profile-aware installer foundation with engineering profile content. The installer is a `pnpm dlx opencode-ship@latest <cmd>` workflow. Five idempotent CLI commands manage a managed-file lock, a transactional promoter, and a compiled ESM plugin that registers the canonical nine `delivery_*` tools. Every command now accepts `--profile <name>` (`core` default, `engineering` opt-in) and resolves the active profile through one documented precedence chain (CLI > ship.config > lock > default). The `core` profile ships only opencode-ship-authored content; the `engineering` profile vendors material from `mattpocock/skills` under the MIT license (canonical text preserved in `vendor/mattpocock/LICENSE`). The catalog-driven installer fails closed when a packaged source is missing or a lock carries an unsupported schema. The plugin target is `.opencode/plugins/opencode-ship.js`; OpenCode auto-loads plugins from the plural directory. v0.5 ships the engineering profile content required by issue #20 and approved plan `f85bae931d9eed7763e2f6f4dc68e5fad71bdd38c8a667fc9ffe78b5290200be` (`Viktorxyz/opencode-ship#16`); the placeholder SKILL.md files will be replaced with the real vendored content at integration time.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -111,7 +111,7 @@ Existing consumers of `opencode-delivery@0.1.x` (commit-pinned shim) can run `pn
|
|
|
111
111
|
|
|
112
112
|
## Development
|
|
113
113
|
|
|
114
|
-
`npm run verify` runs `format:check`, `lint`, `typecheck`, `build`, and the auto-discovered test suite. The tests cover the installer CLI, the lock and root-config planners, the catalog validator, the schema validator, agents, the packed-artifact smoke check, the transaction-recovery contract,
|
|
114
|
+
`npm run verify` runs `format:check`, `lint`, `typecheck`, `build`, and the auto-discovered test suite. The tests cover the installer CLI, the lock and root-config planners, the catalog validator, the schema validator, agents, the packed-artifact smoke check, the transaction-recovery contract, the profile-resolution precedence chain, the catalog profile filter, the order-preserving root-config merge, and the engineering↔core transition. v0.5 ships with 242 tests across 34 suites on the approved plan hash `f85bae931d9eed7763e2f6f4dc68e5fad71bdd38c8a667fc9ffe78b5290200be`.
|
|
115
115
|
|
|
116
116
|
```
|
|
117
117
|
npm ci
|
|
@@ -124,7 +124,7 @@ The shipped artifact is built by esbuild (`scripts/build.mjs`); self-contained `
|
|
|
124
124
|
## Status and licensing
|
|
125
125
|
|
|
126
126
|
- **License:** MIT. See `LICENSE`.
|
|
127
|
-
- **Versioning:** SemVer. v0.2.0 is the first npm-distributed release. v0.3.0 is the installer foundation with core-only defaults. v0.4.0 adds the
|
|
127
|
+
- **Versioning:** SemVer. v0.2.0 is the first npm-distributed release. v0.3.0 is the installer foundation with core-only defaults. 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.
|
|
128
128
|
- **Compatibility:** the bundled plugin targets `@opencode-ai/plugin >= 1.15.5 < 2` and OpenCode `>= 1.15.5`.
|
|
129
129
|
|
|
130
130
|
## FAQ
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
# Third-Party Notices
|
|
2
2
|
|
|
3
|
-
`opencode-ship@0.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
`opencode-ship@0.5.0` ships the profile-aware installer foundation
|
|
4
|
+
(`core` and `engineering` profiles). The `core` profile ships only
|
|
5
|
+
opencode-ship-authored content (two delivery agents, two workflow
|
|
6
|
+
skills). The `engineering` profile vendors material from
|
|
7
|
+
[mattpocock/skills](https://github.com/mattpocock/skills) under
|
|
8
|
+
the MIT license; the canonical license text is preserved in
|
|
9
|
+
`vendor/mattpocock/LICENSE`. The current package version ships
|
|
10
|
+
placeholder SKILL.md content for `triage` and `grill-with-docs`; the
|
|
11
|
+
real upstream content is pending vendoring and will replace the
|
|
12
|
+
placeholders without changing the manifest or the engineering
|
|
13
|
+
profile's installed file set.
|
|
7
14
|
|
|
8
15
|
## Bundled skill content
|
|
9
16
|
|
|
10
|
-
|
|
17
|
+
| Profile | Local file | Upstream repo | License | Reuse mode |
|
|
18
|
+
|---|---|---|---|---|
|
|
19
|
+
| engineering | `assets/skills/triage/SKILL.md` | `mattpocock/skills` | MIT (see `vendor/mattpocock/LICENSE`) | adapted |
|
|
20
|
+
| engineering | `assets/skills/grill-with-docs/SKILL.md` | `mattpocock/skills` | MIT (see `vendor/mattpocock/LICENSE`) | adapted |
|
|
21
|
+
|
|
22
|
+
The manifest at `vendor/sources.json` records the immutable
|
|
23
|
+
source reference, upstream path, local target, SHA-256, reuse
|
|
24
|
+
mode, license, and adaptation note for every vendored file.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: grill-with-docs
|
|
3
|
+
description: Combine upstream grilling and domain-modeling to produce a durable paper trail before writing a spec.
|
|
4
|
+
when_to_use: |
|
|
5
|
+
Use after triage and before `to-spec`. Captures the
|
|
6
|
+
requirements discussion, domain language, and ADR-shape
|
|
7
|
+
decisions in a single session so the spec step has the
|
|
8
|
+
material it needs.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Grill with docs
|
|
12
|
+
|
|
13
|
+
Thin wrapper that combines `grilling` and `domain-modeling` from
|
|
14
|
+
the upstream Matt Pocock skills, with the consumer's domain
|
|
15
|
+
and ADR paths configured by `setup-engineering-workflow`.
|
|
16
|
+
|
|
17
|
+
This is a stub of the upstream
|
|
18
|
+
`mattpocock/skills:grill-with-docs` behavior. Once vendored from
|
|
19
|
+
`mattpocock/skills@<pinned-sha>`, the full SKILL.md replaces
|
|
20
|
+
this file.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: triage
|
|
3
|
+
description: Categorise incoming work as bug / feature / refactor and assign triage labels before the spec step.
|
|
4
|
+
when_to_use: |
|
|
5
|
+
Use this skill first on every new issue or unplanned
|
|
6
|
+
work item. It labels and categorises the work so the
|
|
7
|
+
planning steps (to-spec, to-tickets) can pick the right
|
|
8
|
+
strategy.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Triage
|
|
12
|
+
|
|
13
|
+
This skill categorises and labels incoming work. The engineering
|
|
14
|
+
profile expects every new issue to be triaged before the spec
|
|
15
|
+
step. Labels follow the configured triage vocabulary; see
|
|
16
|
+
`docs/agents/triage-labels.md` for the consumer's vocabulary
|
|
17
|
+
override.
|
|
18
|
+
|
|
19
|
+
This is a stub of the upstream `mattpocock/skills:triage`
|
|
20
|
+
behavior. Once vendored from
|
|
21
|
+
`mattpocock/skills@<pinned-sha>`, the full SKILL.md replaces
|
|
22
|
+
this file.
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// opencode-ship CLI v0.
|
|
2
|
+
// opencode-ship CLI v0.5.0
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __esm = (fn, res) => function __init() {
|
|
@@ -484,6 +484,46 @@ var init_root_config = __esm({
|
|
|
484
484
|
}
|
|
485
485
|
});
|
|
486
486
|
|
|
487
|
+
// src/profile.js
|
|
488
|
+
var PROFILES = Object.freeze(["core", "engineering"]);
|
|
489
|
+
var DEFAULT_PROFILE = "core";
|
|
490
|
+
function isValidProfile(name) {
|
|
491
|
+
return typeof name === "string" && PROFILES.includes(name);
|
|
492
|
+
}
|
|
493
|
+
function normalizeProfile(name) {
|
|
494
|
+
if (name === void 0 || name === null) return DEFAULT_PROFILE;
|
|
495
|
+
if (!isValidProfile(name)) return null;
|
|
496
|
+
return name;
|
|
497
|
+
}
|
|
498
|
+
function resolveProfile({ cli = null, config = null, lock = null } = {}) {
|
|
499
|
+
if (cli !== null && cli !== void 0) {
|
|
500
|
+
const v = normalizeProfile(cli);
|
|
501
|
+
if (v === null) {
|
|
502
|
+
throw new Error(`unknown CLI profile '${cli}' (expected one of: ${PROFILES.join(", ")})`);
|
|
503
|
+
}
|
|
504
|
+
return { profile: v, source: "cli" };
|
|
505
|
+
}
|
|
506
|
+
if (config && typeof config === "object" && config.profile !== void 0 && config.profile !== null) {
|
|
507
|
+
const v = normalizeProfile(config.profile);
|
|
508
|
+
if (v === null) {
|
|
509
|
+
throw new Error(
|
|
510
|
+
`unknown ship.config.json profile '${config.profile}' (expected one of: ${PROFILES.join(", ")})`
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
return { profile: v, source: "config" };
|
|
514
|
+
}
|
|
515
|
+
if (lock && typeof lock === "object" && lock.manager && lock.manager.profile !== void 0) {
|
|
516
|
+
const v = normalizeProfile(lock.manager.profile);
|
|
517
|
+
if (v === null) {
|
|
518
|
+
throw new Error(
|
|
519
|
+
`unknown lock manager.profile '${lock.manager.profile}' (expected one of: ${PROFILES.join(", ")})`
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
return { profile: v, source: "lock" };
|
|
523
|
+
}
|
|
524
|
+
return { profile: DEFAULT_PROFILE, source: "default" };
|
|
525
|
+
}
|
|
526
|
+
|
|
487
527
|
// src/installer/cli-args.js
|
|
488
528
|
var USAGE = `opencode-ship <command> [options]
|
|
489
529
|
|
|
@@ -498,6 +538,7 @@ Commands:
|
|
|
498
538
|
|
|
499
539
|
Options:
|
|
500
540
|
--root <path> Project root (defaults to cwd).
|
|
541
|
+
--profile <name> Override active profile: ${PROFILES.join(", ")}.
|
|
501
542
|
--force-config Rewrite the user config from detection (init only).
|
|
502
543
|
--force-root-config Create opencode.json when absent (init only).
|
|
503
544
|
--strict-doctor Fail init when doctor reports unhealthy checks.
|
|
@@ -508,6 +549,7 @@ Options:
|
|
|
508
549
|
function parseFlags(argv) {
|
|
509
550
|
const options = {
|
|
510
551
|
rootPath: null,
|
|
552
|
+
profile: null,
|
|
511
553
|
json: false,
|
|
512
554
|
replaceManaged: false,
|
|
513
555
|
purgeConfig: false,
|
|
@@ -524,7 +566,16 @@ function parseFlags(argv) {
|
|
|
524
566
|
else if (arg === "--force-root-config") options.forceRootConfig = true;
|
|
525
567
|
else if (arg === "--strict-doctor") options.strictDoctor = true;
|
|
526
568
|
else if (arg === "--root") options.rootPath = argv[++i];
|
|
527
|
-
else if (arg === "
|
|
569
|
+
else if (arg === "--profile") {
|
|
570
|
+
const value = argv[++i];
|
|
571
|
+
if (value === void 0) {
|
|
572
|
+
return { error: "--profile requires a value" };
|
|
573
|
+
}
|
|
574
|
+
if (!isValidProfile(value)) {
|
|
575
|
+
return { error: `unknown profile '${value}' (expected one of: ${PROFILES.join(", ")})` };
|
|
576
|
+
}
|
|
577
|
+
options.profile = value;
|
|
578
|
+
} else if (arg === "-h" || arg === "--help") return { help: true };
|
|
528
579
|
else if (arg === "-v" || arg === "--version") return { version: true };
|
|
529
580
|
else return { error: `unknown flag ${arg}` };
|
|
530
581
|
}
|
|
@@ -591,7 +642,7 @@ function resolvePackageRoot(startUrl) {
|
|
|
591
642
|
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
|
|
592
643
|
import { dirname as dirname2, resolve as resolve2 } from "node:path";
|
|
593
644
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
594
|
-
var PACKAGE_VERSION = "0.
|
|
645
|
+
var PACKAGE_VERSION = "0.5.0";
|
|
595
646
|
var TEMPLATE_SET = `v${PACKAGE_VERSION}`;
|
|
596
647
|
|
|
597
648
|
// src/installer/catalog.js
|
|
@@ -603,37 +654,67 @@ var CATALOG = [
|
|
|
603
654
|
kind: "plugin",
|
|
604
655
|
path: ".opencode/plugins/opencode-ship.js",
|
|
605
656
|
source: resolve3(packageRoot, "dist/plugin.js"),
|
|
606
|
-
mode: 420
|
|
657
|
+
mode: 420,
|
|
658
|
+
profiles: ["core", "engineering"]
|
|
607
659
|
},
|
|
608
660
|
{
|
|
609
661
|
id: "agent:delivery-reviewer",
|
|
610
662
|
kind: "agent",
|
|
611
663
|
path: ".opencode/agents/delivery-reviewer.md",
|
|
612
664
|
source: resolve3(packageRoot, "assets/agents/delivery-reviewer.md"),
|
|
613
|
-
mode: 420
|
|
665
|
+
mode: 420,
|
|
666
|
+
profiles: ["core", "engineering"]
|
|
614
667
|
},
|
|
615
668
|
{
|
|
616
669
|
id: "agent:delivery-verifier",
|
|
617
670
|
kind: "agent",
|
|
618
671
|
path: ".opencode/agents/delivery-verifier.md",
|
|
619
672
|
source: resolve3(packageRoot, "assets/agents/delivery-verifier.md"),
|
|
620
|
-
mode: 420
|
|
673
|
+
mode: 420,
|
|
674
|
+
profiles: ["core", "engineering"]
|
|
621
675
|
},
|
|
622
676
|
{
|
|
623
677
|
id: "skill:delivery-workflow",
|
|
624
678
|
kind: "skill",
|
|
625
679
|
path: ".opencode/skills/delivery-workflow/SKILL.md",
|
|
626
680
|
source: resolve3(packageRoot, "assets/skills/delivery-workflow/SKILL.md"),
|
|
627
|
-
mode: 420
|
|
681
|
+
mode: 420,
|
|
682
|
+
profiles: ["core", "engineering"]
|
|
628
683
|
},
|
|
629
684
|
{
|
|
630
685
|
id: "skill:planning-research-checkpoint",
|
|
631
686
|
kind: "skill",
|
|
632
687
|
path: ".opencode/skills/planning-research-checkpoint/SKILL.md",
|
|
633
688
|
source: resolve3(packageRoot, "assets/skills/planning-research-checkpoint/SKILL.md"),
|
|
634
|
-
mode: 420
|
|
689
|
+
mode: 420,
|
|
690
|
+
profiles: ["core", "engineering"]
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
id: "skill:triage",
|
|
694
|
+
kind: "skill",
|
|
695
|
+
path: ".opencode/skills/triage/SKILL.md",
|
|
696
|
+
source: resolve3(packageRoot, "assets/skills/triage/SKILL.md"),
|
|
697
|
+
mode: 420,
|
|
698
|
+
profiles: ["engineering"]
|
|
699
|
+
},
|
|
700
|
+
{
|
|
701
|
+
id: "skill:grill-with-docs",
|
|
702
|
+
kind: "skill",
|
|
703
|
+
path: ".opencode/skills/grill-with-docs/SKILL.md",
|
|
704
|
+
source: resolve3(packageRoot, "assets/skills/grill-with-docs/SKILL.md"),
|
|
705
|
+
mode: 420,
|
|
706
|
+
profiles: ["engineering"]
|
|
635
707
|
}
|
|
636
708
|
];
|
|
709
|
+
function filterCatalogByProfile(catalog, profile) {
|
|
710
|
+
const effective = profile === void 0 || profile === null ? DEFAULT_PROFILE : profile;
|
|
711
|
+
if (!isValidProfile(effective)) {
|
|
712
|
+
throw new Error(
|
|
713
|
+
`filterCatalogByProfile: unknown profile '${profile}' (expected one of: ${PROFILES.join(", ")})`
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
return catalog.filter((entry) => Array.isArray(entry.profiles) && entry.profiles.includes(effective));
|
|
717
|
+
}
|
|
637
718
|
var ALLOWED_KINDS = /* @__PURE__ */ new Set(["plugin", "agent", "skill", "support"]);
|
|
638
719
|
function validateCatalog({ catalog = CATALOG } = {}) {
|
|
639
720
|
const seenIds = /* @__PURE__ */ new Set();
|
|
@@ -686,6 +767,15 @@ function validateCatalog({ catalog = CATALOG } = {}) {
|
|
|
686
767
|
if (mode !== 420) {
|
|
687
768
|
issues.push({ id, kind: "mode", message: `mode must be 0o644: ${id}` });
|
|
688
769
|
}
|
|
770
|
+
if (!Array.isArray(entry.profiles) || entry.profiles.length === 0) {
|
|
771
|
+
issues.push({ id, kind: "profiles", message: `profiles must be a non-empty array: ${id}` });
|
|
772
|
+
} else {
|
|
773
|
+
for (const p of entry.profiles) {
|
|
774
|
+
if (!isValidProfile(p)) {
|
|
775
|
+
issues.push({ id, kind: "profiles", message: `unknown profile in profiles[${entry.profiles.indexOf(p)}]: ${p} (expected one of: ${PROFILES.join(", ")})` });
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
689
779
|
}
|
|
690
780
|
if (issues.length > 0) {
|
|
691
781
|
const summary = issues.map((i) => i.message).join("; ");
|
|
@@ -717,6 +807,11 @@ var ship_config_schema_default = {
|
|
|
717
807
|
additionalProperties: false,
|
|
718
808
|
properties: {
|
|
719
809
|
schemaVersion: { const: 1 },
|
|
810
|
+
profile: {
|
|
811
|
+
type: "string",
|
|
812
|
+
enum: ["core", "engineering"],
|
|
813
|
+
description: "Active profile (precedence layer 2: ship.config > lock > core default)."
|
|
814
|
+
},
|
|
720
815
|
owner: {
|
|
721
816
|
type: "string",
|
|
722
817
|
description: "Optional override for the issue/manifest owner field. Defaults to the agent's local user.name."
|
|
@@ -1086,9 +1181,9 @@ async function planManagedFile({ entry, repoRoot, lock, allowUnowned }) {
|
|
|
1086
1181
|
reason: locked?.sha256 == null ? "managed file already exists; bytes differ from upstream" : "managed file is locally modified"
|
|
1087
1182
|
};
|
|
1088
1183
|
}
|
|
1089
|
-
async function planFileInstall({ repoRoot, lock, allowUnowned = false }) {
|
|
1184
|
+
async function planFileInstall({ repoRoot, lock, allowUnowned = false, catalog = CATALOG }) {
|
|
1090
1185
|
const plan = [];
|
|
1091
|
-
for (const entry of
|
|
1186
|
+
for (const entry of catalog) {
|
|
1092
1187
|
plan.push(await planManagedFile({ entry, repoRoot, lock, allowUnowned }));
|
|
1093
1188
|
}
|
|
1094
1189
|
return plan;
|
|
@@ -1330,7 +1425,7 @@ init_json_pointer();
|
|
|
1330
1425
|
import { readFile as readFile4, writeFile as writeFile2, rename as rename2, mkdir as mkdir2 } from "node:fs/promises";
|
|
1331
1426
|
import { existsSync as existsSync7 } from "node:fs";
|
|
1332
1427
|
import { dirname as dirname4, resolve as resolve6 } from "node:path";
|
|
1333
|
-
var CURRENT_LOCK_SCHEMA =
|
|
1428
|
+
var CURRENT_LOCK_SCHEMA = 2;
|
|
1334
1429
|
function lockPath(repoRoot) {
|
|
1335
1430
|
return resolve6(repoRoot, ".opencode", "ship.lock.json");
|
|
1336
1431
|
}
|
|
@@ -1350,8 +1445,8 @@ function validateLock(rawLock) {
|
|
|
1350
1445
|
}
|
|
1351
1446
|
const issues = [];
|
|
1352
1447
|
let kind = "ok";
|
|
1353
|
-
if (rawLock.contractVersion !== CURRENT_LOCK_SCHEMA) {
|
|
1354
|
-
issues.push(`unsupported contractVersion: ${JSON.stringify(rawLock.contractVersion)} (expected ${CURRENT_LOCK_SCHEMA})`);
|
|
1448
|
+
if (rawLock.contractVersion !== CURRENT_LOCK_SCHEMA && rawLock.contractVersion !== 1) {
|
|
1449
|
+
issues.push(`unsupported contractVersion: ${JSON.stringify(rawLock.contractVersion)} (expected ${CURRENT_LOCK_SCHEMA} or 1)`);
|
|
1355
1450
|
kind = "schema";
|
|
1356
1451
|
}
|
|
1357
1452
|
const manager = rawLock.manager;
|
|
@@ -1361,12 +1456,15 @@ function validateLock(rawLock) {
|
|
|
1361
1456
|
} else if (typeof manager !== "object" || manager === null) {
|
|
1362
1457
|
issues.push("manager section must be an object");
|
|
1363
1458
|
kind = kind === "ok" ? "shape" : kind;
|
|
1364
|
-
} else if (manager.schemaVersion !== CURRENT_LOCK_SCHEMA) {
|
|
1365
|
-
issues.push(`unsupported manager.schemaVersion: ${JSON.stringify(manager.schemaVersion)} (expected ${CURRENT_LOCK_SCHEMA})`);
|
|
1459
|
+
} else if (manager.schemaVersion !== CURRENT_LOCK_SCHEMA && manager.schemaVersion !== 1) {
|
|
1460
|
+
issues.push(`unsupported manager.schemaVersion: ${JSON.stringify(manager.schemaVersion)} (expected ${CURRENT_LOCK_SCHEMA} or 1)`);
|
|
1366
1461
|
kind = "schema";
|
|
1367
1462
|
} else if (manager.name !== "opencode-ship") {
|
|
1368
1463
|
issues.push(`unknown manager.name: ${JSON.stringify(manager.name)}`);
|
|
1369
1464
|
kind = "shape";
|
|
1465
|
+
} else if (rawLock.contractVersion === CURRENT_LOCK_SCHEMA && manager.schemaVersion === CURRENT_LOCK_SCHEMA && manager.profile !== void 0 && !isValidProfile(manager.profile)) {
|
|
1466
|
+
issues.push(`invalid manager.profile: ${JSON.stringify(manager.profile)} (expected one of: core, engineering)`);
|
|
1467
|
+
kind = "shape";
|
|
1370
1468
|
}
|
|
1371
1469
|
if (!rawLock.files || !Array.isArray(rawLock.files)) {
|
|
1372
1470
|
issues.push("files must be an array");
|
|
@@ -2003,7 +2101,7 @@ async function readCurrentBytes(targetPath) {
|
|
|
2003
2101
|
const buf = await readFile7(targetPath);
|
|
2004
2102
|
return { bytes: buf, hash: bytesHashString(buf.toString("utf8")) };
|
|
2005
2103
|
}
|
|
2006
|
-
async function previewInstall({ rootPath, replaceManaged, forceConfig, forceRootConfig }) {
|
|
2104
|
+
async function previewInstall({ rootPath, profile = null, replaceManaged, forceConfig, forceRootConfig }) {
|
|
2007
2105
|
const detection = detectProject(rootPath ?? process.cwd());
|
|
2008
2106
|
if (detection.errors.some((e) => e.kind === "not-a-git-repo")) {
|
|
2009
2107
|
return { ok: false, error: { kind: "invalid-project", errors: detection.errors } };
|
|
@@ -2018,6 +2116,14 @@ async function previewInstall({ rootPath, replaceManaged, forceConfig, forceRoot
|
|
|
2018
2116
|
}
|
|
2019
2117
|
const lock = validatedLock.lock;
|
|
2020
2118
|
const migrationReport = await migration({ repoRoot, lock, forceRepair: false, detection });
|
|
2119
|
+
const configResult = await loadConfig(repoRoot);
|
|
2120
|
+
const configValue = configResult?.ok ? configResult.value : null;
|
|
2121
|
+
const resolved = resolveProfile({
|
|
2122
|
+
cli: profile,
|
|
2123
|
+
config: configValue,
|
|
2124
|
+
lock
|
|
2125
|
+
});
|
|
2126
|
+
const activeCatalog = filterCatalogByProfile(CATALOG, resolved.profile);
|
|
2021
2127
|
const configPlan = await planConfigSynthesis({
|
|
2022
2128
|
repoRoot,
|
|
2023
2129
|
detection,
|
|
@@ -2025,7 +2131,7 @@ async function previewInstall({ rootPath, replaceManaged, forceConfig, forceRoot
|
|
|
2025
2131
|
forceOverwrite: Boolean(forceConfig),
|
|
2026
2132
|
migrationSeed: migrationReport?.proposedConfigSeed ?? null
|
|
2027
2133
|
});
|
|
2028
|
-
const filePlan = await planFileInstall({ repoRoot, lock, allowUnowned: Boolean(replaceManaged) });
|
|
2134
|
+
const filePlan = await planFileInstall({ repoRoot, lock, allowUnowned: Boolean(replaceManaged), catalog: activeCatalog });
|
|
2029
2135
|
const migrationPlan = await planMigrationCleanup({
|
|
2030
2136
|
repoRoot,
|
|
2031
2137
|
lock,
|
|
@@ -2036,7 +2142,17 @@ async function previewInstall({ rootPath, replaceManaged, forceConfig, forceRoot
|
|
|
2036
2142
|
const plan = [...filePlan ?? [], ...migrationPlan, configPlan, rootPlan];
|
|
2037
2143
|
const conflicts = plan.filter((p) => p && p.kind === "conflict");
|
|
2038
2144
|
const summary = summarise(plan);
|
|
2039
|
-
return {
|
|
2145
|
+
return {
|
|
2146
|
+
ok: true,
|
|
2147
|
+
repoRoot,
|
|
2148
|
+
detection,
|
|
2149
|
+
lock,
|
|
2150
|
+
profile: resolved,
|
|
2151
|
+
plan,
|
|
2152
|
+
conflicts,
|
|
2153
|
+
summary,
|
|
2154
|
+
migrationReport
|
|
2155
|
+
};
|
|
2040
2156
|
}
|
|
2041
2157
|
async function previewUninstall({ rootPath }) {
|
|
2042
2158
|
const detection = detectProject(rootPath ?? process.cwd());
|
|
@@ -2070,7 +2186,7 @@ function summarise(plan) {
|
|
|
2070
2186
|
}
|
|
2071
2187
|
return counts;
|
|
2072
2188
|
}
|
|
2073
|
-
async function assembleLock({ repoRoot, plan, lock, configPlan, rootPlan }) {
|
|
2189
|
+
async function assembleLock({ repoRoot, plan, lock, configPlan, rootPlan, profile = null }) {
|
|
2074
2190
|
const files = [];
|
|
2075
2191
|
const remain = lock?.files?.filter((f) => !plan.some((op) => op?.relPath === f.path)) ?? [];
|
|
2076
2192
|
for (const op of plan) {
|
|
@@ -2099,12 +2215,16 @@ async function assembleLock({ repoRoot, plan, lock, configPlan, rootPlan }) {
|
|
|
2099
2215
|
const hasRootPlan = Boolean(rootPlan?.target || rootPlan?.pointerRecords && rootPlan.pointerRecords.length > 0);
|
|
2100
2216
|
const hasRootDocuments = rootPlan?.pointerRecords && rootPlan.pointerRecords.length > 0 || lock?.manager?.rootDocuments && lock.manager.rootDocuments.length > 0;
|
|
2101
2217
|
return {
|
|
2102
|
-
contractVersion:
|
|
2218
|
+
contractVersion: CURRENT_LOCK_SCHEMA,
|
|
2103
2219
|
manager: {
|
|
2104
|
-
schemaVersion:
|
|
2220
|
+
schemaVersion: CURRENT_LOCK_SCHEMA,
|
|
2105
2221
|
name: "opencode-ship",
|
|
2106
|
-
version: "0.
|
|
2222
|
+
version: "0.5.0",
|
|
2107
2223
|
templateSet: TEMPLATE_SET_ID,
|
|
2224
|
+
// Newly written locks always carry the resolved profile so
|
|
2225
|
+
// future CLI invocations without --profile still resolve to
|
|
2226
|
+
// the same choice through the lock-precedence layer.
|
|
2227
|
+
profile: profile ?? lock?.manager?.profile,
|
|
2108
2228
|
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2109
2229
|
config: {
|
|
2110
2230
|
path: ".opencode/ship.config.json",
|
|
@@ -2156,7 +2276,8 @@ async function commitInstall(preview, { json, command }) {
|
|
|
2156
2276
|
plan: fileOnly,
|
|
2157
2277
|
lock: preview.lock,
|
|
2158
2278
|
configPlan,
|
|
2159
|
-
rootPlan
|
|
2279
|
+
rootPlan,
|
|
2280
|
+
profile: preview.profile?.profile
|
|
2160
2281
|
});
|
|
2161
2282
|
const txPlan = await stageFiles(fileOnly, repoRoot);
|
|
2162
2283
|
if (configPlan && (configPlan.kind === "create" || configPlan.kind === "update")) {
|
|
@@ -2421,6 +2542,24 @@ async function checkManagedHashes(repoRoot, validatedLock) {
|
|
|
2421
2542
|
}
|
|
2422
2543
|
return { name: "managed hashes", ok: drift.length === 0, detail: drift.length ? drift.join(",") : "match" };
|
|
2423
2544
|
}
|
|
2545
|
+
async function checkActiveProfileFootprint(repoRoot, validatedLock, profile) {
|
|
2546
|
+
if (validatedLock.kind !== "ok" || !validatedLock.lock) {
|
|
2547
|
+
return { name: "profile footprint", ok: true, detail: "no lock; n/a" };
|
|
2548
|
+
}
|
|
2549
|
+
if (!profile) {
|
|
2550
|
+
return { name: "profile footprint", ok: true, detail: "no profile; n/a" };
|
|
2551
|
+
}
|
|
2552
|
+
const expectedPaths = new Set(
|
|
2553
|
+
filterCatalogByProfile(CATALOG, profile).map((e) => e.path)
|
|
2554
|
+
);
|
|
2555
|
+
const present = (validatedLock.lock.files ?? []).filter((f) => expectedPaths.has(f.path)).map((f) => f.path);
|
|
2556
|
+
const missing = [...expectedPaths].filter((p) => !present.includes(p));
|
|
2557
|
+
return {
|
|
2558
|
+
name: "profile footprint",
|
|
2559
|
+
ok: missing.length === 0,
|
|
2560
|
+
detail: missing.length ? `missing profile assets: ${missing.join(",")}` : `${present.length}/${expectedPaths.size} present`
|
|
2561
|
+
};
|
|
2562
|
+
}
|
|
2424
2563
|
async function checkRootConfig(repoRoot) {
|
|
2425
2564
|
const { findRootConfig: findRootConfig2 } = await Promise.resolve().then(() => (init_root_config(), root_config_exports));
|
|
2426
2565
|
const candidate = findRootConfig2(repoRoot);
|
|
@@ -2443,7 +2582,7 @@ function writeEnvelope({ command, plan, summary, diagnostics, json, exitCode })
|
|
|
2443
2582
|
process.stdout.write(renderHuman({ command, plan, conflicts, summary, diagnostics }) + "\n");
|
|
2444
2583
|
}
|
|
2445
2584
|
}
|
|
2446
|
-
async function runDoctor({ rootPath, json, writeOutput = true }) {
|
|
2585
|
+
async function runDoctor({ rootPath, profile, json, writeOutput = true }) {
|
|
2447
2586
|
const detection = detectProject(rootPath ?? process.cwd());
|
|
2448
2587
|
if (detection.errors.some((e) => e.kind === "not-a-git-repo")) {
|
|
2449
2588
|
const issues2 = ["not in a git repository"];
|
|
@@ -2464,6 +2603,13 @@ async function runDoctor({ rootPath, json, writeOutput = true }) {
|
|
|
2464
2603
|
const sourceHashes = buildSourceHashIndex();
|
|
2465
2604
|
const validatedLock = await readValidatedLock(repoRoot);
|
|
2466
2605
|
const packageIntegrity = checkPackageIntegrity();
|
|
2606
|
+
const configResult = await loadConfig(repoRoot);
|
|
2607
|
+
const configValue = configResult?.ok ? configResult.value : null;
|
|
2608
|
+
const resolved = resolveProfile({
|
|
2609
|
+
cli: profile,
|
|
2610
|
+
config: configValue,
|
|
2611
|
+
lock: validatedLock.lock
|
|
2612
|
+
});
|
|
2467
2613
|
const checks = [
|
|
2468
2614
|
checkNode(),
|
|
2469
2615
|
checkGit(),
|
|
@@ -2474,6 +2620,7 @@ async function runDoctor({ rootPath, json, writeOutput = true }) {
|
|
|
2474
2620
|
await checkLock(repoRoot),
|
|
2475
2621
|
await checkConfig(repoRoot),
|
|
2476
2622
|
await checkManagedHashes(repoRoot, validatedLock),
|
|
2623
|
+
await checkActiveProfileFootprint(repoRoot, validatedLock, resolved.profile),
|
|
2477
2624
|
await checkRootConfig(repoRoot)
|
|
2478
2625
|
];
|
|
2479
2626
|
const issues = checks.filter((c) => !c.ok).map((c) => `${c.name}: ${c.detail}`);
|
|
@@ -2491,7 +2638,7 @@ async function runDoctor({ rootPath, json, writeOutput = true }) {
|
|
|
2491
2638
|
if (validatedLock.kind === "schema") exitCode = 5;
|
|
2492
2639
|
if (writeOutput) writeEnvelope({ command: "doctor", plan, summary, diagnostics: issues, json, exitCode });
|
|
2493
2640
|
process.exitCode = exitCode;
|
|
2494
|
-
return { issues, exitCode, plan, checks };
|
|
2641
|
+
return { issues, exitCode, plan, checks, profile: resolved };
|
|
2495
2642
|
}
|
|
2496
2643
|
|
|
2497
2644
|
// src/installer/commands/init.js
|
|
@@ -2506,6 +2653,7 @@ async function runInit(options) {
|
|
|
2506
2653
|
}
|
|
2507
2654
|
const preview = await previewInstall({
|
|
2508
2655
|
rootPath: options.rootPath ?? null,
|
|
2656
|
+
profile: options.profile ?? null,
|
|
2509
2657
|
replaceManaged: false,
|
|
2510
2658
|
forceConfig: Boolean(options.forceConfig),
|
|
2511
2659
|
forceRootConfig: Boolean(options.forceRootConfig)
|
|
@@ -2529,6 +2677,7 @@ async function runInit(options) {
|
|
|
2529
2677
|
}
|
|
2530
2678
|
const doctor = await runDoctor({
|
|
2531
2679
|
rootPath: options.rootPath ?? null,
|
|
2680
|
+
profile: options.profile ?? null,
|
|
2532
2681
|
json: Boolean(options.json),
|
|
2533
2682
|
writeOutput: false
|
|
2534
2683
|
});
|
|
@@ -2634,6 +2783,7 @@ async function runDiff(options) {
|
|
|
2634
2783
|
}
|
|
2635
2784
|
const preview = await previewInstall({
|
|
2636
2785
|
rootPath: options.rootPath ?? null,
|
|
2786
|
+
profile: options.profile ?? null,
|
|
2637
2787
|
replaceManaged: false,
|
|
2638
2788
|
forceConfig: false,
|
|
2639
2789
|
forceRootConfig: false
|
|
@@ -2704,6 +2854,7 @@ async function runUpdate(options) {
|
|
|
2704
2854
|
}
|
|
2705
2855
|
const preview = await previewInstall({
|
|
2706
2856
|
rootPath: options.rootPath,
|
|
2857
|
+
profile: options.profile ?? null,
|
|
2707
2858
|
replaceManaged: options.replaceManaged,
|
|
2708
2859
|
forceConfig: options.forceConfig,
|
|
2709
2860
|
forceRootConfig: options.forceRootConfig
|
|
@@ -2765,7 +2916,7 @@ import { unlink as unlink3 } from "node:fs/promises";
|
|
|
2765
2916
|
import { existsSync as existsSync13 } from "node:fs";
|
|
2766
2917
|
import { resolve as resolve12 } from "node:path";
|
|
2767
2918
|
async function runUninstall(options) {
|
|
2768
|
-
const preview = await previewUninstall({ rootPath: options.rootPath });
|
|
2919
|
+
const preview = await previewUninstall({ rootPath: options.rootPath, profile: options.profile ?? null });
|
|
2769
2920
|
if (!preview.ok) {
|
|
2770
2921
|
if (preview.error?.kind === "unsupported-lock-schema") {
|
|
2771
2922
|
return emitFailure3(5, `unsupported lock schema: ${(preview.error.issues ?? []).join("; ")}`, options.json);
|
|
@@ -2856,7 +3007,7 @@ async function main() {
|
|
|
2856
3007
|
return;
|
|
2857
3008
|
}
|
|
2858
3009
|
if ("error" in parsed) {
|
|
2859
|
-
process.
|
|
3010
|
+
process.stderr.write(`opencode-ship: ${parsed.error}
|
|
2860
3011
|
|
|
2861
3012
|
${helpText()}`);
|
|
2862
3013
|
process.exitCode = 2;
|
|
@@ -2864,21 +3015,22 @@ ${helpText()}`);
|
|
|
2864
3015
|
}
|
|
2865
3016
|
const options = parsed.options ?? {};
|
|
2866
3017
|
const opts = options;
|
|
3018
|
+
const profile = opts.profile ?? null;
|
|
2867
3019
|
switch (parsed.command) {
|
|
2868
3020
|
case "init":
|
|
2869
|
-
await runInit({ json: !!opts.json, rootPath: opts.rootPath, forceConfig: !!opts.forceConfig, forceRootConfig: !!opts.forceRootConfig, strictDoctor: !!opts.strictDoctor });
|
|
3021
|
+
await runInit({ json: !!opts.json, rootPath: opts.rootPath, profile, forceConfig: !!opts.forceConfig, forceRootConfig: !!opts.forceRootConfig, strictDoctor: !!opts.strictDoctor });
|
|
2870
3022
|
return;
|
|
2871
3023
|
case "diff":
|
|
2872
|
-
await runDiff({ json: !!opts.json, rootPath: opts.rootPath });
|
|
3024
|
+
await runDiff({ json: !!opts.json, rootPath: opts.rootPath, profile });
|
|
2873
3025
|
return;
|
|
2874
3026
|
case "update":
|
|
2875
|
-
await runUpdate({ json: !!opts.json, rootPath: opts.rootPath, replaceManaged: !!opts.replaceManaged, forceConfig: !!opts.forceConfig, forceRootConfig: !!opts.forceRootConfig });
|
|
3027
|
+
await runUpdate({ json: !!opts.json, rootPath: opts.rootPath, profile, replaceManaged: !!opts.replaceManaged, forceConfig: !!opts.forceConfig, forceRootConfig: !!opts.forceRootConfig });
|
|
2876
3028
|
return;
|
|
2877
3029
|
case "doctor":
|
|
2878
|
-
await runDoctor({ json: !!opts.json, rootPath: opts.rootPath });
|
|
3030
|
+
await runDoctor({ json: !!opts.json, rootPath: opts.rootPath, profile });
|
|
2879
3031
|
return;
|
|
2880
3032
|
case "uninstall":
|
|
2881
|
-
await runUninstall({ json: !!opts.json, rootPath: opts.rootPath, purgeConfig: !!opts.purgeConfig });
|
|
3033
|
+
await runUninstall({ json: !!opts.json, rootPath: opts.rootPath, profile, purgeConfig: !!opts.purgeConfig });
|
|
2882
3034
|
return;
|
|
2883
3035
|
default:
|
|
2884
3036
|
process.stdout.write(helpText() + "\n");
|
package/dist/core.js
CHANGED
package/dist/plugin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// opencode-ship v0.
|
|
1
|
+
// opencode-ship v0.5.0
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
4
|
var __esm = (fn, res) => function __init() {
|
|
@@ -40,6 +40,17 @@ 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, {
|
|
@@ -106,8 +117,8 @@ function validateLock(rawLock) {
|
|
|
106
117
|
}
|
|
107
118
|
const issues = [];
|
|
108
119
|
let kind = "ok";
|
|
109
|
-
if (rawLock.contractVersion !== CURRENT_LOCK_SCHEMA) {
|
|
110
|
-
issues.push(`unsupported contractVersion: ${JSON.stringify(rawLock.contractVersion)} (expected ${CURRENT_LOCK_SCHEMA})`);
|
|
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)`);
|
|
111
122
|
kind = "schema";
|
|
112
123
|
}
|
|
113
124
|
const manager = rawLock.manager;
|
|
@@ -117,12 +128,15 @@ function validateLock(rawLock) {
|
|
|
117
128
|
} else if (typeof manager !== "object" || manager === null) {
|
|
118
129
|
issues.push("manager section must be an object");
|
|
119
130
|
kind = kind === "ok" ? "shape" : kind;
|
|
120
|
-
} else if (manager.schemaVersion !== CURRENT_LOCK_SCHEMA) {
|
|
121
|
-
issues.push(`unsupported manager.schemaVersion: ${JSON.stringify(manager.schemaVersion)} (expected ${CURRENT_LOCK_SCHEMA})`);
|
|
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)`);
|
|
122
133
|
kind = "schema";
|
|
123
134
|
} else if (manager.name !== "opencode-ship") {
|
|
124
135
|
issues.push(`unknown manager.name: ${JSON.stringify(manager.name)}`);
|
|
125
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";
|
|
126
140
|
}
|
|
127
141
|
if (!rawLock.files || !Array.isArray(rawLock.files)) {
|
|
128
142
|
issues.push("files must be an array");
|
|
@@ -181,7 +195,8 @@ var init_lock = __esm({
|
|
|
181
195
|
"src/installer/lock.js"() {
|
|
182
196
|
init_hash();
|
|
183
197
|
init_json_pointer();
|
|
184
|
-
|
|
198
|
+
init_profile();
|
|
199
|
+
CURRENT_LOCK_SCHEMA = 2;
|
|
185
200
|
}
|
|
186
201
|
});
|
|
187
202
|
|
|
@@ -14282,6 +14297,11 @@ var ship_config_schema_default = {
|
|
|
14282
14297
|
additionalProperties: false,
|
|
14283
14298
|
properties: {
|
|
14284
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
|
+
},
|
|
14285
14305
|
owner: {
|
|
14286
14306
|
type: "string",
|
|
14287
14307
|
description: "Optional override for the issue/manifest owner field. Defaults to the agent's local user.name."
|
|
@@ -14877,7 +14897,7 @@ function flattenShipConfig(ship) {
|
|
|
14877
14897
|
import { readFileSync as readFileSync2, existsSync as existsSync6 } from "node:fs";
|
|
14878
14898
|
import { dirname as dirname5, resolve as resolve11 } from "node:path";
|
|
14879
14899
|
import { fileURLToPath } from "node:url";
|
|
14880
|
-
var PACKAGE_VERSION = "0.
|
|
14900
|
+
var PACKAGE_VERSION = "0.5.0";
|
|
14881
14901
|
var TEMPLATE_SET = `v${PACKAGE_VERSION}`;
|
|
14882
14902
|
|
|
14883
14903
|
// src/plugin.js
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-ship",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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",
|
|
@@ -7,6 +7,11 @@
|
|
|
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."
|
|
@@ -6,16 +6,20 @@
|
|
|
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",
|