SLOPSHOPPER

feedback-gate

Claude Code hooks that inject feedback rules, enforce them on tool calls and at stop time, and run per-project quality gates defined in .claude/gate.yaml

newbandstatusprocesstimer
v0.4.0MITupdated 2026-10-09nonchan7720/claude-hook-gate
A shopper browsing a rack in a slop shop
README

feedback-gate

日本語

A Claude Code plugin made of function hooks. It does three things:

  • Injects your feedback rules (~/.claude/feedback/*.md) into each prompt and enforces them on tool calls and when Claude stops.
  • Runs per-project quality gates (lint, tests, etc.) defined in .claude/gate.yaml after file edits and when Claude stops.
  • Sends a desktop notification when Claude waits for you or stops (macOS).

Hooks

The hooks are registered in src/register.ts (bundled into hooks/register.js by CI; that bundle is what Claude Code loads) and call the TypeScript modules under src/.

EventModuleRole
SessionStartsrc/reset-gate.tsClears this session's gate state on startup / clear; keeps it on resume / compact.
UserPromptSubmitsrc/rules-file.ts + src/feedback-inject.tsAdds the rules file (your rulesFile if it exists, otherwise the bundled rules/feedback_rules.md) and the confirmed feedback rules (count >= 3) to the context.
PreToolUse (Bash / Edit / Write / MultiEdit)src/feedback-guard.tsEvaluates pre_bash / pre_edit enforce entries; denies, asks or warns by count.
PostToolUse (Write / Edit / MultiEdit)src/record-changes.ts, src/feedback-post-edit.ts, then src/stop-test-gate.ts (rules)Records changed files, evaluates post_edit enforce entries against the edited file as a whole (blocks or warns by count), then runs the matching rules of .claude/gate.yaml (via src/gate.ts).
PreToolUse (Bash)src/bash-changes.ts (bashStarted), before feedback-guardNotes when the command started (bash_started.<id>.json).
PostToolUse (Bash)src/bash-changes.ts, then src/stop-test-gate.ts (rules)Records only the files the command actually rewrote (git-modified or untracked files whose mtime is at or after the command start), then runs the matching rules only if something was recorded. Git history / working-tree operations (rebase / checkout / merge etc.) are never recorded.
PreToolUse (Agent / SendMessage), opt-insrc/agent-launch-guard.tsOnly when agentLaunchGuard is true: asks before sending instructions to a subagent, showing the full text.
Notificationsrc/notification.ts (notify)Desktop notification when Claude waits for you (needs terminal-notifier, macOS).
Stopsrc/all-stop.tsRuns, in order: the gate checks phase, src/feedback-stop-check.ts, then the stop notification.
SubagentStopgate checks phase, src/feedback-stop-check.tsSame gate and stop_check rules for subagents.

Agent launch guard (optional)

src/agent-launch-guard.ts is active only when the agentLaunchGuard option (userConfig) is true. It is false by default. When enabled, it runs on PreToolUse for Agent and SendMessage and returns ask, putting the full text that would be sent into the confirmation reason so you can review it before the subagent receives it:

  • Agent: asks when subagent_type is code-implementer, showing the prompt.
  • SendMessage: asks for every recipient except git-operator, showing the message.

Anything else passes through to the normal permission flow. The target agent types and the allowed recipients are the ASK_AGENT_TYPES and ALLOW_RECIPIENTS arrays at the top of src/agent-launch-guard.ts.

To enable it, turn on agentLaunchGuard on the settings screen shown when you install the plugin with /plugin, or change it later in the /config menu (the plugin's agentLaunchGuard row).

flowchart LR
  P[UserPromptSubmit] --> I[feedback-inject]
  T[PreToolUse: Bash / Edit / Write / MultiEdit] --> G[feedback-guard]
  T2["PreToolUse: Agent / SendMessage<br/>(agentLaunchGuard = true)"] --> AL[agent-launch-guard]
  E[PostToolUse] --> R[record-changes]
  R --> PE[feedback-post-edit]
  PE --> S[stop-test-gate: rules]
  S --> SG[gate]
  X[Stop] --> A[all-stop]
  A --> C[stop-test-gate: checks]
  A --> F[feedback-stop-check]
  C --> SG

Install

/plugin install feedback-gate --marketplace nonchan7720/claude-hook-gate

Answer y to add the marketplace, then choose the scope.

Requirements

Nothing to install: the hooks are plain TypeScript, bundled into one JavaScript file that Claude Code runs itself (no Python, bash or jq).

  • terminal-notifier (optional, macOS): needed only for the desktop notifications.
  • dogwood (optional; an AWS tool, source at <https://github.com/dogwood-policy/dogwood>). Build and install it with cargo install --git https://github.com/dogwood-policy/dogwood amzn-dogwood-cli (crate amzn-dogwood-cli, binary dogwood). The gate uses it to decide whether each .claude/gate.yaml command runs now or is deferred. It is looked up in DOGWOOD_BIN, PATH, ~/.cargo/bin/dogwood and ~/.local/share/mise/shims/dogwood, in that order. When it is not found, commands under the bundled default policy run every time, while commands under a policy: you set are skipped and deferred.

What it reads

  • .claude/gate.yaml in the project (opt-in: without it, the gate does nothing). The format is described by scripts/gate.schema.json; the default policy is in scripts/dogwood/.
  • ~/.claude/feedback/*.md: feedback rules with frontmatter (name, description, type, count, optional enforce, expires, projects, when_exists). Rules with count >= 3 are injected and enforced. expires (YYYY-MM-DD, valid through the end of that day in UTC, or an ISO 8601 datetime) turns a rule off after that point; projects (a string or list of globs matched against the project directory's absolute path, a leading ~ expands to HOME) limits it to matching projects; when_exists (a string or list of globs relative to the project root) limits it to projects where at least one exists. A rule is active only when all three that are set are satisfied; an unparsable expires is ignored. These filters apply only to the merged global + project lookup, and an expired project rule also hides the global rule of the same name. Override the directory with CLAUDE_FEEDBACK_DIR. The project's .claude/feedback/*.md is read as well; when a project rule has the same name as a global one, the project rule takes precedence.
  • The rules file, added to the context on every prompt. By default the bundled rules/feedback_rules.md is used. To replace it entirely, put your own file at the rulesFile path (default ~/.claude/feedback-gate/feedback_rules.md, configurable in the plugin settings; a leading ~ is expanded). If that file exists it is used instead of the bundled one (never both). Avoid ~/.claude/rules/: Claude Code loads that directory itself, so the rules would be injected twice.

State files (changed files, attempt counters, command logs) are written under the project's .claude/.gate-status/ directory (gate.yaml stays directly under .claude/). Add .claude/.gate-status/ to the project's .gitignore. State files left by older versions directly under .claude/ (and .claude/hooks/logs/) are removed automatically at session start. bash_started.*.json there holds the start time of the Bash command in flight, used to tell which files that command rewrote.

While gate commands run, a band above the prompt shows what is running, one item per line, e.g.

[gate] 実行中:
  typecheck ✓ (0.7s)
  lint ✗ (0.2s)
  test $ bun run test (8s)

Finished commands stay in start order with ✓ for success or ✗ for failure and their fixed duration; a running one shows name $ cmd and its elapsed seconds, refreshed every second. A multi-line command is folded to its first line, and a long one is cut with ... so each line stays around 80 characters. The band yields to the engine's own band while a survey is shown.

The band is shared by the agents of one session (the main agent and its subagents): their running commands are merged into one list, e.g. lint $ bun lint (3s) and test $ bun test 待機中 (2s) under [gate] 実行中:. 待機中 marks a command that is waiting for the same run in another agent (see below). The band disappears only when every agent of the session has finished; while another agent of the session is still running, its list stays. Other Claude Code sessions open on the same project (for example in another terminal) are not shown, and the one-line summary below is kept per session too.

When everything has finished, the band goes away and the result of that round is left on the one-line status line instead, as counts only, e.g. [gate] 完了: ✓ 5 / ✗ 1 (81.0s) (✗ is left out when nothing failed, ✓ when nothing passed; the seconds run from the first start to the last finish, not the sum, since checks run in parallel; check names are not shown, see the Stop hook output for which one failed. The status line cannot hold line breaks or long text, so it stays short). It stays until the next gate command starts running and the band appears again. Feedback checks still use the status line while they run: [feedback-guard] 評価中: <rule> or [feedback-stop-check] 検査中: <rule> (<file>), cleared when the hook finishes.

Sharing runs between agents

When several agents (the main session and subagents) run the same command at the same time, the gate starts it once. A command is the same when the root, the cwd, the cmd and the environment passed to it (such as CLAUDE_GATE_FILES) all match; the variable that identifies the agent (CLAUDE_AGENT_ID) is ignored. The agent that comes later does not start it again: it waits for the running one and receives the same result (exit status, output, timeout). The result is then handled as if that agent had run it itself: it is recorded in its own trace and log directory, it consumes a deferred entry with the same key, and it counts for its phase result. A finished run is never reused; only overlapping runs are merged. Coordination goes through files under .claude/.gate-status/shared/ (a lock directory per command; a lock older than the command's timeout is treated as dead and taken over), because the hooks of different agents are not guaranteed to run in the same process.

To opt a command out, set share: false on its run element ({cmd, name, timeout, share}):

run:
  - { cmd: "bun test", name: test, share: false }

Success report

When the checks phase (Stop / SubagentStop) actually ran at least one command and all of them passed, the gate blocks the stop once and returns a summary as the reason, e.g. [gate] 検証がすべて通りました: lint ✓ / typecheck ✓ / test ✓。この結果をユーザーに報告して終了してください。, so the agent can report the result and finish instead of waiting for you to say it passed. The stop right after such a report (stop_hook_active) runs no gate command and passes. Nothing is reported when no command ran, when stop_hook_active is true (a continuation after a failure block), or when it is turned off with a top-level report_success: false in .claude/gate.yaml (default: on). Failures block as before.

Development

Requires Bun. The hook module runs inside Claude Code, which only resolves relative imports and claude-code; so the source in src/ (plus the yaml package) is bundled into hooks/register.js. hooks/hooks.json points at that bundle. All file and process access goes through the engine's $ API ($.fs, $.process, $.env, $.session), wrapped by the Io interface in src/io.ts; the tests run the same code against Node's fs / child_process.

bun install          # install dev dependencies
bun run lint         # Biome (lint + format check); `bun run format` fixes
bun run typecheck    # tsc --noEmit
bun run test         # bun test (tests/*.spec.ts)
bun run build        # bundle src/register.ts -> hooks/register.js
bun run test:plugin  # build, then run hooks/register.test.ts on the real engine (needs the `claude` CLI)

The bundle hooks/register.js is generated and committed by CI on the pull request; contributors do not commit it (it is not in the repository until CI adds it). The "Build bundle" workflow builds it on every pull request (same-repo branches) and pushes a chore: build bundle commit to the branch, adding the file or updating it if it changed. Run bun run build to produce it locally (e.g. for bun run test:plugin) and leave it uncommitted. Do not edit it by hand. scripts/build.ts fails the build if the bundle imports anything but relative paths and claude-code.

Dependencies have a 7-day freeze period: bunfig.toml sets minimumReleaseAge so bun install only resolves versions published at least 7 days ago, and Dependabot (.github/dependabot.yml) waits 7 days (cooldown) before proposing npm or GitHub Actions updates. Actions are pinned to commit SHAs.

Releases

Versions are managed by release-please. Write commit messages (and PR titles) as Conventional Commits (feat:, fix:, refactor:, chore:, ...): on every push to main the "Release Please" workflow opens or updates a release PR that bumps the version in package.json, .claude-plugin/plugin.json and .claude-plugin/marketplace.json and updates CHANGELOG.md; merging it tags the release. Both workflows use the default GITHUB_TOKEN (no personal access token), so commits made by the bot and the release-please PR do not trigger CI on their own. If you have required status checks, re-run them or push an empty commit by hand.

Source 1 files
hooks/register.js 9279 lines
1// src/io.ts
2var ok = (stdout = "", stderr = "") => ({ exitCode: 0, stdout, stderr });
3
4// src/pyutil.ts
5function truthy(v) {
6  if (Array.isArray(v))
7    return v.length > 0;
8  if (v !== null && typeof v === "object")
9    return Object.keys(v).length > 0;
10  return Boolean(v);
11}
12function pyRepr(v) {
13  if (v === null || v === undefined)
14    return "None";
15  if (v === true)
16    return "True";
17  if (v === false)
18    return "False";
19  if (typeof v === "number")
20    return String(v);
21  if (typeof v === "string") {
22    const quote = v.includes("'") && !v.includes('"') ? '"' : "'";
23    const body = v.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
24    return quote + (quote === "'" ? body.replace(/'/g, "\\'") : body) + quote;
25  }
26  if (Array.isArray(v))
27    return `[${v.map(pyRepr).join(", ")}]`;
28  if (typeof v === "object") {
29    return `{${Object.entries(v).map(([k, x]) => `${pyRepr(k)}: ${pyRepr(x)}`).join(", ")}}`;
30  }
31  return String(v);
32}
33function toCount(v) {
34  if (!truthy(v))
35    return 0;
36  if (typeof v === "number")
37    return Math.trunc(v);
38  if (v === true)
39    return 1;
40  if (typeof v === "string" && /^\s*[+-]?\d+\s*$/.test(v))
41    return Number.parseInt(v, 10);
42  return 0;
43}
44function pyRegExp(source, flags = "") {
45  let src = source;
46  let fl = flags;
47  const m = /^\(\?([aiLmsux]+)\)/.exec(src);
48  if (m) {
49    src = src.slice(m[0].length);
50    for (const c of m[1] ?? "")
51      if ("ims".includes(c) && !fl.includes(c))
52        fl += c;
53  }
54  src = src.replace(/\(\?P<([A-Za-z_]\w*)>/g, "(?<$1>").replace(/\(\?P=([A-Za-z_]\w*)\)/g, "\\k<$1>");
55  return new RegExp(src, fl);
56}
57function jqStr(v) {
58  if (v === null || v === undefined || v === false)
59    return "";
60  return typeof v === "string" ? v : JSON.stringify(v);
61}
62var isDict = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
63
64// src/agent-launch-guard.ts
65var ASK_AGENT_TYPES = ["code-implementer"];
66var ALLOW_RECIPIENTS = ["git-operator"];
67var CHECKLIST = "--- チェック: やること / 背景 / 既存コードの現状 / やらないこと / 完了条件";
68var ask = (reason) => ok(`${JSON.stringify({
69  hookSpecificOutput: {
70    hookEventName: "PreToolUse",
71    permissionDecision: "ask",
72    permissionDecisionReason: reason
73  }
74})}
75`);
76function agentLaunchGuard(payload) {
77  const tool = jqStr(payload.tool_name);
78  const input = isDict(payload.tool_input) ? payload.tool_input : {};
79  if (tool === "Agent") {
80    const agentType = jqStr(input.subagent_type);
81    if (agentType === "")
82      return ok();
83    if (ASK_AGENT_TYPES.includes(agentType)) {
84      return ask(`${agentType} に送るプロンプト:
85
86${jqStr(input.prompt)}
87
88${CHECKLIST}`);
89    }
90  } else if (tool === "SendMessage") {
91    const recipient = jqStr(input.to);
92    if (!ALLOW_RECIPIENTS.includes(recipient)) {
93      return ask(`${recipient || "宛先不明"} に送るメッセージ:
94
95${jqStr(input.message)}
96
97${CHECKLIST}`);
98    }
99  }
100  return ok();
101}
102
103// src/path.ts
104var isAbsolute = (p) => p.startsWith("/");
105function join(...parts) {
106  let out = "";
107  for (const part of parts) {
108    if (part.startsWith("/"))
109      out = part;
110    else if (out === "" || out.endsWith("/"))
111      out += part;
112    else
113      out += `/${part}`;
114  }
115  return out;
116}
117function normpath(p) {
118  if (p === "")
119    return ".";
120  const abs = p.startsWith("/");
121  const out = [];
122  for (const seg of p.split("/")) {
123    if (seg === "" || seg === ".")
124      continue;
125    if (seg === "..") {
126      const last = out[out.length - 1];
127      if (last !== undefined && last !== "..")
128        out.pop();
129      else if (!abs)
130        out.push("..");
131    } else {
132      out.push(seg);
133    }
134  }
135  const joined = out.join("/");
136  if (abs)
137    return `/${joined}`;
138  return joined === "" ? "." : joined;
139}
140function dirname(p) {
141  const i = p.lastIndexOf("/") + 1;
142  const head = p.slice(0, i);
143  if (head !== "" && head !== "/".repeat(head.length))
144    return head.replace(/\/+$/, "");
145  return head;
146}
147function basename(p) {
148  return p.slice(p.lastIndexOf("/") + 1);
149}
150function splitext(p) {
151  const base = basename(p);
152  const dot = base.lastIndexOf(".");
153  if (dot <= 0 || /^\.+$/.test(base.slice(0, dot)))
154    return [p, ""];
155  const cut = p.length - base.length + dot;
156  return [p.slice(0, cut), p.slice(cut)];
157}
158function relpath(path, start) {
159  const a = normpath(path).split("/").filter(Boolean);
160  const b = normpath(start).split("/").filter(Boolean);
161  let i = 0;
162  while (i < a.length && i < b.length && a[i] === b[i])
163    i++;
164  const rel = [...b.slice(i).map(() => ".."), ...a.slice(i)];
165  return rel.length === 0 ? "." : rel.join("/");
166}
167function expandUser(p, home) {
168  if (!home)
169    return p;
170  if (p === "~")
171    return home;
172  if (p.startsWith("~/"))
173    return home.replace(/\/+$/, "") + p.slice(1);
174  return p;
175}
176
177// src/glob.ts
178function splitTopCommas(s) {
179  const parts = [];
180  let depth = 0;
181  let cur = "";
182  for (const c of s) {
183    if (c === "{") {
184      depth++;
185      cur += c;
186    } else if (c === "}") {
187      depth--;
188      cur += c;
189    } else if (c === "," && depth === 0) {
190      parts.push(cur);
191      cur = "";
192    } else {
193      cur += c;
194    }
195  }
196  parts.push(cur);
197  return parts;
198}
199function expandBraces(s) {
200  let depth = 0;
201  let start = -1;
202  for (let i = 0;i < s.length; i++) {
203    const c = s.charAt(i);
204    if (c === "{") {
205      if (depth === 0)
206        start = i;
207      depth++;
208    } else if (c === "}") {
209      depth--;
210      if (depth === 0) {
211        const pre = s.slice(0, start);
212        const inner = s.slice(start + 1, i);
213        const post = s.slice(i + 1);
214        const out = [];
215        for (const part of splitTopCommas(inner))
216          out.push(...expandBraces(pre + part + post));
217        return out;
218      }
219    }
220  }
221  return [s];
222}
223var escapeRegex = (c) => c.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
224function globToRegex(pat) {
225  const n = pat.length;
226  let i = 0;
227  let out = "^";
228  while (i < n) {
229    const c = pat.charAt(i);
230    if (c === "*") {
231      let j = i;
232      while (j < n && pat.charAt(j) === "*")
233        j++;
234      if (j - i >= 2) {
235        if (j < n && pat.charAt(j) === "/") {
236          out += "(?:.*/)?";
237          i = j + 1;
238        } else {
239          out += ".*";
240          i = j;
241        }
242      } else {
243        out += "[^/]*";
244        i++;
245      }
246    } else if (c === "?") {
247      out += "[^/]";
248      i++;
249    } else if (c === "[") {
250      let j = i + 1;
251      if (j < n && "!^".includes(pat.charAt(j)))
252        j++;
253      if (j < n && pat.charAt(j) === "]")
254        j++;
255      while (j < n && pat.charAt(j) !== "]")
256        j++;
257      let cls = pat.slice(i, j + 1);
258      if (cls.startsWith("[!"))
259        cls = `[^${cls.slice(2)}`;
260      out += cls;
261      i = j + 1;
262    } else {
263      out += escapeRegex(c);
264      i++;
265    }
266  }
267  return `${out}$`;
268}
269function matchPatterns(pats) {
270  const pairs = [];
271  for (const pat of pats) {
272    for (const ex of expandBraces(pat))
273      pairs.push([ex, new RegExp(globToRegex(ex))]);
274  }
275  return pairs;
276}
277var compileGlobs = (patterns) => matchPatterns(patterns).map(([, rx]) => rx);
278function asList(v) {
279  if (typeof v === "string")
280    return [v];
281  if (Array.isArray(v))
282    return v;
283  return [];
284}
285var hasMagic = (seg) => /[*?[]/.test(seg);
286async function globExists(io, pattern) {
287  const abs = pattern.startsWith("/");
288  const segs = pattern.split("/").filter((s) => s !== "");
289  return walk(io, abs ? "/" : "", segs, 0);
290}
291async function walk(io, dir, segs, i) {
292  if (i >= segs.length)
293    return dir === "" ? true : io.exists(dir);
294  const seg = segs.at(i) ?? "";
295  const here = dir === "" ? "." : dir;
296  if (seg === "**") {
297    if (i === segs.length - 1)
298      return (await io.stat(here))?.kind === "dir";
299    if (await walk(io, dir, segs, i + 1))
300      return true;
301    for (const e of await io.list(here)) {
302      if (e.name.startsWith("."))
303        continue;
304      const sub = join(dir, e.name);
305      if ((await io.stat(sub))?.kind === "dir" && await walk(io, sub, segs, i))
306        return true;
307    }
308    return false;
309  }
310  if (!hasMagic(seg))
311    return walk(io, join(dir, seg), segs, i + 1);
312  const rx = new RegExp(globToRegex(seg));
313  for (const e of await io.list(here)) {
314    if (e.name.startsWith(".") && !seg.startsWith("."))
315      continue;
316    if (!rx.test(e.name))
317      continue;
318    if (await walk(io, join(dir, e.name), segs, i + 1))
319      return true;
320  }
321  return false;
322}
323
324// node_modules/yaml/browser/dist/nodes/identity.js
325var ALIAS = Symbol.for("yaml.alias");
326var DOC = Symbol.for("yaml.document");
327var MAP = Symbol.for("yaml.map");
328var PAIR = Symbol.for("yaml.pair");
329var SCALAR = Symbol.for("yaml.scalar");
330var SEQ = Symbol.for("yaml.seq");
331var NODE_TYPE = Symbol.for("yaml.node.type");
332var isAlias = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === ALIAS;
333var isDocument = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === DOC;
334var isMap = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === MAP;
335var isPair = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === PAIR;
336var isScalar = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === SCALAR;
337var isSeq = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === SEQ;
338function isCollection(node) {
339  if (node && typeof node === "object")
340    switch (node[NODE_TYPE]) {
341      case MAP:
342      case SEQ:
343        return true;
344    }
345  return false;
346}
347function isNode(node) {
348  if (node && typeof node === "object")
349    switch (node[NODE_TYPE]) {
350      case ALIAS:
351      case MAP:
352      case SCALAR:
353      case SEQ:
354        return true;
355    }
356  return false;
357}
358var hasAnchor = (node) => (isScalar(node) || isCollection(node)) && !!node.anchor;
359
360// node_modules/yaml/browser/dist/visit.js
361var BREAK = Symbol("break visit");
362var SKIP = Symbol("skip children");
363var REMOVE = Symbol("remove node");
364function visit(node, visitor) {
365  const visitor_ = initVisitor(visitor);
366  if (isDocument(node)) {
367    const cd = visit_(null, node.contents, visitor_, Object.freeze([node]));
368    if (cd === REMOVE)
369      node.contents = null;
370  } else
371    visit_(null, node, visitor_, Object.freeze([]));
372}
373visit.BREAK = BREAK;
374visit.SKIP = SKIP;
375visit.REMOVE = REMOVE;
376function visit_(key, node, visitor, path) {
377  const ctrl = callVisitor(key, node, visitor, path);
378  if (isNode(ctrl) || isPair(ctrl)) {
379    replaceNode(key, path, ctrl);
380    return visit_(key, ctrl, visitor, path);
381  }
382  if (typeof ctrl !== "symbol") {
383    if (isCollection(node)) {
384      path = Object.freeze(path.concat(node));
385      for (let i = 0;i < node.items.length; ++i) {
386        const ci = visit_(i, node.items[i], visitor, path);
387        if (typeof ci === "number")
388          i = ci - 1;
389        else if (ci === BREAK)
390          return BREAK;
391        else if (ci === REMOVE) {
392          node.items.splice(i, 1);
393          i -= 1;
394        }
395      }
396    } else if (isPair(node)) {
397      path = Object.freeze(path.concat(node));
398      const ck = visit_("key", node.key, visitor, path);
399      if (ck === BREAK)
400        return BREAK;
401      else if (ck === REMOVE)
402        node.key = null;
403      const cv = visit_("value", node.value, visitor, path);
404      if (cv === BREAK)
405        return BREAK;
406      else if (cv === REMOVE)
407        node.value = null;
408    }
409  }
410  return ctrl;
411}
412async function visitAsync(node, visitor) {
413  const visitor_ = initVisitor(visitor);
414  if (isDocument(node)) {
415    const cd = await visitAsync_(null, node.contents, visitor_, Object.freeze([node]));
416    if (cd === REMOVE)
417      node.contents = null;
418  } else
419    await visitAsync_(null, node, visitor_, Object.freeze([]));
420}
421visitAsync.BREAK = BREAK;
422visitAsync.SKIP = SKIP;
423visitAsync.REMOVE = REMOVE;
424async function visitAsync_(key, node, visitor, path) {
425  const ctrl = await callVisitor(key, node, visitor, path);
426  if (isNode(ctrl) || isPair(ctrl)) {
427    replaceNode(key, path, ctrl);
428    return visitAsync_(key, ctrl, visitor, path);
429  }
430  if (typeof ctrl !== "symbol") {
431    if (isCollection(node)) {
432      path = Object.freeze(path.concat(node));
433      for (let i = 0;i < node.items.length; ++i) {
434        const ci = await visitAsync_(i, node.items[i], visitor, path);
435        if (typeof ci === "number")
436          i = ci - 1;
437        else if (ci === BREAK)
438          return BREAK;
439        else if (ci === REMOVE) {
440          node.items.splice(i, 1);
441          i -= 1;
442        }
443      }
444    } else if (isPair(node)) {
445      path = Object.freeze(path.concat(node));
446      const ck = await visitAsync_("key", node.key, visitor, path);
447      if (ck === BREAK)
448        return BREAK;
449      else if (ck === REMOVE)
450        node.key = null;
451      const cv = await visitAsync_("value", node.value, visitor, path);
452      if (cv === BREAK)
453        return BREAK;
454      else if (cv === REMOVE)
455        node.value = null;
456    }
457  }
458  return ctrl;
459}
460function initVisitor(visitor) {
461  if (typeof visitor === "object" && (visitor.Collection || visitor.Node || visitor.Value)) {
462    return Object.assign({
463      Alias: visitor.Node,
464      Map: visitor.Node,
465      Scalar: visitor.Node,
466      Seq: visitor.Node
467    }, visitor.Value && {
468      Map: visitor.Value,
469      Scalar: visitor.Value,
470      Seq: visitor.Value
471    }, visitor.Collection && {
472      Map: visitor.Collection,
473      Seq: visitor.Collection
474    }, visitor);
475  }
476  return visitor;
477}
478function callVisitor(key, node, visitor, path) {
479  if (typeof visitor === "function")
480    return visitor(key, node, path);
481  if (isMap(node))
482    return visitor.Map?.(key, node, path);
483  if (isSeq(node))
484    return visitor.Seq?.(key, node, path);
485  if (isPair(node))
486    return visitor.Pair?.(key, node, path);
487  if (isScalar(node))
488    return visitor.Scalar?.(key, node, path);
489  if (isAlias(node))
490    return visitor.Alias?.(key, node, path);
491  return;
492}
493function replaceNode(key, path, node) {
494  const parent = path[path.length - 1];
495  if (isCollection(parent)) {
496    parent.items[key] = node;
497  } else if (isPair(parent)) {
498    if (key === "key")
499      parent.key = node;
500    else
501      parent.value = node;
502  } else if (isDocument(parent)) {
503    parent.contents = node;
504  } else {
505    const pt = isAlias(parent) ? "alias" : "scalar";
506    throw new Error(`Cannot replace node with ${pt} parent`);
507  }
508}
509
510// node_modules/yaml/browser/dist/doc/directives.js
511var escapeChars = {
512  "!": "%21",
513  ",": "%2C",
514  "[": "%5B",
515  "]": "%5D",
516  "{": "%7B",
517  "}": "%7D"
518};
519var escapeTagName = (tn) => tn.replace(/[!,[\]{}]/g, (ch) => escapeChars[ch]);
520
521class Directives {
522  constructor(yaml, tags) {
523    this.docStart = null;
524    this.docEnd = false;
525    this.yaml = Object.assign({}, Directives.defaultYaml, yaml);
526    this.tags = Object.assign({}, Directives.defaultTags, tags);
527  }
528  clone() {
529    const copy = new Directives(this.yaml, this.tags);
530    copy.docStart = this.docStart;
531    return copy;
532  }
533  atDocument() {
534    const res = new Directives(this.yaml, this.tags);
535    switch (this.yaml.version) {
536      case "1.1":
537        this.atNextDocument = true;
538        break;
539      case "1.2":
540        this.atNextDocument = false;
541        this.yaml = {
542          explicit: Directives.defaultYaml.explicit,
543          version: "1.2"
544        };
545        this.tags = Object.assign({}, Directives.defaultTags);
546        break;
547    }
548    return res;
549  }
550  add(line, onError) {
551    if (this.atNextDocument) {
552      this.yaml = { explicit: Directives.defaultYaml.explicit, version: "1.1" };
553      this.tags = Object.assign({}, Directives.defaultTags);
554      this.atNextDocument = false;
555    }
556    const parts = line.trim().split(/[ \t]+/);
557    const name = parts.shift();
558    switch (name) {
559      case "%TAG": {
560        if (parts.length !== 2) {
561          onError(0, "%TAG directive should contain exactly two parts");
562          if (parts.length < 2)
563            return false;
564        }
565        const [handle, prefix] = parts;
566        this.tags[handle] = prefix;
567        return true;
568      }
569      case "%YAML": {
570        this.yaml.explicit = true;
571        if (parts.length !== 1) {
572          onError(0, "%YAML directive should contain exactly one part");
573          return false;
574        }
575        const [version] = parts;
576        if (version === "1.1" || version === "1.2") {
577          this.yaml.version = version;
578          return true;
579        } else {
580          const isValid = /^\d+\.\d+$/.test(version);
581          onError(6, `Unsupported YAML version ${version}`, isValid);
582          return false;
583        }
584      }
585      default:
586        onError(0, `Unknown directive ${name}`, true);
587        return false;
588    }
589  }
590  tagName(source, onError) {
591    if (source === "!")
592      return "!";
593    if (source[0] !== "!") {
594      onError(`Not a valid tag: ${source}`);
595      return null;
596    }
597    if (source[1] === "<") {
598      const verbatim = source.slice(2, -1);
599      if (verbatim === "!" || verbatim === "!!") {
600        onError(`Verbatim tags aren't resolved, so ${source} is invalid.`);
601        return null;
602      }
603      if (source[source.length - 1] !== ">")
604        onError("Verbatim tags must end with a >");
605      return verbatim;
606    }
607    const [, handle, suffix] = source.match(/^(.*!)([^!]*)$/s);
608    if (!suffix)
609      onError(`The ${source} tag has no suffix`);
610    const prefix = this.tags[handle];
611    if (prefix) {
612      try {
613        return prefix + decodeURIComponent(suffix);
614      } catch (error) {
615        onError(String(error));
616        return null;
617      }
618    }
619    if (handle === "!")
620      return source;
621    onError(`Could not resolve tag: ${source}`);
622    return null;
623  }
624  tagString(tag) {
625    for (const [handle, prefix] of Object.entries(this.tags)) {
626      if (tag.startsWith(prefix))
627        return handle + escapeTagName(tag.substring(prefix.length));
628    }
629    return tag[0] === "!" ? tag : `!<${tag}>`;
630  }
631  toString(doc) {
632    const lines = this.yaml.explicit ? [`%YAML ${this.yaml.version || "1.2"}`] : [];
633    const tagEntries = Object.entries(this.tags);
634    let tagNames;
635    if (doc && tagEntries.length > 0 && isNode(doc.contents)) {
636      const tags = {};
637      visit(doc.contents, (_key, node) => {
638        if (isNode(node) && node.tag)
639          tags[node.tag] = true;
640      });
641      tagNames = Object.keys(tags);
642    } else
643      tagNames = [];
644    for (const [handle, prefix] of tagEntries) {
645      if (handle === "!!" && prefix === "tag:yaml.org,2002:")
646        continue;
647      if (!doc || tagNames.some((tn) => tn.startsWith(prefix)))
648        lines.push(`%TAG ${handle} ${prefix}`);
649    }
650    return lines.join(`
651`);
652  }
653}
654Directives.defaultYaml = { explicit: false, version: "1.2" };
655Directives.defaultTags = { "!!": "tag:yaml.org,2002:" };
656
657// node_modules/yaml/browser/dist/doc/anchors.js
658function anchorIsValid(anchor) {
659  if (/[\x00-\x19\s,[\]{}]/.test(anchor)) {
660    const sa = JSON.stringify(anchor);
661    const msg = `Anchor must not contain whitespace or control characters: ${sa}`;
662    throw new Error(msg);
663  }
664  return true;
665}
666function anchorNames(root) {
667  const anchors = new Set;
668  visit(root, {
669    Value(_key, node) {
670      if (node.anchor)
671        anchors.add(node.anchor);
672    }
673  });
674  return anchors;
675}
676function findNewAnchor(prefix, exclude) {
677  for (let i = 1;; ++i) {
678    const name = `${prefix}${i}`;
679    if (!exclude.has(name))
680      return name;
681  }
682}
683function createNodeAnchors(doc, prefix) {
684  const aliasObjects = [];
685  const sourceObjects = new Map;
686  let prevAnchors = null;
687  return {
688    onAnchor: (source) => {
689      aliasObjects.push(source);
690      prevAnchors ?? (prevAnchors = anchorNames(doc));
691      const anchor = findNewAnchor(prefix, prevAnchors);
692      prevAnchors.add(anchor);
693      return anchor;
694    },
695    setAnchors: () => {
696      for (const source of aliasObjects) {
697        const ref = sourceObjects.get(source);
698        if (typeof ref === "object" && ref.anchor && (isScalar(ref.node) || isCollection(ref.node))) {
699          ref.node.anchor = ref.anchor;
700        } else {
701          const error = new Error("Failed to resolve repeated object (this should not happen)");
702          error.source = source;
703          throw error;
704        }
705      }
706    },
707    sourceObjects
708  };
709}
710
711// node_modules/yaml/browser/dist/doc/applyReviver.js
712function applyReviver(reviver, obj, key, val) {
713  if (val && typeof val === "object") {
714    if (Array.isArray(val)) {
715      for (let i = 0, len = val.length;i < len; ++i) {
716        const v0 = val[i];
717        const v1 = applyReviver(reviver, val, String(i), v0);
718        if (v1 === undefined)
719          delete val[i];
720        else if (v1 !== v0)
721          val[i] = v1;
722      }
723    } else if (val instanceof Map) {
724      for (const k of Array.from(val.keys())) {
725        const v0 = val.get(k);
726        const v1 = applyReviver(reviver, val, k, v0);
727        if (v1 === undefined)
728          val.delete(k);
729        else if (v1 !== v0)
730          val.set(k, v1);
731      }
732    } else if (val instanceof Set) {
733      for (const v0 of Array.from(val)) {
734        const v1 = applyReviver(reviver, val, v0, v0);
735        if (v1 === undefined)
736          val.delete(v0);
737        else if (v1 !== v0) {
738          val.delete(v0);
739          val.add(v1);
740        }
741      }
742    } else {
743      for (const [k, v0] of Object.entries(val)) {
744        const v1 = applyReviver(reviver, val, k, v0);
745        if (v1 === undefined)
746          delete val[k];
747        else if (v1 !== v0)
748          val[k] = v1;
749      }
750    }
751  }
752  return reviver.call(obj, key, val);
753}
754
755// node_modules/yaml/browser/dist/nodes/toJS.js
756function toJS(value, arg, ctx) {
757  if (Array.isArray(value))
758    return value.map((v, i) => toJS(v, String(i), ctx));
759  if (value && typeof value.toJSON === "function") {
760    if (!ctx || !hasAnchor(value))
761      return value.toJSON(arg, ctx);
762    const data = { aliasCount: 0, count: 1, res: undefined };
763    ctx.anchors.set(value, data);
764    ctx.onCreate = (res) => {
765      data.res = res;
766      delete ctx.onCreate;
767    };
768    const res = value.toJSON(arg, ctx);
769    if (ctx.onCreate)
770      ctx.onCreate(res);
771    return res;
772  }
773  if (typeof value === "bigint" && !ctx?.keep)
774    return Number(value);
775  return value;
776}
777
778// node_modules/yaml/browser/dist/nodes/Node.js
779class NodeBase {
780  constructor(type) {
781    Object.defineProperty(this, NODE_TYPE, { value: type });
782  }
783  clone() {
784    const copy = Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this));
785    if (this.range)
786      copy.range = this.range.slice();
787    return copy;
788  }
789  toJS(doc, { mapAsMap, maxAliasCount, onAnchor, reviver } = {}) {
790    if (!isDocument(doc))
791      throw new TypeError("A document argument is required");
792    const ctx = {
793      anchors: new Map,
794      doc,
795      keep: true,
796      mapAsMap: mapAsMap === true,
797      mapKeyWarned: false,
798      maxAliasCount: typeof maxAliasCount === "number" ? maxAliasCount : 100
799    };
800    const res = toJS(this, "", ctx);
801    if (typeof onAnchor === "function")
802      for (const { count, res } of ctx.anchors.values())
803        onAnchor(res, count);
804    return typeof reviver === "function" ? applyReviver(reviver, { "": res }, "", res) : res;
805  }
806}
807
808// node_modules/yaml/browser/dist/nodes/Alias.js
809class Alias extends NodeBase {
810  constructor(source) {
811    super(ALIAS);
812    this.source = source;
813    Object.defineProperty(this, "tag", {
814      set() {
815        throw new Error("Alias nodes cannot have tags");
816      }
817    });
818  }
819  resolve(doc, ctx) {
820    if (ctx?.maxAliasCount === 0)
821      throw new ReferenceError("Alias resolution is disabled");
822    let nodes;
823    if (ctx?.aliasResolveCache) {
824      nodes = ctx.aliasResolveCache;
825    } else {
826      nodes = [];
827      visit(doc, {
828        Node: (_key, node) => {
829          if (isAlias(node) || hasAnchor(node))
830            nodes.push(node);
831        }
832      });
833      if (ctx)
834        ctx.aliasResolveCache = nodes;
835    }
836    let found = undefined;
837    for (const node of nodes) {
838      if (node === this)
839        break;
840      if (node.anchor === this.source)
841        found = node;
842    }
843    if (found && ctx) {
844      const { anchors, doc, maxAliasCount } = ctx;
845      let data = anchors.get(found);
846      if (!data) {
847        toJS(found, null, ctx);
848        data = anchors.get(found);
849      }
850      if (data?.res === undefined) {
851        const msg = "This should not happen: Alias anchor was not resolved?";
852        throw new ReferenceError(msg);
853      }
854      if (maxAliasCount >= 0) {
855        data.count += 1;
856        if (data.aliasCount === 0)
857          data.aliasCount = getAliasCount(doc, found, anchors);
858        if (data.count * data.aliasCount > maxAliasCount) {
859          const msg = "Excessive alias count indicates a resource exhaustion attack";
860          throw new ReferenceError(msg);
861        }
862      }
863    }
864    return found;
865  }
866  toJSON(_arg, ctx) {
867    if (!ctx)
868      return { source: this.source };
869    const source = this.resolve(ctx.doc, ctx);
870    if (!source) {
871      const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`;
872      throw new ReferenceError(msg);
873    }
874    return ctx.anchors.get(source).res;
875  }
876  toString(ctx, _onComment, _onChompKeep) {
877    const src = `*${this.source}`;
878    if (ctx) {
879      anchorIsValid(this.source);
880      if (ctx.options.verifyAliasOrder && !ctx.anchors.has(this.source)) {
881        const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`;
882        throw new Error(msg);
883      }
884      if (ctx.implicitKey)
885        return `${src} `;
886    }
887    return src;
888  }
889}
890function getAliasCount(doc, node, anchors) {
891  if (isAlias(node)) {
892    const source = node.resolve(doc);
893    const anchor = anchors && source && anchors.get(source);
894    return anchor ? anchor.count * anchor.aliasCount : 0;
895  } else if (isCollection(node)) {
896    let count = 0;
897    for (const item of node.items) {
898      const c = getAliasCount(doc, item, anchors);
899      if (c > count)
900        count = c;
901    }
902    return count;
903  } else if (isPair(node)) {
904    const kc = getAliasCount(doc, node.key, anchors);
905    const vc = getAliasCount(doc, node.value, anchors);
906    return Math.max(kc, vc);
907  }
908  return 1;
909}
910
911// node_modules/yaml/browser/dist/nodes/Scalar.js
912var isScalarValue = (value) => !value || typeof value !== "function" && typeof value !== "object";
913
914class Scalar extends NodeBase {
915  constructor(value) {
916    super(SCALAR);
917    this.value = value;
918  }
919  toJSON(arg, ctx) {
920    return ctx?.keep ? this.value : toJS(this.value, arg, ctx);
921  }
922  toString() {
923    return String(this.value);
924  }
925}
926Scalar.BLOCK_FOLDED = "BLOCK_FOLDED";
927Scalar.BLOCK_LITERAL = "BLOCK_LITERAL";
928Scalar.PLAIN = "PLAIN";
929Scalar.QUOTE_DOUBLE = "QUOTE_DOUBLE";
930Scalar.QUOTE_SINGLE = "QUOTE_SINGLE";
931
932// node_modules/yaml/browser/dist/doc/createNode.js
933var defaultTagPrefix = "tag:yaml.org,2002:";
934function findTagObject(value, tagName, tags) {
935  if (tagName) {
936    const match = tags.filter((t) => t.tag === tagName);
937    const tagObj = match.find((t) => !t.format) ?? match[0];
938    if (!tagObj)
939      throw new Error(`Tag ${tagName} not found`);
940    return tagObj;
941  }
942  return tags.find((t) => t.identify?.(value) && !t.format);
943}
944function createNode(value, tagName, ctx) {
945  if (isDocument(value))
946    value = value.contents;
947  if (isNode(value))
948    return value;
949  if (isPair(value)) {
950    const map = ctx.schema[MAP].createNode?.(ctx.schema, null, ctx);
951    map.items.push(value);
952    return map;
953  }
954  if (value instanceof String || value instanceof Number || value instanceof Boolean || typeof BigInt !== "undefined" && value instanceof BigInt) {
955    value = value.valueOf();
956  }
957  const { aliasDuplicateObjects, onAnchor, onTagObj, schema, sourceObjects } = ctx;
958  let ref = undefined;
959  if (aliasDuplicateObjects && value && typeof value === "object") {
960    ref = sourceObjects.get(value);
961    if (ref) {
962      ref.anchor ?? (ref.anchor = onAnchor(value));
963      return new Alias(ref.anchor);
964    } else {
965      ref = { anchor: null, node: null };
966      sourceObjects.set(value, ref);
967    }
968  }
969  if (tagName?.startsWith("!!"))
970    tagName = defaultTagPrefix + tagName.slice(2);
971  let tagObj = findTagObject(value, tagName, schema.tags);
972  if (!tagObj) {
973    if (value && typeof value.toJSON === "function") {
974      value = value.toJSON();
975    }
976    if (!value || typeof value !== "object") {
977      const node = new Scalar(value);
978      if (ref)
979        ref.node = node;
980      return node;
981    }
982    tagObj = value instanceof Map ? schema[MAP] : (Symbol.iterator in Object(value)) ? schema[SEQ] : schema[MAP];
983  }
984  if (onTagObj) {
985    onTagObj(tagObj);
986    delete ctx.onTagObj;
987  }
988  const node = tagObj?.createNode ? tagObj.createNode(ctx.schema, value, ctx) : typeof tagObj?.nodeClass?.from === "function" ? tagObj.nodeClass.from(ctx.schema, value, ctx) : new Scalar(value);
989  if (tagName)
990    node.tag = tagName;
991  else if (!tagObj.default)
992    node.tag = tagObj.tag;
993  if (ref)
994    ref.node = node;
995  return node;
996}
997
998// node_modules/yaml/browser/dist/nodes/Collection.js
999function collectionFromPath(schema, path, value) {
1000  let v = value;
1001  for (let i = path.length - 1;i >= 0; --i) {
1002    const k = path[i];
1003    if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
1004      const a = [];
1005      a[k] = v;
1006      v = a;
1007    } else {
1008      v = new Map([[k, v]]);
1009    }
1010  }
1011  return createNode(v, undefined, {
1012    aliasDuplicateObjects: false,
1013    keepUndefined: false,
1014    onAnchor: () => {
1015      throw new Error("This should not happen, please report a bug.");
1016    },
1017    schema,
1018    sourceObjects: new Map
1019  });
1020}
1021var isEmptyPath = (path) => path == null || typeof path === "object" && !!path[Symbol.iterator]().next().done;
1022
1023class Collection extends NodeBase {
1024  constructor(type, schema) {
1025    super(type);
1026    Object.defineProperty(this, "schema", {
1027      value: schema,
1028      configurable: true,
1029      enumerable: false,
1030      writable: true
1031    });
1032  }
1033  clone(schema) {
1034    const copy = Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this));
1035    if (schema)
1036      copy.schema = schema;
1037    copy.items = copy.items.map((it) => isNode(it) || isPair(it) ? it.clone(schema) : it);
1038    if (this.range)
1039      copy.range = this.range.slice();
1040    return copy;
1041  }
1042  addIn(path, value) {
1043    if (isEmptyPath(path))
1044      this.add(value);
1045    else {
1046      const [key, ...rest] = path;
1047      const node = this.get(key, true);
1048      if (isCollection(node))
1049        node.addIn(rest, value);
1050      else if (node === undefined && this.schema)
1051        this.set(key, collectionFromPath(this.schema, rest, value));
1052      else
1053        throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
1054    }
1055  }
1056  deleteIn(path) {
1057    const [key, ...rest] = path;
1058    if (rest.length === 0)
1059      return this.delete(key);
1060    const node = this.get(key, true);
1061    if (isCollection(node))
1062      return node.deleteIn(rest);
1063    else
1064      throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
1065  }
1066  getIn(path, keepScalar) {
1067    const [key, ...rest] = path;
1068    const node = this.get(key, true);
1069    if (rest.length === 0)
1070      return !keepScalar && isScalar(node) ? node.value : node;
1071    else
1072      return isCollection(node) ? node.getIn(rest, keepScalar) : undefined;
1073  }
1074  hasAllNullValues(allowScalar) {
1075    return this.items.every((node) => {
1076      if (!isPair(node))
1077        return false;
1078      const n = node.value;
1079      return n == null || allowScalar && isScalar(n) && n.value == null && !n.commentBefore && !n.comment && !n.tag;
1080    });
1081  }
1082  hasIn(path) {
1083    const [key, ...rest] = path;
1084    if (rest.length === 0)
1085      return this.has(key);
1086    const node = this.get(key, true);
1087    return isCollection(node) ? node.hasIn(rest) : false;
1088  }
1089  setIn(path, value) {
1090    const [key, ...rest] = path;
1091    if (rest.length === 0) {
1092      this.set(key, value);
1093    } else {
1094      const node = this.get(key, true);
1095      if (isCollection(node))
1096        node.setIn(rest, value);
1097      else if (node === undefined && this.schema)
1098        this.set(key, collectionFromPath(this.schema, rest, value));
1099      else
1100        throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
1101    }
1102  }
1103}
1104
1105// node_modules/yaml/browser/dist/stringify/stringifyComment.js
1106var stringifyComment = (str) => str.replace(/^(?!$)(?: $)?/gm, "#");
1107function indentComment(comment, indent) {
1108  if (/^\n+$/.test(comment))
1109    return comment.substring(1);
1110  return indent ? comment.replace(/^(?! *$)/gm, indent) : comment;
1111}
1112var lineComment = (str, indent, comment) => str.endsWith(`
1113`) ? indentComment(comment, indent) : comment.includes(`
1114`) ? `
1115` + indentComment(comment, indent) : (str.endsWith(" ") ? "" : " ") + comment;
1116
1117// node_modules/yaml/browser/dist/stringify/foldFlowLines.js
1118var FOLD_FLOW = "flow";
1119var FOLD_BLOCK = "block";
1120var FOLD_QUOTED = "quoted";
1121function foldFlowLines(text, indent, mode = "flow", { indentAtStart, lineWidth = 80, minContentWidth = 20, onFold, onOverflow } = {}) {
1122  if (!lineWidth || lineWidth < 0)
1123    return text;
1124  if (lineWidth < minContentWidth)
1125    minContentWidth = 0;
1126  const endStep = Math.max(1 + minContentWidth, 1 + lineWidth - indent.length);
1127  if (text.length <= endStep)
1128    return text;
1129  const folds = [];
1130  const escapedFolds = {};
1131  let end = lineWidth - indent.length;
1132  if (typeof indentAtStart === "number") {
1133    if (indentAtStart > lineWidth - Math.max(2, minContentWidth))
1134      folds.push(0);
1135    else
1136      end = lineWidth - indentAtStart;
1137  }
1138  let split = undefined;
1139  let prev = undefined;
1140  let overflow = false;
1141  let i = -1;
1142  let escStart = -1;
1143  let escEnd = -1;
1144  if (mode === FOLD_BLOCK) {
1145    i = consumeMoreIndentedLines(text, i, indent.length);
1146    if (i !== -1)
1147      end = i + endStep;
1148  }
1149  for (let ch;ch = text[i += 1]; ) {
1150    if (mode === FOLD_QUOTED && ch === "\\") {
1151      escStart = i;
1152      switch (text[i + 1]) {
1153        case "x":
1154          i += 3;
1155          break;
1156        case "u":
1157          i += 5;
1158          break;
1159        case "U":
1160          i += 9;
1161          break;
1162        default:
1163          i += 1;
1164      }
1165      escEnd = i;
1166    }
1167    if (ch === `
1168`) {
1169      if (mode === FOLD_BLOCK)
1170        i = consumeMoreIndentedLines(text, i, indent.length);
1171      end = i + indent.length + endStep;
1172      split = undefined;
1173    } else {
1174      if (ch === " " && prev && prev !== " " && prev !== `
1175` && prev !== "\t") {
1176        const next = text[i + 1];
1177        if (next && next !== " " && next !== `
1178` && next !== "\t")
1179          split = i;
1180      }
1181      if (i >= end) {
1182        if (split) {
1183          folds.push(split);
1184          end = split + endStep;
1185          split = undefined;
1186        } else if (mode === FOLD_QUOTED) {
1187          while (prev === " " || prev === "\t") {
1188            prev = ch;
1189            ch = text[i += 1];
1190            overflow = true;
1191          }
1192          const j = i > escEnd + 1 ? i - 2 : escStart - 1;
1193          if (escapedFolds[j])
1194            return text;
1195          folds.push(j);
1196          escapedFolds[j] = true;
1197          end = j + endStep;
1198          split = undefined;
1199        } else {
1200          overflow = true;