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

A Claude Code plugin made of function hooks. It does three things:
~/.claude/feedback/*.md) into each prompt and enforces them on tool calls and when Claude stops..claude/gate.yaml after file edits and when Claude stops.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/.
| Event | Module | Role |
|---|---|---|
| SessionStart | src/reset-gate.ts | Clears this session's gate state on startup / clear; keeps it on resume / compact. |
| UserPromptSubmit | src/rules-file.ts + src/feedback-inject.ts | Adds 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.ts | Evaluates 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-guard | Notes 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-in | src/agent-launch-guard.ts | Only when agentLaunchGuard is true: asks before sending instructions to a subagent, showing the full text. |
| Notification | src/notification.ts (notify) | Desktop notification when Claude waits for you (needs terminal-notifier, macOS). |
| Stop | src/all-stop.ts | Runs, in order: the gate checks phase, src/feedback-stop-check.ts, then the stop notification. |
| SubagentStop | gate checks phase, src/feedback-stop-check.ts | Same gate and stop_check rules for subagents. |
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
/plugin install feedback-gate --marketplace nonchan7720/claude-hook-gate
Answer y to add the marketplace, then choose the scope.
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..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.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.
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 }
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.
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.
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.
hooks/register.js 9279 lines1// 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;