opencode-ship 0.5.0 → 0.7.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 CHANGED
@@ -2,6 +2,42 @@
2
2
 
3
3
  All notable changes to `opencode-ship` are recorded here.
4
4
 
5
+ ## 0.7.0 — M3 task loop contract
6
+
7
+ `opencode-ship@0.7.0` ships the M3 task loop contract required by issue #22 (Task 7 in approved plan). The run store persists task state under `.git/opencode-ship/runs/<taskId>/`; the task brief extractor surfaces only the active task plus the plan header; the task reviewer emits separate Spec and Quality verdicts; the build-side commit ownership returns true only when the immutable review package is sealed and the plan hash still matches; the three-round breaker routes a failed third round back to the GPT planning role for a revision; the commit binding appends the immutable range to the run ledger; and the compaction context builder emits the short pointer set the chat hook injects when the context overflows. No plan body, no report body, and no commit diffs ever enter the chat.
8
+
9
+ ### Verification
10
+
11
+ - `npm run verify` exits `0` with 302 tests across 34 suites on the v0.7 HEAD.
12
+
13
+ ### Added
14
+
15
+ - **Run store.** `src/installer/run-store.js` exposes `ensureRunDir`, `writeProgress`, `readProgress`, `recordCommitRange` (append-only, dedup-rejected), `readCommitRanges`. Persists run state under `.git/opencode-ship/runs/<taskId>/` with the progress.md / ledger.json / reports/ layout.
16
+ - **Task brief + compact context.** `src/installer/task-brief.js` exposes `buildTaskBrief` (extracts the active task from a multi-task plan plus the plan header) and `renderCompactContext` (emits the short pointer set the compaction hook injects into chat).
17
+ - **Task reviewer.** `src/installer/task-reviewer.js` exposes `emitSpecVerdict`, `emitQualityVerdict` (separate verdicts with `specKind` / `qualityKind` discriminators), `shouldCommit` (only non-blocking on both sides), `assembleReviewPackage` (writes the immutable package to `reports/review-package.json`), `readReviewPackage`.
18
+ - **Build-side commit ownership.** `src/installer/build-ownership.js` returns true only when both verdicts are non-blocking AND the sealed review package is on disk AND the plan hash still matches.
19
+ - **Three-round breaker.** `src/installer/three-round-breaker.js` exposes `MAX_FIX_ROUNDS = 3` and `shouldRequestPlanRevision` (returns true only when `fixRound >= 3`).
20
+ - **Compaction context.** `src/installer/compaction.js` exposes `buildCompactionContext` and `compactContextForRun` (reads the ledger entry count from disk and merges it with the caller-supplied pointer set).
21
+ - **Commit binding.** `src/installer/commit-binding.js` re-exports the run-store `recordCommitRange` as `recordApprovedCommit` so Build can call one name per role.
22
+
23
+ ## 0.6.0 — Durable plan artifact + Plan Mode integration
24
+
25
+ `opencode-ship@0.6.0` ships the durable plan artifact and the Plan Mode permission integration required by issue #21. The runtime now supports the GPT-to-MiniMax handoff end-to-end: a planning sub-agent can write a hash-verified plan to `.git/opencode-ship/plans/<slug>/revision-NNNN.json`, mirror it to the parent issue as a marked comment, and run with a deny-first, narrow-allow permission block that prevents it from touching source/config/docs.
26
+
27
+ ### Verification
28
+
29
+ - `npm run verify` exits `0` with 283 tests across 34 suites on the v0.6 HEAD.
30
+
31
+ ### Added
32
+
33
+ - **Plan artifact.** `src/installer/plan.js` declares the plan schema (version, revision, parentIssue, baseSha, architecture, global constraints, file responsibilities, ordered tasks with interfaces / testSeams / commands / expectedEvidence, acceptance, out of scope, recovery). `validatePlan` is fail-closed; `computePlanHash` produces a stable SHA-256 over the canonical content; `canRevise` enforces the append-only N+1 rule; `planNeedsPlaceholderReview` flags any `<placeholder>` marker so a final-reviewer can refuse approval.
34
+ - **Plan persistence.** `src/installer/plan-store.js` writes, reads, and lists plan revisions under `.git/opencode-ship/plans/<planSlug>/revision-NNNN.json`. The store refuses to overwrite or skip revisions.
35
+ - **Plan issue mirror.** `src/installer/plan-mirror.js` posts the approved plan to the parent issue as a marked comment with the stable `opencode-ship-execution-handoff:v1` marker, the plan hash, and the revision. Retries with linear backoff. The client is injectable so tests run without `gh`.
36
+ - **Engineering config.** `src/installer/engineering-config.js` validates the user config (`models.{planner,builder,finalReviewer}` and `plans.{root,mirrorToIssue}`) and resolves model roles with documented defaults. Strict mode throws on missing roles.
37
+ - **Plan Mode permission block.** `src/installer/plan-mode-permissions.js` produces the deny-first, narrow-allow permission set documented in the approved plan: bash / webfetch / task.plan-agent / task.build-agent deny, edit / write allow only `.git/opencode-ship/plans/**`.
38
+ - **OpenCode config integration.** `src/installer/root-config.js` gains `applyPlanModeOwnership` which injects the Plan Mode block under `agent.plan.permission` on the consumer's `opencode.json` when the active profile is `engineering`. Captures the previous value so uninstall can restore it.
39
+ - **Executor wiring.** `src/installer/executor.js` and `planner.js` thread the active profile through to the planner so core consumers never see the Plan Mode block.
40
+
5
41
  ## 0.5.0 — Engineering profile content
6
42
 
7
43
  `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.
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.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.
5
+ > **Status:** v0.7.0 compact-safe M3 task loop. 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`) AND installs the GPT-to-MiniMax Plan Mode permission block under `agent.plan.permission` so consumers can spin up a Plan Mode sub-agent without hand-editing the opencode.json. The M3 task loop contract (run store + task brief + Spec/Quality verdicts + 3-round breaker + commit binding + compaction context) is now implemented end-to-end. v0.7 ships the M3 task loop contract required by issue #22 and approved plan `f85bae931d9eed7763e2f6f4dc68e5fad71bdd38c8a667fc9ffe78b5290200be` (`Viktorxyz/opencode-ship#16`); the actual implementer and reviewer sub-agents are runtime pieces that depend on the consumer's OpenCode configuration.
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, 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`.
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, the engineering↔core transition, the durable plan artifact, the Plan Mode permission integration, the M3 task loop contract (run store + task brief + Spec/Quality verdicts + 3-round breaker + commit binding + compaction context). v0.7 ships with 302 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 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.
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. 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.
128
128
  - **Compatibility:** the bundled plugin targets `@opencode-ai/plugin >= 1.15.5 < 2` and OpenCode `>= 1.15.5`.
129
129
 
130
130
  ## FAQ
@@ -1,9 +1,11 @@
1
1
  # Third-Party Notices
2
2
 
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
3
+ `opencode-ship@0.7.0` ships the profile-aware installer foundation
4
+ (`core` and `engineering` profiles), the durable plan artifact
5
+ with Plan Mode permission integration, and the M3 task loop
6
+ contract. The `core` profile ships only opencode-ship-authored
7
+ content (two delivery agents, two workflow skills). The
8
+ `engineering` profile vendors material from
7
9
  [mattpocock/skills](https://github.com/mattpocock/skills) under
8
10
  the MIT license; the canonical license text is preserved in
9
11
  `vendor/mattpocock/LICENSE`. The current package version ships
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // opencode-ship CLI v0.5.0
2
+ // opencode-ship CLI v0.7.0
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __esm = (fn, res) => function __init() {
@@ -110,16 +110,51 @@ var init_hash = __esm({
110
110
  }
111
111
  });
112
112
 
113
+ // src/installer/plan-mode-permissions.js
114
+ function planModePermissions() {
115
+ return {
116
+ build: {
117
+ // Broad-deny entries for the standard Build verbs. These
118
+ // cover bash, source edits, and writes outside the plans
119
+ // path. opencode.js evaluates the last matching rule, so
120
+ // a narrow object-typed entry after a string-typed deny
121
+ // creates a per-path exception.
122
+ bash: DENY_DEFAULT,
123
+ edit: { [PLANS_GLOB]: ALLOW_PLANS },
124
+ write: { [PLANS_GLOB]: ALLOW_PLANS },
125
+ webfetch: DENY_DEFAULT,
126
+ // The Plan Mode sub-agent also has no business calling
127
+ // delivery tools itself; that is the Build controller's
128
+ // job. Keep the canonical nine tools as deny so a Plan
129
+ // Mode prompt injection cannot trigger them.
130
+ "task.plan-agent": DENY_DEFAULT,
131
+ "task.build-agent": DENY_DEFAULT
132
+ }
133
+ };
134
+ }
135
+ var PLAN_PATH_PREFIX, DENY_DEFAULT, ALLOW_PLANS, PLANS_GLOB;
136
+ var init_plan_mode_permissions = __esm({
137
+ "src/installer/plan-mode-permissions.js"() {
138
+ PLAN_PATH_PREFIX = ".git/opencode-ship/plans";
139
+ DENY_DEFAULT = "deny";
140
+ ALLOW_PLANS = "allow";
141
+ PLANS_GLOB = `${PLAN_PATH_PREFIX}/**`;
142
+ }
143
+ });
144
+
113
145
  // src/installer/root-config.js
114
146
  var root_config_exports = {};
115
147
  __export(root_config_exports, {
148
+ PLAN_MODE_POINTER: () => PLAN_MODE_POINTER,
116
149
  POINTER_ENTRIES: () => POINTER_ENTRIES,
117
150
  applyOwnedPointers: () => applyOwnedPointers,
151
+ applyPlanModeOwnership: () => applyPlanModeOwnership,
118
152
  defaultRootConfigPath: () => defaultRootConfigPath,
119
153
  findRootConfig: () => findRootConfig,
120
154
  formatRootConfig: () => formatRootConfig,
121
155
  formatRootConfigPreserving: () => formatRootConfigPreserving,
122
156
  parseRootConfigPreservingOrder: () => parseRootConfigPreservingOrder,
157
+ planModeBlock: () => planModeBlock,
123
158
  readRootConfig: () => readRootConfig,
124
159
  synthesizeDefaultRootConfig: () => synthesizeDefaultRootConfig
125
160
  });
@@ -229,6 +264,14 @@ function applyOwnedPointers(rootDoc, { pointerEntries = POINTER_ENTRIES, allowEq
229
264
  }
230
265
  return result;
231
266
  }
267
+ function applyPlanModeOwnership(rootDoc, { pointer = PLAN_MODE_POINTER, block = planModePermissions().build } = {}) {
268
+ const previous = getPointer(rootDoc, pointer);
269
+ const doc = setPointer(rootDoc, pointer, block);
270
+ return { doc, previous: previous === void 0 ? null : previous, id: pointer };
271
+ }
272
+ function planModeBlock() {
273
+ return planModePermissions().build;
274
+ }
232
275
  function synthesizeDefaultRootConfig() {
233
276
  return {
234
277
  $schema: "https://opencode.ai/config.json",
@@ -292,11 +335,12 @@ function parseRootConfigPreservingOrder(text) {
292
335
  const isJsonc = text.includes("//") || text.includes("/*");
293
336
  return { value, format: isJsonc ? "jsonc" : "json" };
294
337
  }
295
- var POINTER_ENTRIES, ROOT_PATH_CANDIDATES, RootConfigParser;
338
+ var POINTER_ENTRIES, ROOT_PATH_CANDIDATES, PLAN_MODE_POINTER, RootConfigParser;
296
339
  var init_root_config = __esm({
297
340
  "src/installer/root-config.js"() {
298
341
  init_json_pointer();
299
342
  init_hash();
343
+ init_plan_mode_permissions();
300
344
  POINTER_ENTRIES = [
301
345
  {
302
346
  pointer: "/agent/build/permission/delivery_inspect",
@@ -355,6 +399,7 @@ var init_root_config = __esm({
355
399
  }
356
400
  ];
357
401
  ROOT_PATH_CANDIDATES = ["opencode.json", "opencode.jsonc"];
402
+ PLAN_MODE_POINTER = "/agent/plan/permission";
358
403
  RootConfigParser = class {
359
404
  constructor(text) {
360
405
  this.text = text;
@@ -642,7 +687,7 @@ function resolvePackageRoot(startUrl) {
642
687
  import { readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
643
688
  import { dirname as dirname2, resolve as resolve2 } from "node:path";
644
689
  import { fileURLToPath as fileURLToPath2 } from "node:url";
645
- var PACKAGE_VERSION = "0.5.0";
690
+ var PACKAGE_VERSION = "0.7.0";
646
691
  var TEMPLATE_SET = `v${PACKAGE_VERSION}`;
647
692
 
648
693
  // src/installer/catalog.js
@@ -1267,7 +1312,7 @@ async function planConfigSynthesis({ repoRoot, detection, lock, forceOverwrite,
1267
1312
  reason
1268
1313
  };
1269
1314
  }
1270
- async function planRootConfigApply({ repoRoot, lock, forceRepair }) {
1315
+ async function planRootConfigApply({ repoRoot, lock, forceRepair, planMode = null }) {
1271
1316
  const detected = findRootConfig(repoRoot);
1272
1317
  const target = detected.path ?? defaultRootConfigPath(repoRoot);
1273
1318
  const previous = lock?.manager?.rootDocuments?.find((d) => d.path === detected.relative);
@@ -1279,6 +1324,14 @@ async function planRootConfigApply({ repoRoot, lock, forceRepair }) {
1279
1324
  installedSha256: bytesHashString(stableStringify(entry.value)),
1280
1325
  previous: { existed: false }
1281
1326
  }));
1327
+ if (planMode) {
1328
+ seededRecords.push({
1329
+ pointer: planMode.id,
1330
+ strategy: "value",
1331
+ installedSha256: bytesHashString(stableStringify(planMode.block)),
1332
+ previous: { existed: false }
1333
+ });
1334
+ }
1282
1335
  return {
1283
1336
  kind: "noop",
1284
1337
  op: "root-config",
@@ -1290,8 +1343,12 @@ async function planRootConfigApply({ repoRoot, lock, forceRepair }) {
1290
1343
  };
1291
1344
  }
1292
1345
  if (fileMissing && forceRepair) {
1293
- const { synthesizeDefaultRootConfig: synthesizeDefaultRootConfig2, formatRootConfig: formatRootConfig3 } = await Promise.resolve().then(() => (init_root_config(), root_config_exports));
1294
- const doc = synthesizeDefaultRootConfig2();
1346
+ const { synthesizeDefaultRootConfig: synthesizeDefaultRootConfig2, formatRootConfig: formatRootConfig3, applyPlanModeOwnership: applyPlanModeOwnership2 } = await Promise.resolve().then(() => (init_root_config(), root_config_exports));
1347
+ let doc = synthesizeDefaultRootConfig2();
1348
+ if (planMode) {
1349
+ const applied = applyPlanModeOwnership2(doc, { block: planMode.block, pointer: planMode.id });
1350
+ doc = applied.doc;
1351
+ }
1295
1352
  const bytes2 = Buffer.from(formatRootConfig3(doc), "utf8");
1296
1353
  const newSha2 = bytesHashString(stableStringify(doc));
1297
1354
  const installedPointers2 = POINTER_ENTRIES.map((entry) => ({
@@ -1300,6 +1357,14 @@ async function planRootConfigApply({ repoRoot, lock, forceRepair }) {
1300
1357
  installedSha256: bytesHashString(stableStringify(entry.value)),
1301
1358
  previous: { existed: false }
1302
1359
  }));
1360
+ if (planMode) {
1361
+ installedPointers2.push({
1362
+ pointer: planMode.id,
1363
+ strategy: "value",
1364
+ installedSha256: bytesHashString(stableStringify(planMode.block)),
1365
+ previous: { existed: false }
1366
+ });
1367
+ }
1303
1368
  return {
1304
1369
  kind: "create",
1305
1370
  op: "root-config",
@@ -2096,6 +2161,7 @@ function legacyToShipConfig(legacy, detection = null) {
2096
2161
  }
2097
2162
 
2098
2163
  // src/installer/executor.js
2164
+ init_plan_mode_permissions();
2099
2165
  async function readCurrentBytes(targetPath) {
2100
2166
  if (!existsSync11(targetPath)) return null;
2101
2167
  const buf = await readFile7(targetPath);
@@ -2138,7 +2204,8 @@ async function previewInstall({ rootPath, profile = null, replaceManaged, forceC
2138
2204
  migrationReport,
2139
2205
  allowUnowned: Boolean(replaceManaged)
2140
2206
  });
2141
- const rootPlan = await planRootConfigApply({ repoRoot, lock, forceRepair: Boolean(forceRootConfig) });
2207
+ const planMode = resolved.profile === "engineering" ? { id: "/agent/plan/permission", block: planModePermissions().build } : null;
2208
+ const rootPlan = await planRootConfigApply({ repoRoot, lock, forceRepair: Boolean(forceRootConfig), planMode });
2142
2209
  const plan = [...filePlan ?? [], ...migrationPlan, configPlan, rootPlan];
2143
2210
  const conflicts = plan.filter((p) => p && p.kind === "conflict");
2144
2211
  const summary = summarise(plan);
@@ -2219,7 +2286,7 @@ async function assembleLock({ repoRoot, plan, lock, configPlan, rootPlan, profil
2219
2286
  manager: {
2220
2287
  schemaVersion: CURRENT_LOCK_SCHEMA,
2221
2288
  name: "opencode-ship",
2222
- version: "0.5.0",
2289
+ version: "0.7.0",
2223
2290
  templateSet: TEMPLATE_SET_ID,
2224
2291
  // Newly written locks always carry the resolved profile so
2225
2292
  // future CLI invocations without --profile still resolve to
package/dist/core.js CHANGED
@@ -1,4 +1,4 @@
1
- // opencode-ship/core v0.5.0
1
+ // opencode-ship/core v0.7.0
2
2
 
3
3
  // src/adapter.js
4
4
  import { readFile, writeFile, mkdir, rename } from "node:fs/promises";
package/dist/plugin.js CHANGED
@@ -1,4 +1,4 @@
1
- // opencode-ship v0.5.0
1
+ // opencode-ship v0.7.0
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
4
  var __esm = (fn, res) => function __init() {
@@ -14897,7 +14897,7 @@ function flattenShipConfig(ship) {
14897
14897
  import { readFileSync as readFileSync2, existsSync as existsSync6 } from "node:fs";
14898
14898
  import { dirname as dirname5, resolve as resolve11 } from "node:path";
14899
14899
  import { fileURLToPath } from "node:url";
14900
- var PACKAGE_VERSION = "0.5.0";
14900
+ var PACKAGE_VERSION = "0.7.0";
14901
14901
  var TEMPLATE_SET = `v${PACKAGE_VERSION}`;
14902
14902
 
14903
14903
  // src/plugin.js
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-ship",
3
- "version": "0.5.0",
3
+ "version": "0.7.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",