@kody-ade/kody-engine 0.3.9 → 0.3.11

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/dist/bin/kody.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // package.json
4
4
  var package_default = {
5
5
  name: "@kody-ade/kody-engine",
6
- version: "0.3.9",
6
+ version: "0.3.11",
7
7
  description: "kody \u2014 autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
8
8
  license: "MIT",
9
9
  type: "module",
@@ -795,6 +795,7 @@ function coerceBare(spec, value) {
795
795
  }
796
796
 
797
797
  // src/executor.ts
798
+ import { spawnSync as spawnSync2 } from "child_process";
798
799
  import * as fs21 from "fs";
799
800
  import * as path18 from "path";
800
801
 
@@ -977,7 +978,7 @@ function loadProfile(profilePath) {
977
978
  function validateScriptReferences(profile, registeredScripts) {
978
979
  const missing = [];
979
980
  for (const e of [...profile.scripts.preflight, ...profile.scripts.postflight]) {
980
- if (!registeredScripts.has(e.script)) missing.push(e.script);
981
+ if (e.script && !registeredScripts.has(e.script)) missing.push(e.script);
981
982
  }
982
983
  return missing;
983
984
  }
@@ -1137,11 +1138,20 @@ function parseScriptList(p, key, raw) {
1137
1138
  const out = [];
1138
1139
  for (const [i, item] of raw.entries()) {
1139
1140
  if (!item || typeof item !== "object") {
1140
- throw new ProfileError(p, `scripts.${key}[${i}] must be an object like { script, runWhen? }`);
1141
+ throw new ProfileError(p, `scripts.${key}[${i}] must be an object like { script, runWhen? } or { shell, runWhen? }`);
1141
1142
  }
1142
1143
  const r = item;
1143
- const script = requireString(p, r, "script");
1144
- const entry = { script };
1144
+ const hasScript = typeof r.script === "string" && r.script.length > 0;
1145
+ const hasShell = typeof r.shell === "string" && r.shell.length > 0;
1146
+ if (hasScript && hasShell) {
1147
+ throw new ProfileError(p, `scripts.${key}[${i}] cannot set both "script" and "shell" \u2014 pick one`);
1148
+ }
1149
+ if (!hasScript && !hasShell) {
1150
+ throw new ProfileError(p, `scripts.${key}[${i}] must set "script" (registered TS function) or "shell" (filename in executable dir)`);
1151
+ }
1152
+ const entry = {};
1153
+ if (hasScript) entry.script = r.script;
1154
+ if (hasShell) entry.shell = r.shell;
1145
1155
  if (r.runWhen && typeof r.runWhen === "object") {
1146
1156
  entry.runWhen = r.runWhen;
1147
1157
  }
@@ -4327,6 +4337,24 @@ function lastReleaseTag(cwd) {
4327
4337
  return null;
4328
4338
  }
4329
4339
  }
4340
+ function remoteBranchExists(branch, cwd) {
4341
+ try {
4342
+ const out = git3(["ls-remote", "--heads", "origin", branch], cwd, 3e4);
4343
+ return out.length > 0;
4344
+ } catch {
4345
+ return false;
4346
+ }
4347
+ }
4348
+ function findOpenPrForBranch(branch, cwd) {
4349
+ try {
4350
+ const out = gh2(["pr", "list", "--head", branch, "--state", "open", "--json", "url", "--limit", "1"], { cwd });
4351
+ const parsed = JSON.parse(out || "[]");
4352
+ const first = parsed[0];
4353
+ return first?.url ?? null;
4354
+ } catch {
4355
+ return null;
4356
+ }
4357
+ }
4330
4358
  function runShell(cmd, cwd, timeoutMs) {
4331
4359
  const r = spawnSync(cmd, {
4332
4360
  cwd,
@@ -4341,6 +4369,7 @@ var releaseFlow = async (ctx) => {
4341
4369
  const mode = ctx.args.mode ?? "prepare";
4342
4370
  const bump = ctx.args.bump ?? "patch";
4343
4371
  const dryRun = ctx.args["dry-run"] === true || ctx.args.dryRun === true;
4372
+ const prefer = ctx.args.prefer ?? void 0;
4344
4373
  const issueNumber = typeof ctx.args.issue === "number" ? ctx.args.issue : void 0;
4345
4374
  const cwd = ctx.cwd;
4346
4375
  const releaseCfg = ctx.config.release ?? {};
@@ -4348,7 +4377,7 @@ var releaseFlow = async (ctx) => {
4348
4377
  const timeoutMs = releaseCfg.timeoutMs ?? 6e5;
4349
4378
  ctx.skipAgent = true;
4350
4379
  if (mode === "prepare") {
4351
- await runPrepare({ cwd, bump, dryRun, versionFiles, ctx });
4380
+ await runPrepare({ cwd, bump, dryRun, prefer, versionFiles, ctx });
4352
4381
  } else if (mode === "finalize") {
4353
4382
  await runFinalize({ cwd, dryRun, timeoutMs, releaseCfg, ctx });
4354
4383
  } else {
@@ -4378,7 +4407,7 @@ function buildIssueNotice(mode, dryRun, ctx) {
4378
4407
  return `\u2705 kody ${label} complete`;
4379
4408
  }
4380
4409
  async function runPrepare(args) {
4381
- const { cwd, bump, dryRun, versionFiles, ctx } = args;
4410
+ const { cwd, bump, dryRun, prefer, versionFiles, ctx } = args;
4382
4411
  const pkgPath = path17.join(cwd, "package.json");
4383
4412
  if (!fs19.existsSync(pkgPath)) {
4384
4413
  ctx.output.exitCode = 99;
@@ -4398,11 +4427,35 @@ async function runPrepare(args) {
4398
4427
  `);
4399
4428
  if (dryRun) {
4400
4429
  ctx.output.exitCode = 0;
4401
- ctx.output.reason = `dry-run \u2014 would bump to ${newVersion}`;
4430
+ ctx.output.reason = `dry-run \u2014 would bump to ${newVersion}${prefer ? ` (--prefer ${prefer})` : ""}`;
4402
4431
  process.stdout.write(`RELEASE_PLAN=bump=${newVersion} tag=${tag}
4403
4432
  `);
4404
4433
  return;
4405
4434
  }
4435
+ const releaseBranch = `release/${tag}`;
4436
+ const collides = remoteBranchExists(releaseBranch, cwd);
4437
+ if (collides) {
4438
+ if (prefer === "theirs") {
4439
+ const existingPr = findOpenPrForBranch(releaseBranch, cwd);
4440
+ if (existingPr) {
4441
+ process.stdout.write(` reusing existing PR (--prefer theirs): ${existingPr}
4442
+ `);
4443
+ ctx.output.prUrl = existingPr;
4444
+ ctx.output.exitCode = 0;
4445
+ return;
4446
+ }
4447
+ ctx.output.exitCode = 4;
4448
+ ctx.output.reason = `release prepare --prefer theirs: ${releaseBranch} exists on remote but has no open PR \u2014 nothing to reuse`;
4449
+ return;
4450
+ }
4451
+ if (prefer !== "ours") {
4452
+ ctx.output.exitCode = 4;
4453
+ ctx.output.reason = `release prepare: branch ${releaseBranch} already exists on remote. Use --prefer ours to force-push, or --prefer theirs to reuse the existing PR.`;
4454
+ return;
4455
+ }
4456
+ process.stdout.write(` branch ${releaseBranch} exists on remote \u2014 will force-push (--prefer ours)
4457
+ `);
4458
+ }
4406
4459
  const touched = [];
4407
4460
  for (const f of versionFiles) {
4408
4461
  if (updateVersionInFile(f, newVersion, cwd)) touched.push(f);
@@ -4418,12 +4471,12 @@ async function runPrepare(args) {
4418
4471
  prependChangelog(cwd, entry);
4419
4472
  process.stdout.write(` wrote CHANGELOG.md
4420
4473
  `);
4421
- const releaseBranch = `release/${tag}`;
4422
4474
  try {
4423
4475
  git3(["checkout", "-b", releaseBranch], cwd);
4424
4476
  for (const f of [...touched, "CHANGELOG.md"]) git3(["add", "--", f], cwd);
4425
4477
  git3(["commit", "--no-gpg-sign", "-m", `chore: release ${tag}`], cwd);
4426
- git3(["push", "-u", "origin", releaseBranch], cwd, 12e4);
4478
+ const pushArgs = collides && prefer === "ours" ? ["push", "-u", "--force-with-lease", "origin", releaseBranch] : ["push", "-u", "origin", releaseBranch];
4479
+ git3(pushArgs, cwd, 12e4);
4427
4480
  } catch (err) {
4428
4481
  const msg = err instanceof Error ? err.message : String(err);
4429
4482
  ctx.output.exitCode = 4;
@@ -4442,16 +4495,23 @@ ${rawEntry}
4442
4495
 
4443
4496
  Merge this and then run \`kody release --mode finalize\`.`;
4444
4497
  let prUrl = "";
4445
- try {
4446
- prUrl = gh2(["pr", "create", "--head", releaseBranch, "--base", base, "--title", title, "--body-file", "-"], {
4447
- input: body,
4448
- cwd
4449
- }).trim();
4450
- } catch (err) {
4451
- const msg = err instanceof Error ? err.message : String(err);
4452
- ctx.output.exitCode = 4;
4453
- ctx.output.reason = `release prepare: gh pr create failed: ${msg}`;
4454
- return;
4498
+ const preexistingPr = collides && prefer === "ours" ? findOpenPrForBranch(releaseBranch, cwd) : null;
4499
+ if (preexistingPr) {
4500
+ process.stdout.write(` PR already open for ${releaseBranch}: ${preexistingPr}
4501
+ `);
4502
+ prUrl = preexistingPr;
4503
+ } else {
4504
+ try {
4505
+ prUrl = gh2(["pr", "create", "--head", releaseBranch, "--base", base, "--title", title, "--body-file", "-"], {
4506
+ input: body,
4507
+ cwd
4508
+ }).trim();
4509
+ } catch (err) {
4510
+ const msg = err instanceof Error ? err.message : String(err);
4511
+ ctx.output.exitCode = 4;
4512
+ ctx.output.reason = `release prepare: gh pr create failed: ${msg}`;
4513
+ return;
4514
+ }
4455
4515
  }
4456
4516
  ctx.output.prUrl = prUrl;
4457
4517
  ctx.output.exitCode = 0;
@@ -4681,6 +4741,7 @@ var resolveFlow = async (ctx) => {
4681
4741
  }
4682
4742
  ctx.data.conflictedFiles = conflictedFiles;
4683
4743
  ctx.data.conflictMarkersPreview = getConflictMarkersPreview(conflictedFiles, ctx.cwd);
4744
+ ctx.data.preferBlock = buildPreferBlock(ctx.args.prefer, baseBranch);
4684
4745
  const runUrl = getRunUrl();
4685
4746
  const runSuffix = runUrl ? `, run ${runUrl}` : "";
4686
4747
  tryPostPr3(
@@ -4689,6 +4750,23 @@ var resolveFlow = async (ctx) => {
4689
4750
  ctx.cwd
4690
4751
  );
4691
4752
  };
4753
+ function buildPreferBlock(prefer, baseBranch) {
4754
+ if (prefer !== "ours" && prefer !== "theirs") return "";
4755
+ const keepSide = prefer === "ours" ? "HEAD (this PR branch)" : `origin/${baseBranch} (base branch)`;
4756
+ const keepMarkers = prefer === "ours" ? "content between `<<<<<<< HEAD` and `=======`" : `content between \`=======\` and \`>>>>>>> origin/${baseBranch}\``;
4757
+ const dropSide = prefer === "ours" ? `origin/${baseBranch}` : "HEAD";
4758
+ return [
4759
+ "# Conflict resolution directive (AUTHORITATIVE \u2014 overrides defaults below)",
4760
+ "",
4761
+ `The user requested \`--prefer ${prefer}\`. For **every** conflict in **every** file:`,
4762
+ "",
4763
+ `- Keep the **${prefer}** side: ${keepSide} \u2014 ${keepMarkers}.`,
4764
+ `- Discard the **${prefer === "ours" ? "theirs" : "ours"}** side (from ${dropSide}) entirely.`,
4765
+ "- Remove all `<<<<<<<`, `=======`, `>>>>>>>` markers.",
4766
+ "- Do NOT attempt to merge the two sides or apply judgement.",
4767
+ ""
4768
+ ].join("\n");
4769
+ }
4692
4770
  function getConflictedFiles(cwd) {
4693
4771
  try {
4694
4772
  const out = execFileSync17("git", ["diff", "--name-only", "--diff-filter=U"], {
@@ -5373,9 +5451,13 @@ async function runExecutable(profileName, input) {
5373
5451
  try {
5374
5452
  for (const entry of profile.scripts.preflight) {
5375
5453
  if (!shouldRun(entry, ctx)) continue;
5376
- const fn = preflightScripts[entry.script];
5377
- if (!fn) return finish({ exitCode: 99, reason: `preflight script not registered: ${entry.script}` });
5378
- await fn(ctx, profile, entry.with);
5454
+ if (entry.shell) {
5455
+ runShellEntry(entry, ctx, profile);
5456
+ } else {
5457
+ const fn = preflightScripts[entry.script];
5458
+ if (!fn) return finish({ exitCode: 99, reason: `preflight script not registered: ${entry.script}` });
5459
+ await fn(ctx, profile, entry.with);
5460
+ }
5379
5461
  if (ctx.skipAgent && ctx.output.exitCode !== void 0 && ctx.output.exitCode !== 0) {
5380
5462
  return finish(ctx.output);
5381
5463
  }
@@ -5390,15 +5472,20 @@ async function runExecutable(profileName, input) {
5390
5472
  }
5391
5473
  for (const entry of profile.scripts.postflight) {
5392
5474
  if (!shouldRun(entry, ctx)) continue;
5393
- const fn = postflightScripts[entry.script];
5394
- if (!fn) return finish({ exitCode: 99, reason: `postflight script not registered: ${entry.script}` });
5475
+ const label = entry.script ?? entry.shell ?? "<unknown>";
5395
5476
  try {
5396
- await fn(ctx, profile, agentResult, entry.with);
5477
+ if (entry.shell) {
5478
+ runShellEntry(entry, ctx, profile);
5479
+ } else {
5480
+ const fn = postflightScripts[entry.script];
5481
+ if (!fn) return finish({ exitCode: 99, reason: `postflight script not registered: ${entry.script}` });
5482
+ await fn(ctx, profile, agentResult, entry.with);
5483
+ }
5397
5484
  } catch (err) {
5398
5485
  const msg = err instanceof Error ? err.message : String(err);
5399
- process.stderr.write(`[kody] postflight script "${entry.script}" crashed: ${msg}
5486
+ process.stderr.write(`[kody] postflight "${label}" crashed: ${msg}
5400
5487
  `);
5401
- if (!ctx.output.reason) ctx.output.reason = `postflight ${entry.script} crashed: ${msg}`;
5488
+ if (!ctx.output.reason) ctx.output.reason = `postflight ${label} crashed: ${msg}`;
5402
5489
  if (ctx.output.exitCode === 0) ctx.output.exitCode = 99;
5403
5490
  }
5404
5491
  }
@@ -5515,6 +5602,48 @@ function finish(out) {
5515
5602
  `);
5516
5603
  return out;
5517
5604
  }
5605
+ var SHELL_TIMEOUT_MS = 3e5;
5606
+ function runShellEntry(entry, ctx, profile) {
5607
+ const shellName = entry.shell;
5608
+ const shellPath = path18.join(profile.dir, shellName);
5609
+ if (!fs21.existsSync(shellPath)) {
5610
+ ctx.skipAgent = true;
5611
+ ctx.output.exitCode = 99;
5612
+ ctx.output.reason = `shell script not found: ${shellName} (looked in ${profile.dir})`;
5613
+ return;
5614
+ }
5615
+ const positional = entry.with ? Object.values(entry.with).map((v) => String(v)) : [];
5616
+ const env = { ...process.env, HUSKY: "0", SKIP_HOOKS: "1" };
5617
+ for (const [k, v] of Object.entries(ctx.args)) {
5618
+ if (v === void 0 || v === null) continue;
5619
+ env[`KODY_ARG_${k.toUpperCase().replace(/-/g, "_")}`] = String(v);
5620
+ }
5621
+ const r = spawnSync2("bash", [shellPath, ...positional], {
5622
+ cwd: ctx.cwd,
5623
+ encoding: "utf-8",
5624
+ env,
5625
+ stdio: ["pipe", "pipe", "pipe"],
5626
+ timeout: SHELL_TIMEOUT_MS
5627
+ });
5628
+ const stdout = r.stdout ?? "";
5629
+ const stderr = r.stderr ?? "";
5630
+ if (stdout) process.stdout.write(stdout);
5631
+ if (stderr) process.stderr.write(stderr);
5632
+ if (/^KODY_SKIP_AGENT=true\s*$/m.test(stdout)) {
5633
+ ctx.skipAgent = true;
5634
+ }
5635
+ const exit = r.status ?? -1;
5636
+ if (exit !== 0) {
5637
+ ctx.skipAgent = true;
5638
+ if (ctx.output.exitCode === void 0 || ctx.output.exitCode === 0) {
5639
+ ctx.output.exitCode = exit;
5640
+ }
5641
+ if (!ctx.output.reason) {
5642
+ const tail = (stderr || stdout).slice(-800);
5643
+ ctx.output.reason = `shell '${shellName}' exited ${exit}${tail ? `: ${tail}` : ""}`;
5644
+ }
5645
+ }
5646
+ }
5518
5647
 
5519
5648
  // src/kody-cli.ts
5520
5649
  var CI_HELP = `kody ci \u2014 minimal-YAML autonomous engineer (CI preflight + run)
@@ -40,6 +40,17 @@
40
40
  "type": "int",
41
41
  "required": false,
42
42
  "describe": "Issue number to post success/failure follow-up on. Auto-populated by dispatch when triggered via @kody comment."
43
+ },
44
+ {
45
+ "name": "prefer",
46
+ "flag": "--prefer",
47
+ "type": "enum",
48
+ "values": [
49
+ "ours",
50
+ "theirs"
51
+ ],
52
+ "required": false,
53
+ "describe": "On release/vX.Y.Z branch collision (prepare mode): 'ours' force-pushes over the remote branch; 'theirs' reuses the existing branch and its open PR. Default (unset): refuse on non-ff."
43
54
  }
44
55
  ],
45
56
  "claudeCode": {
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Deterministic conflict resolution for `kody resolve --prefer ours|theirs`.
4
+ #
5
+ # Runs as a preflight shell entry (see src/executables/resolve/profile.json).
6
+ # Gated by runWhen on args.prefer, so it only fires when the user requested
7
+ # a side. Reads the side from $KODY_ARG_PREFER (the executor exposes every
8
+ # ctx.args.<key> as an env var).
9
+ #
10
+ # Preconditions set by the prior `resolveFlow` preflight:
11
+ # - cwd is on the PR branch.
12
+ # - `git merge origin/<base> --no-edit --no-ff` already ran and conflicted.
13
+ # - Working tree has unmerged paths.
14
+ #
15
+ # Behavior: for each unmerged file, git checkout --ours (or --theirs), add,
16
+ # commit the merge, push the branch. Prints `KODY_SKIP_AGENT=true` on
17
+ # success so the executor bypasses the agent entirely.
18
+ #
19
+ # Exits:
20
+ # 0 — resolved + pushed (or nothing to resolve)
21
+ # 64 — invalid side value
22
+ # 1+ — git operation failed (executor will surface stderr)
23
+
24
+ set -euo pipefail
25
+
26
+ side="${KODY_ARG_PREFER:-}"
27
+ if [[ "$side" != "ours" && "$side" != "theirs" ]]; then
28
+ echo "apply-prefer: expected KODY_ARG_PREFER=ours|theirs, got '$side'" >&2
29
+ exit 64
30
+ fi
31
+
32
+ unmerged=$(git diff --name-only --diff-filter=U)
33
+ if [[ -z "$unmerged" ]]; then
34
+ echo "apply-prefer: no unmerged paths — nothing to resolve"
35
+ echo "KODY_SKIP_AGENT=true"
36
+ exit 0
37
+ fi
38
+
39
+ count=0
40
+ while IFS= read -r f; do
41
+ [[ -z "$f" ]] && continue
42
+ git checkout "--$side" -- "$f"
43
+ git add -- "$f"
44
+ count=$((count + 1))
45
+ done <<< "$unmerged"
46
+ echo "apply-prefer: resolved $count file(s) via --$side"
47
+
48
+ # Complete the merge. git merge left MERGE_MSG in place; --no-edit uses it.
49
+ HUSKY=0 SKIP_HOOKS=1 git -c commit.gpgsign=false commit --no-edit
50
+
51
+ branch=$(git rev-parse --abbrev-ref HEAD)
52
+ git push origin "$branch"
53
+ echo "apply-prefer: pushed $branch"
54
+ echo "KODY_SKIP_AGENT=true"
@@ -9,6 +9,14 @@
9
9
  "type": "int",
10
10
  "required": true,
11
11
  "describe": "GitHub PR number whose branch has conflicts with the default branch."
12
+ },
13
+ {
14
+ "name": "prefer",
15
+ "flag": "--prefer",
16
+ "type": "enum",
17
+ "values": ["ours", "theirs"],
18
+ "required": false,
19
+ "describe": "Force one side for every conflict: 'ours' = PR branch (HEAD), 'theirs' = base branch. Omit to let the agent merge by judgement."
12
20
  }
13
21
  ],
14
22
  "claudeCode": {
@@ -45,6 +53,10 @@
45
53
  {
46
54
  "script": "resolveFlow"
47
55
  },
56
+ {
57
+ "shell": "apply-prefer.sh",
58
+ "runWhen": { "args.prefer": ["ours", "theirs"] }
59
+ },
48
60
  {
49
61
  "script": "loadTaskState"
50
62
  },
@@ -9,13 +9,13 @@ You are Kody, an autonomous engineer. A `git merge origin/{{baseBranch}}` into P
9
9
 
10
10
  {{conflictedFiles}}
11
11
 
12
- {{conventionsBlock}}{{toolsUsage}}# Working-tree conflict markers (truncated)
12
+ {{preferBlock}}{{conventionsBlock}}{{toolsUsage}}# Working-tree conflict markers (truncated)
13
13
 
14
14
  {{conflictMarkersPreview}}
15
15
 
16
16
  # Required steps
17
17
  1. For each conflicted file: read it, understand both sides of the `<<<<<<<` / `=======` / `>>>>>>>` markers, and produce the correct merged content. Remove all conflict markers.
18
- 2. Preserve the PR's intent (the HEAD side) unless `origin/{{baseBranch}}` made a change that should be preserved (e.g. security fix, renamed API). Use judgement.
18
+ 2. If a conflict resolution directive is given above, follow it exactly — take the specified side for every conflict, no judgement. Otherwise, preserve the PR's intent (the HEAD side) unless `origin/{{baseBranch}}` made a change that should be preserved (e.g. security fix, renamed API), and use judgement.
19
19
  3. After resolving, run the quality commands with Bash and fix any issues YOUR resolution introduced.
20
20
  4. Final message format (or `FAILED: <reason>` on failure):
21
21
 
@@ -159,7 +159,20 @@ export interface CliToolSpec {
159
159
  }
160
160
 
161
161
  export interface ScriptEntry {
162
- script: string
162
+ /**
163
+ * Name of a registered TS function in src/scripts/index.ts. Mutually
164
+ * exclusive with `shell` — exactly one must be set.
165
+ */
166
+ script?: string
167
+ /**
168
+ * Filename of a shell script colocated with the executable
169
+ * (e.g. "apply-prefer.sh"). Resolved relative to the profile's
170
+ * directory. Invoked via `bash <path> <with-args>` with ctx.args
171
+ * exposed as env vars (KODY_ARG_<UPPER_NAME>=<value>). A stdout
172
+ * line `KODY_SKIP_AGENT=true` signals the executor to bypass the
173
+ * agent. Non-zero exit is treated as a preflight failure.
174
+ */
175
+ shell?: string
163
176
  /**
164
177
  * Optional conditional. Keys are dotted paths into the context (e.g.
165
178
  * "args.mode"). Values are a single primitive or an array of primitives.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine",
3
- "version": "0.3.9",
3
+ "version": "0.3.11",
4
4
  "description": "kody — autonomous development engine. Single-session Claude Code agent behind a generic executor + declarative executable profiles.",
5
5
  "license": "MIT",
6
6
  "type": "module",