Skill: encryptshawn/agentic-cli-coding (version 1.0.0)

--- name: agentic_cli_coding description: "Use this skill for any task that involves reading, understanding, searching, or modifying source code in a project — across JavaScript, TypeScript, Vue, React, Node, Python, Go, Ruby, PHP, Java, Rust, C/C++, shell scripts, JSON, YAML, HTML, CSS, SQL, and Markdown. Trigger whenever the user asks to fix a bug, add a feature, refactor, rename, restructure, search a codebase, locate a function or symbol, apply a patch, modify configuration, or make any multi-file change. Also use when the user references files by path or name and wants something done to them, when reviewing or auditing code, or when planning an edit before making it. Provides the `oce` command — a unified, validation-aware, transaction-capable editing toolkit that backs up every change and rolls back on syntax failure. Prefer `oce` over raw sed/awk/perl-i for any persistent edit; those are read-only here." --- # Agentic CLI Coding A code-editing toolkit for agents. Provides the `oce` command, which wraps reads, searches, edits, validation, formatting, backups, and multi-file transactions behind one predictable interface that always validates after writing and rolls back on syntax failure. This skill exists because raw `sed`, `awk`, and `perl -i` are too sharp for autonomous use — they apply changes silently, give regex-style false matches, leave no audit trail, and can't roll back. `oce` keeps the speed of CLI editing while adding the safety rails an agent needs. --- ## Invocation — `oce` shorthand Throughout this document, the command `oce <subcommand>` is shown for readability. The actual invocation is one of: ```bash # Option A — direct (works immediately, no setup): bash <skill-path>/scripts/oce.sh <subcommand> [args] # Option B — one-time alias for the session (recommended for agents): alias oce="bash <skill-path>/scripts/oce.sh" # Option C — install a wrapper on PATH (persistent): bash <skill-path>/scripts/install.sh ``` For agentic use, set up the alias once at the top of any session that involves editing, then use `oce <subcommand>` for the rest of the session. Replace `<skill-path>` with the actual install location of the skill (often something like `/home/agent/.skills/agentic_cli_coding`). --- ## Setup verification After setting up the alias (or installing), run this once at the start of any editing session: ```bash oce doctor ``` Exit code 0 with `Setup OK` means the toolkit is ready. The "core tools" must all be present (node, patch, diff, grep, acorn). Missing optional tools (prettier, gofmt, etc.) only affect formatting for that language — editing still works. If you see `oce: command not found`, the alias wasn't set or the install wrapper isn't on PATH. Use Option A (direct invocation) instead. --- ## Methodology — how to approach any code task Skip directly to the relevant step if the user's task is simple. For anything non-trivial, walk through all four. ### 1. Orient — understand the project Before editing, know what you're editing. ```bash oce tree --depth 2 # What's in this project? oce find "<keyword>" --type <lang> # Where does the relevant code live? oce ast symbols path/to/file.js # What functions/classes does that file define? ``` Don't skip orientation just because you "know" the answer from training. Repository conventions vary; assumptions are how agents break codebases. ### 2. Read — load the exact context you'll need ```bash oce read src/server.js --around "handleAuth" --context 20 oce grep-context "TODO" src/server.js -c 5 oce read src/auth.js --lines 45:120 ``` Read enough surrounding context that you can predict what your edit will affect. The cheapest way to break code is to edit a function in isolation without seeing its callers. ### 3. Plan — write down what you'll change before changing anything For non-trivial edits, state the plan explicitly before executing: - **What's being added** (new lines, new files) - **What's being removed** (deletions, deprecations) - **What's being modified** (in-place changes) - **What's at risk** (files that touch the same symbols, public APIs, tests) - **What you'll validate** (which files need to compile, which tests should still pass) If the change spans multiple files, **start a transaction** before the first edit: ```bash TXN=$(oce transaction begin) ``` Then pass `--txn "$TXN"` to every subsequent `oce replace`, `oce insert`, `oce delete`, `oce write`, `oce patch`, or `oce ast` command. At the end you commit (validates everything atomically) or roll back (restores every file). ### 4. Execute — pick the right edit tool, then verify The decision flow: ``` Need to change code? ├── Tiny, surgical, exact-string change → oce replace ├── Insert new code at a known anchor → oce insert --before-match | --after-match | --line ├── Remove specific lines or matching lines → oce delete --lines | --match ├── Multi-line precision change with context → oce patch apply (write a unified diff) ├── Rename a JS/JSX identifier scope-wide → oce ast rename ├── Replace an entire function/class body → oce ast replace-symbol ├── Brand-new file or full rewrite → oce write └── Coordinated changes across multiple files → oce transaction + any of the above ``` After every edit, the toolkit auto-validates the file's syntax and rolls back if it broke. You don't need to re-validate manually, but you should `oce diff <file>` to confirm the change matches your intent. --- ## Decision tree — which command for which job ``` DISCOVERY READING ──────────────── ──────────────── project layout? tree full file? read <file> where is X? find specific lines? read <file> --lines A:B function/class list? ast symbols context around X? read <file> --around X -c N match in context? grep-context match with ctx? grep-context X file -c N EDITING (small) EDITING (large / structural) ──────────────── ──────────────── exact string swap replace full file rewrite write insert at anchor insert apply unified diff patch apply delete lines/matches delete rename symbol (JS/JSX) ast rename replace function body ast replace-symbol VERIFY & RECOVER ──────────────── syntax check validate (auto-runs after every edit) canonical format format (manual) view recent change diff (vs last backup) list backups backup list restore backup restore <file> [--at N] MULTI-FILE ATOMIC ──────────────── TXN=$(oce transaction begin) oce <edit> ... --txn "$TXN" (repeat) oce transaction validate "$TXN" oce transaction commit "$TXN" | oce transaction rollback "$TXN" ``` --- ## Standard workflows ### Modifying an existing function (single file) ```bash oce find "function processRequest" --type js oce read src/server.js --around "processRequest" --context 15 oce replace src/server.js \ --old "return data;" \ --new "return sanitize(data);" oce diff src/server.js ``` ### Adding a new code block at a known anchor ```bash oce find "// END ROUTES" src/server.js cat > /tmp/new_route.js <<'EOF' app.get('/health', (req, res) => res.json({ ok: true })); EOF oce insert src/server.js --before-match "// END ROUTES" --content-file /tmp/new_route.js ``` ### Multi-file rename / refactor ```bash TXN=$(oce transaction begin) oce replace src/auth.js --old "validateToken" --new "verifyToken" --all --txn "$TXN" oce replace src/api.js --old "validateToken" --new "verifyToken" --all --txn "$TXN" oce replace tests/auth.test.js --old "validateToken" --new "verifyToken" --all --txn "$TXN" oce transaction validate "$TXN" oce transaction commit "$TXN" # or rollback if anything looks wrong ``` For pure JS/JSX, `oce ast rename` is preferable — it walks the AST and only renames real identifier references, not strings or comments. ### Precise multi-line patch When you need exact control over a multi-line change, write a unified diff: ```bash cat > /tmp/fix.patch <<'EOF' --- a/src/auth.js +++ b/src/auth.js @@ -12,6 +12,10 @@ function authenticate(req) { + if (!req.headers.authorization) { + throw new Error('Missing auth header'); + } const [REDACTED_SECRET]' ')[1]; EOF oce patch apply /tmp/fix.patch ``` The patch is dry-run-checked first; if it doesn't apply cleanly the command fails before touching anything. After applying, every affected file is validated and the entire patch is rolled back if any file's syntax broke. ### Recovering from a bad edit ```bash oce backup list <file> # See available snapshots oce backup diff <file> # Compare current vs. most recent backup oce backup restore <file> # Restore most recent backup oce backup restore <file> --at 3 # Restore the 4th-most-recent (0-indexed) ``` Backups are auto-created before every destructive operation. They live in `.oce/backups/` in the workspace. --- ## Anti-patterns — never do these These are the failure modes that bite agents most often. Internalize them. **Do not use `sed -i`, `awk` redirected to the same file, or `perl -i`** for editing. They give zero validation, no backup, and silent partial-success on regex misses. Use `oce replace` (literal match) or `oce patch` (precise) instead. **Do not use `oce write` to make small edits.** `write` replaces the entire file. If you only need to change three lines, use `replace`, `patch`, or `insert`. Full rewrites are reserved for new files or when you've already loaded and modified the entire file's content programmatically. **Do not pass a non-unique `--old` to `oce replace` without `--all`.** The command will fail with an "ambiguous match" error — that's by design. Either make `--old` more specific (include surrounding context like an indent or a closing brace) or pass `--all` if you genuinely want every occurrence changed. **Do not edit multiple files for a single logical change without a transaction.** If file 2's edit fails validation, file 1 is now in an inconsistent state. `transaction begin` + `--txn` + `commit`/`rollback` keeps everything atomic. **Do not skip `oce diff` after a non-trivial edit.** It takes one command and tells you whether the change matches your mental model. The auto-validation only catches syntax errors, not logic errors. **Do not assume a formatter ran.** `oce format` only runs if the formatter is installed. Check `oce doctor` output if you care. **Do not edit binary files.** `oce` refuses by default; if you find yourself reaching for `OCE_MAX_FILE_SIZE` overrides or trying to bypass the binary check, stop and reconsider. **Do not invent file paths.** Always confirm a file exists with `oce read` or `oce tree` before editing it. The toolkit will fail loudly on missing files, but the better habit is checking first. --- ## Output format — `--json` for programmatic parsing Every command supports `--json`, which emits a single-line JSON object on stdout (or stderr for errors). Use this when chaining commands or parsing results. Quick reference (full schema in `references/json-schema.md`): ```json // Success {"status":"success", "file":"src/x.js", "replacements":3, "backup":"/path/to/backup"} // Error {"status":"error", "message":"Found 5 matches; pass --all or make --old more unique"} // Dry run {"status":"dry_run", "file":"src/x.js", "matches":3, "message":"would replace 3 occurrence(s)"} // Validation {"status":"success", "file":"src/x.js", "language":"javascript", "validator":"node --check", "valid":true, "output":""} ``` For ambiguous results (find with many hits, ast symbols), the JSON includes a `matches` or `symbols` array. --- ## Global flags These work on every command: | Flag | Effect | |---|---| | `--json` | Emit machine-readable JSON instead of human text | | `--dry-run` | Show what would happen, change nothing (writes only) | | `--no-color` | Disable ANSI colors | | `--txn <id>` | Register backups with a transaction (write commands only) | --- ## Language support summary `oce` knows about and handles edits for files in: JavaScript (.js .mjs .cjs .jsx), TypeScript (.ts .tsx), Vue (.vue), Svelte (.svelte), Python (.py), Ruby (.rb), Go (.go), Rust (.rs), Java (.java), Kotlin (.kt), Swift (.swift), C/C++ (.c .h .cpp .hpp), C# (.cs), PHP (.php), Bash/Zsh (.sh .bash .zsh), JSON, YAML, TOML, XML, HTML, CSS/SCSS/LESS, Markdown, SQL, Dockerfile, Makefile. Validation depth varies by what's installed locally. See `references/language-support.md` for the full matrix. AST-level operations (`oce ast`) are JS/JSX-native (acorn parses them directly). On TypeScript files, AST commands work for the JS-compatible subset — for full TS support, install local `tsc`. For other languages, use text-based edits (`replace`, `patch`). --- ## Storage and state The skill writes nothing to your home directory. All state lives in `.oce/` inside the current working directory: ``` <project>/.oce/ ├── backups/ Snapshot of every file before destructive edit ├── transactions/ Active and historical transactions └── edit.log Audit log of every edit ``` You can safely add `.oce/` to `.gitignore`. To clean up, `oce backup clean [DAYS]` removes backups older than DAYS (default 30). --- ## Reference material For deeper docs, read these as needed: - **`references/json-schema.md`** — Exact JSON output schema for every command. Read this when chaining commands or parsing output programmatically. - **`references/workflows.md`** — Longer worked examples: bug fix flow, feature add flow, refactor flow, dependency upgrade flow, Vue/React component editing. - **`references/language-support.md`** — Per-language validator and formatter matrix; what works without extra installs, what needs project-local tooling. - **`references/troubleshooting.md`** — Symptom → cause → fix table for the failure modes you'll hit in practice. --- ## One-line summary `oce <verb> <file> [options]` — every verb backs up first, validates after, and rolls back on syntax failure. Use transactions for multi-file changes. Read before you write. Plan before you execute.

path: scripts/oce-write.sh
content: #!/usr/bin/env bash
# oce-write — Safe atomic file writer.
# Reads new contents from stdin. Always backs up first, validates after,
# rolls back on validation failure.

OCE_CALLER="${BASH_SOURCE[0]}"
source "$(dirname "$0")/lib/paths.sh"
source "$(dirname "$0")/lib/common.sh"

FILE=""; SKIP_VALIDATE="0"; TXN=""; FORCE_NEW=""

while [ $# -gt 0 ]; do
  case "$1" in
    --no-validate)  SKIP_VALIDATE="1"; shift ;;
    --txn)          TXN="$2"; shift 2 ;;
    --new)          FORCE_NEW="1"; shift ;;
    --json)         OCE_JSON_MODE="1"; shift ;;
    --dry-run)      OCE_DRY_RUN="1"; shift ;;
    -h|--help)
      cat <<'EOF'
oce write <file> < content
  Reads new contents from stdin. Atomic write (temp + rename).
  Auto-backs-up before writing. Validates after. Rolls back if invalid.

  --new           Require the file to NOT already exist
  --no-validate   Skip post-write validation (rare; use cautiously)
  --txn ID        Register backup with a transaction
  --dry-run       Print what would happen, change nothing
EOF
      exit 0 ;;
    -*) die "Unknown flag: $1" ;;
    *)  FILE="$1"; shift ;;
  esac
done

[ -n "$FILE" ] || die "Usage: oce write <file> < content"

CONTENT=$(cat)
[ -n "$CONTENT" ] || die "No content provided on stdin"

if [ "$FORCE_NEW" = "1" ] && [ -e "$FILE" ]; then
  die "File already exists (--new requires non-existent): $FILE"
fi

if [ "$OCE_DRY_RUN" = "1" ]; then
  bytes=$(printf '%s' "$CONTENT" | wc -c)
  lines=$(printf '%s\n' "$CONTENT" | wc -l)
  if [ "$OCE_JSON_MODE" = "1" ]; then
    emit_json status dry_run file "$FILE" bytes "raw:$bytes" lines "raw:$lines"
  else
    info "DRY RUN: would write $bytes bytes / $lines lines to $FILE"
  fi
  exit 0
fi

require_writable "$FILE"

BACKUP=""
if [ -f "$FILE" ]; then
  preflight_check "$FILE"
  BACKUP=$(create_backup "$FILE")
  log INFO "Backup created: $BACKUP"
fi

if [ -n "$TXN" ] && [ -d "$OCE_TXN_DIR/$TXN" ]; then
  printf '%s\t%s\n' "$FILE" "$BACKUP" >> "$OCE_TXN_DIR/$TXN/files.log"
fi

# Atomic write
TMPFILE="${FILE}.oce.tmp.$$"
printf '%s' "$CONTENT" > "$TMPFILE"
# Ensure trailing newline
if [ "$(tail -c1 "$TMPFILE" | wc -l)" -eq 0 ]; then
  printf '\n' >> "$TMPFILE"
fi
mv "$TMPFILE" "$FILE"

if [ "$SKIP_VALIDATE" = "0" ]; then
  if ! bash "$(dirname "$0")/oce-validate.sh" "$FILE" >/dev/null 2>&1; then
    if [ -n "$BACKUP" ]; then
      cp "$BACKUP" "$FILE"
      die "Write rolled back: file failed validation. Backup at $BACKUP"
    fi
    warn "Validation failed but no backup to roll back to"
  fi
fi

LINES=$(count_lines "$FILE")
if [ "$OCE_JSON_MODE" = "1" ]; then
  emit_json status success file "$FILE" lines "raw:$LINES" \
    backup "${BACKUP:-}" message "wrote $FILE ($LINES lines)"
else
  success "Wrote $FILE ($LINES lines)"
fi


path: scripts/oce-replace.sh
content: #!/usr/bin/env bash
# oce-replace — Exact string replacement (no regex surprises).
# Single Node invocation handles count + replacement + sidecar write atomically.

OCE_CALLER="${BASH_SOURCE[0]}"
source "$(dirname "$0")/lib/paths.sh"
source "$(dirname "$0")/lib/common.sh"

FILE=""; OLD=""; NEW=""; OLD_FILE=""; NEW_FILE=""
REPLACE_ALL="0"; EXPECTED_COUNT=""; TXN=""; SKIP_VALIDATE="0"

while [ $# -gt 0 ]; do
  case "$1" in
    --old)         OLD="$2"; shift 2 ;;
    --old=*)       OLD="${1#*=}"; shift ;;
    --old-file)    OLD_FILE="$2"; shift 2 ;;
    --new)         NEW="$2"; shift 2 ;;
    --new=*)       NEW="${1#*=}"; shift ;;
    --new-file)    NEW_FILE="$2"; shift 2 ;;
    --all)         REPLACE_ALL="1"; shift ;;
    --count)       EXPECTED_COUNT="$2"; shift 2 ;;
    --txn)         TXN="$2"; shift 2 ;;
    --no-validate) SKIP_VALIDATE="1"; shift ;;
    --json)        OCE_JSON_MODE="1"; shift ;;
    --dry-run)     OCE_DRY_RUN="1"; shift ;;
    -h|--help)
      cat <<'EOF'
oce replace <file> --old STR --new STR [options]
  Exact string match (NOT regex). If multiple matches found and --all is not
  given, the command FAILS — make --old more specific to disambiguate.

  --old STR | --old-file F     The literal string to find
  --new STR | --new-file F     The literal replacement (may be empty)
  --all                        Replace every occurrence
  --count N                    Assert exactly N matches will be replaced
  --txn ID                     Register backup with a transaction
  --no-validate                Skip post-write syntax validation
  --dry-run                    Show match count and proposed change, do nothing
EOF
      exit 0 ;;
    -*) die "Unknown flag: $1" ;;
    *)  FILE="$1"; shift ;;
  esac
done

[ -n "$FILE" ] || die "Usage: oce replace <file> --old STR --new STR [--all]"
require_file "$FILE"
preflight_check "$FILE"

[ -n "$OLD_FILE" ] && OLD=$(cat "$OLD_FILE")
[ -n "$NEW_FILE" ] && NEW=$(cat "$NEW_FILE")
[ -n "$OLD" ] || die "--old is required and may not be empty"

PENDING="${FILE}.oce.pending.$$"
trap 'rm -f "$PENDING"' EXIT

# Single Node invocation — counts AND writes pending atomically
# `|| true` guards against `set -e`; we capture the real exit via $?
set +e
RESULT=$(node - "$FILE" "$PENDING" "$OLD" "$NEW" "$REPLACE_ALL" <<'NODE'
const fs = require('fs');
const [,, file, pending, oldStr, newStr, all] = process.argv;
const content = fs.readFileSync(file, 'utf8');

let count = 0, idx = 0;
while ((idx = content.indexOf(oldStr, idx)) !== -1) {
  count++; idx += oldStr.length;
}

if (count === 0) {
  process.stdout.write(JSON.stringify({ status: 'no_match', count: 0 }));
  process.exit(2);
}
if (count > 1 && all !== '1') {
  process.stdout.write(JSON.stringify({ status: 'ambiguous', count }));
  process.exit(3);
}

const out = all === '1'
  ? content.split(oldStr).join(newStr)
  : content.replace(oldStr, newStr);

fs.writeFileSync(pending, out);
process.stdout.write(JSON.stringify({
  status: 'ready', count,
  bytes_changed: out.length - content.length
}));
NODE
)
NODE_EXIT=$?
set -e

# Parse status from JSON without spawning another node
STATUS=$(printf '%s' "$RESULT" | sed -n 's/.*"status":"\([^"]*\)".*/\1/p')
COUNT=$(printf '%s' "$RESULT"  | sed -n 's/.*"count":\([0-9]*\).*/\1/p')

case "$NODE_EXIT" in
  0)  ;;
  2)  die "No match found for --old in $FILE" ;;
  3)  die "Found $COUNT matches; pass --all or make --old more unique" ;;
  *)  die "Replacement failed (node exit $NODE_EXIT)" ;;
esac

if [ -n "$EXPECTED_COUNT" ] && [ "$COUNT" != "$EXPECTED_COUNT" ]; then
  die "Expected $EXPECTED_COUNT replacements but found $COUNT"
fi

if [ "$OCE_DRY_RUN" = "1" ]; then
  rm -f "$PENDING"
  if [ "$OCE_JSON_MODE" = "1" ]; then
    emit_json status dry_run file "$FILE" matches "raw:$COUNT" \
      message "would replace $COUNT occurrence(s)"
  else
    info "DRY RUN: would replace $COUNT occurrence(s) in $FILE"
  fi
  exit 0
fi

BACKUP=$(create_backup "$FILE")
[ -n "$TXN" ] && [ -d "$OCE_TXN_DIR/$TXN" ] && \
  printf '%s\t%s\n' "$FILE" "$BACKUP" >> "$OCE_TXN_DIR/$TXN/files.log"

mv "$PENDING" "$FILE"
trap - EXIT

if [ "$SKIP_VALIDATE" = "0" ]; then
  if ! bash "$(dirname "$0")/oce-validate.sh" "$FILE" >/dev/null 2>&1; then
    cp "$BACKUP" "$FILE"
    die "Replacement caused validation failure — rolled back. Backup: $BACKUP"
  fi
fi

if [ "$OCE_JSON_MODE" = "1" ]; then
  emit_json status success file "$FILE" replacements "raw:$COUNT" \
    backup "$BACKUP" message "replaced $COUNT occurrence(s)"
else
  success "Replaced $COUNT occurrence(s) in $FILE"
fi


path: scripts/install.sh
content: #!/usr/bin/env bash
# install.sh — Optional deployment helper.
#
# Creates an `oce` wrapper on PATH that invokes bash <skill>/scripts/oce.sh.
# This is purely a convenience — the skill works fine when invoked directly
# as `bash scripts/oce.sh <command>`.

set -euo pipefail

SCRIPT_DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_ROOT="$(cd -P "$SCRIPT_DIR/.." && pwd)"

DEFAULT_BIN="$HOME/.local/bin"
INSTALL_BIN="${1:-$DEFAULT_BIN}"

mkdir -p "$INSTALL_BIN"

WRAPPER="$INSTALL_BIN/oce"
cat > "$WRAPPER" <<EOF
#!/usr/bin/env bash
exec bash "$SKILL_ROOT/scripts/oce.sh" "\$@"
EOF
chmod +x "$WRAPPER"

# Make sure node_modules are present (skill ships with them; this is a safety net)
if [ ! -d "$SKILL_ROOT/node_modules" ] && [ -f "$SKILL_ROOT/package.json" ]; then
  if command -v npm >/dev/null; then
    (cd "$SKILL_ROOT" && npm install --omit=dev --silent) || \
      echo "WARNING: npm install failed — AST features may not work"
  fi
fi

echo "✓ Wrapper installed at $WRAPPER"
echo "  → invokes bash $SKILL_ROOT/scripts/oce.sh"
echo
if echo ":$PATH:" | grep -q ":$INSTALL_BIN:"; then
  echo "✓ $INSTALL_BIN is on PATH — try: oce doctor"
else
  echo "  Add $INSTALL_BIN to PATH:"
  echo "    export PATH=\"$INSTALL_BIN:\$PATH\""
  echo "  Then: oce doctor"
fi


path: scripts/oce.sh
content: #!/usr/bin/env bash
# oce.sh — Dispatcher for the agentic_cli_coding skill.
# Routes subcommands to oce-*.sh scripts living next to it in scripts/.
#
# Invocation:
#   bash <skill>/scripts/oce.sh <command> [args]
#
# Or with the bin/ wrapper installed (via scripts/install.sh):
#   oce <command> [args]

set -euo pipefail

# Resolve our own location (follow symlinks)
_self="${BASH_SOURCE[0]:-$0}"
while [ -h "$_self" ]; do
  _dir="$(cd -P "$(dirname "$_self")" && pwd)"
  _self="$(readlink "$_self")"
  [[ "$_self" != /* ]] && _self="$_dir/$_self"
done
OCE_SCRIPTS="$(cd -P "$(dirname "$_self")" && pwd)"
OCE_HOME="$(cd -P "$OCE_SCRIPTS/.." && pwd)"
export OCE_HOME OCE_SCRIPTS

usage() {
  cat <<'EOF'
oce — Code editing for agentic coders

USAGE
  bash scripts/oce.sh <command> [options] [args]

DISCOVERY
  tree [--depth N] [path]                Project structure (skips noise dirs)
  find <pattern> [--type LANG] [path]    Multi-mode search with smart excludes
  grep-context <pattern> <file>          Grep with surrounding lines
  ast symbols <file>                     List functions/classes (JS/TS)

READING
  read <file>                            Whole file with line numbers
  read <file> --lines A:B                Specific range
  read <file> --around PATTERN [-c N]    N lines around first match

EDITING
  replace <file> --old STR --new STR     Exact string replacement (default: 1)
  insert  <file> --before-match P        Insert before first match
  insert  <file> --after-match P         Insert after first match
  insert  <file> --line N                Insert at specific line
  delete  <file> --lines A:B             Delete a range
  delete  <file> --match PATTERN         Delete every matching line
  patch apply <patchfile>                Apply unified diff (with rollback)
  patch create <file> < new              Generate diff against current file
  write   <file>                         Replace file contents from stdin

STRUCTURED EDITS (JS/TS)
  ast rename <file> --from N --to N      Rename identifier (scope-aware)
  ast extract <file> <symbol>            Print a function/class
  ast replace-symbol <file> <symbol>     Replace a function/class from stdin

VALIDATION & FORMAT
  validate <file>                        Auto-detect language & syntax-check
  format   <file> [--check]              Run language formatter (if available)
  diff     <file>                        Diff against last backup

SAFETY
  backup list [<file>]                   List backups
  backup restore <file> [--at N]         Restore Nth backup back (0 = newest)
  backup diff <file> [N]                 Diff vs Nth backup
  backup clean [DAYS]                    Remove backups older than DAYS (default 30)

TRANSACTIONS (multi-file atomic)
  transaction begin                      Print a fresh TXN_ID
  transaction status <txn>               Show files in transaction
  transaction validate <txn>             Validate every file in transaction
  transaction commit <txn>               Validate + finalize
  transaction rollback <txn>             Restore every file from snapshot
  transaction list                       Recent transactions

GLOBAL FLAGS
  --json                                 Machine-readable output
  --dry-run                              Show what would happen, change nothing
  --no-color                             Disable ANSI colors
  --txn <id>                             Run command inside an existing transaction

EXAMPLES
  bash scripts/oce.sh tree --depth 2
  bash scripts/oce.sh find "TODO" --type ts
  bash scripts/oce.sh replace config.js --old "old" --new "new"

See SKILL.md for the methodology and references/ for deeper docs.
EOF
}

if [ $# -eq 0 ]; then usage; exit 0; fi

CMD="$1"; shift
case "$CMD" in
  -h|--help|help)     usage ;;
  version|--version)  echo "oce 1.0.0" ;;
  read)               exec bash "$OCE_SCRIPTS/oce-read.sh" "$@" ;;
  write)              exec bash "$OCE_SCRIPTS/oce-write.sh" "$@" ;;
  replace)            exec bash "$OCE_SCRIPTS/oce-replace.sh" "$@" ;;
  insert)             exec bash "$OCE_SCRIPTS/oce-insert.sh" "$@" ;;
  delete)             exec bash "$OCE_SCRIPTS/oce-delete.sh" "$@" ;;
  patch)              exec bash "$OCE_SCRIPTS/oce-patch.sh" "$@" ;;
  find)               exec bash "$OCE_SCRIPTS/oce-find.sh" "$@" ;;
  grep-context)       exec bash "$OCE_SCRIPTS/oce-grep-context.sh" "$@" ;;
  tree)               exec bash "$OCE_SCRIPTS/oce-tree.sh" "$@" ;;
  validate)           exec bash "$OCE_SCRIPTS/oce-validate.sh" "$@" ;;
  format)             exec bash "$OCE_SCRIPTS/oce-format.sh" "$@" ;;
  diff)               exec bash "$OCE_SCRIPTS/oce-diff.sh" "$@" ;;
  backup)             exec bash "$OCE_SCRIPTS/oce-backup.sh" "$@" ;;
  transaction|txn)    exec bash "$OCE_SCRIPTS/oce-transaction.sh" "$@" ;;
  ast)                exec bash "$OCE_SCRIPTS/oce-ast.sh" "$@" ;;
  doctor)             exec bash "$OCE_SCRIPTS/oce-doctor.sh" "$@" ;;
  *)                  printf 'Unknown command: %s\n\n' "$CMD" >&2; usage >&2; exit 64 ;;
esac


path: scripts/oce-backup.sh
content: #!/usr/bin/env bash
# oce-backup — Backup management (list, restore, diff, clean).

OCE_CALLER="${BASH_SOURCE[0]}"
source "$(dirname "$0")/lib/paths.sh"
source "$(dirname "$0")/lib/common.sh"

SUBCMD="${1:-}"
shift || true

case "$SUBCMD" in
  list)
    FILE=""
    JSON=0
    while [ $# -gt 0 ]; do
      case "$1" in
        --json) JSON=1; shift ;;
        *) FILE="$1"; shift ;;
      esac
    done
    if [ -n "$FILE" ]; then
      backups=$(list_backups "$FILE")
    else
      backups=$(ls -t "$OCE_BACKUP_DIR"/*.bak 2>/dev/null | head -50 || true)
    fi
    if [ "$JSON" = "1" ]; then
      OCE_JSON_MODE=1
      node - <<NODE
const list = $(printf '%s' "$backups" | node -e 'let d=""; process.stdin.on("data",c=>d+=c).on("end",()=>process.stdout.write(JSON.stringify(d)))');
const arr = list ? list.split('\n').filter(Boolean) : [];
process.stdout.write(JSON.stringify({status:'success', count:arr.length, backups:arr}) + '\n');
NODE
    else
      if [ -z "$backups" ]; then
        info "No backups found"
      else
        printf '%s\n' "$backups"
      fi
    fi
    ;;

  restore)
    FILE=""; AT=0
    while [ $# -gt 0 ]; do
      case "$1" in
        --at) AT="$2"; shift 2 ;;
        --json) OCE_JSON_MODE=1; shift ;;
        *) FILE="$1"; shift ;;
      esac
    done
    [ -n "$FILE" ] || die "Usage: oce backup restore <file> [--at N]"
    backups=$(list_backups "$FILE")
    [ -n "$backups" ] || die "No backups found for $FILE"
    target=$(printf '%s\n' "$backups" | sed -n "$((AT + 1))p")
    [ -n "$target" ] || die "No backup at position $AT for $FILE (newest is 0)"
    cp "$target" "$FILE"
    if [ "$OCE_JSON_MODE" = "1" ]; then
      emit_json status success file "$FILE" restored_from "$target"
    else
      success "Restored $FILE from $target"
    fi
    ;;

  diff)
    FILE="${1:-}"; AT="${2:-0}"
    [ -n "$FILE" ] || die "Usage: oce backup diff <file> [N]"
    backups=$(list_backups "$FILE")
    [ -n "$backups" ] || die "No backups for $FILE"
    target=$(printf '%s\n' "$backups" | sed -n "$((AT + 1))p")
    [ -n "$target" ] || die "No backup at position $AT for $FILE"
    diff -u "$target" "$FILE"
    ;;

  clean)
    DAYS="${1:-30}"
    case "$DAYS" in ''|*[!0-9]*) die "DAYS must be a number" ;; esac
    removed=$(find "$OCE_BACKUP_DIR" -name '*.bak' -type f -mtime +"$DAYS" -print -delete 2>/dev/null | wc -l)
    success "Cleaned $removed backup(s) older than $DAYS days"
    ;;

  -h|--help|help|"")
    cat <<'EOF'
oce backup <subcommand>
  list [<file>]              List backups (newest first)
  restore <file> [--at N]    Restore Nth backup (0 = most recent)
  diff <file> [N]            Diff vs Nth backup
  clean [DAYS]               Delete backups older than DAYS (default 30)
EOF
    ;;

  *)
    die "Unknown subcommand: $SUBCMD (try: oce backup help)"
    ;;
esac


path: scripts/oce-find.sh
content: #!/usr/bin/env bash
# oce-find — Pattern search with sensible language filters and excludes.
# Defaults to fixed-string match (-F) for safety; pass --regex for regex.

OCE_CALLER="${BASH_SOURCE[0]}"
source "$(dirname "$0")/lib/paths.sh"
source "$(dirname "$0")/lib/common.sh"

PATTERN=""; PATH_ARG="."; TYPE=""; FILES_ONLY=0; MAX=200
USE_REGEX=0; CASE_INSENSITIVE=0

while [ $# -gt 0 ]; do
  case "$1" in
    --type)         TYPE="$2"; shift 2 ;;
    --files-only)   FILES_ONLY=1; shift ;;
    --max)          MAX="$2"; shift 2 ;;
    --regex)        USE_REGEX=1; shift ;;
    -i|--ignore-case) CASE_INSENSITIVE=1; shift ;;
    --json)         OCE_JSON_MODE="1"; shift ;;
    -h|--help)
      cat <<'EOF'
oce find <pattern> [--type LANG] [--regex] [-i] [--files-only] [path]
  Search for PATTERN under [path] (default: cwd). Auto-excludes node_modules,
  .git, dist, build, .next, __pycache__, vendor, target, .venv.

  --type LANG    Limit to a language: js, ts, py, go, rb, java, c, cpp, php,
                 vue, svelte, sh, sql, css, html, md (or any extension)
  --regex        Treat pattern as a regex (default is literal/fixed-string)
  -i             Case-insensitive
  --files-only   Print matching filenames only
  --max N        Cap results (default 200)
EOF
      exit 0 ;;
    -*) die "Unknown flag: $1" ;;
    *)  if [ -z "$PATTERN" ]; then PATTERN="$1"; else PATH_ARG="$1"; fi; shift ;;
  esac
done

[ -n "$PATTERN" ] || die "Usage: oce find <pattern> [--type LANG] [path]"

INCLUDE=()
case "$TYPE" in
  js)        INCLUDE=(--include='*.js' --include='*.mjs' --include='*.cjs') ;;
  ts)        INCLUDE=(--include='*.ts' --include='*.tsx') ;;
  jsx)       INCLUDE=(--include='*.jsx') ;;
  vue)       INCLUDE=(--include='*.vue') ;;
  svelte)    INCLUDE=(--include='*.svelte') ;;
  py)        INCLUDE=(--include='*.py') ;;
  go)        INCLUDE=(--include='*.go') ;;
  rb)        INCLUDE=(--include='*.rb') ;;
  java)      INCLUDE=(--include='*.java') ;;
  kt)        INCLUDE=(--include='*.kt' --include='*.kts') ;;
  c)         INCLUDE=(--include='*.c' --include='*.h') ;;
  cpp)       INCLUDE=(--include='*.cpp' --include='*.cc' --include='*.cxx' --include='*.hpp' --include='*.hh') ;;
  cs)        INCLUDE=(--include='*.cs') ;;
  php)       INCLUDE=(--include='*.php') ;;
  rs)        INCLUDE=(--include='*.rs') ;;
  sh)        INCLUDE=(--include='*.sh' --include='*.bash') ;;
  sql)       INCLUDE=(--include='*.sql') ;;
  css)       INCLUDE=(--include='*.css' --include='*.scss' --include='*.sass' --include='*.less') ;;
  html)      INCLUDE=(--include='*.html' --include='*.htm') ;;
  md)        INCLUDE=(--include='*.md' --include='*.mdx') ;;
  json)      INCLUDE=(--include='*.json') ;;
  yaml)      INCLUDE=(--include='*.yaml' --include='*.yml') ;;
  "")        INCLUDE=() ;;
  *)         INCLUDE=(--include="*.$TYPE") ;;
esac

EXCLUDE=(
  --exclude-dir=node_modules --exclude-dir=.git --exclude-dir=dist
  --exclude-dir=build --exclude-dir=.next --exclude-dir=__pycache__
  --exclude-dir=vendor --exclude-dir=target --exclude-dir=.venv
  --exclude-dir=.oce --exclude-dir=coverage --exclude-dir=.cache
)

FLAGS=(-r)
[ "$USE_REGEX" = "0" ] && FLAGS+=(-F)
[ "$CASE_INSENSITIVE" = "1" ] && FLAGS+=(-i)
if [ "$FILES_ONLY" = "1" ]; then FLAGS+=(-l); else FLAGS+=(-n); fi

# Run the search
RESULTS=$(grep "${FLAGS[@]}" "${INCLUDE[@]}" "${EXCLUDE[@]}" -- "$PATTERN" "$PATH_ARG" 2>/dev/null | head -n "$MAX" || true)

if [ "$OCE_JSON_MODE" = "1" ]; then
  if [ "$FILES_ONLY" = "1" ]; then
    node - <<NODE
const lines = $(printf '%s' "$RESULTS" | node -e 'let d=""; process.stdin.on("data",c=>d+=c).on("end",()=>{const arr=d?d.split("\n").filter(Boolean):[];process.stdout.write(JSON.stringify(arr))})');
process.stdout.write(JSON.stringify({status:"success", mode:"files-only", count:lines.length, files:lines}) + "\n");
NODE
  else
    node - <<NODE
const text = $(printf '%s' "$RESULTS" | node -e 'let d=""; process.stdin.on("data",c=>d+=c).on("end",()=>process.stdout.write(JSON.stringify(d)))');
const matches = text ? text.split("\n").filter(Boolean).map(line => {
  const m = line.match(/^([^:]+):(\d+):(.*)$/);
  return m ? { file: m[1], line: parseInt(m[2], 10), text: m[3] } : { file: null, line: null, text: line };
}) : [];
process.stdout.write(JSON.stringify({status:"success", count:matches.length, matches}) + "\n");
NODE
  fi
else
  if [ -z "$RESULTS" ]; then
    info "No matches"
  else
    printf '%s\n' "$RESULTS"
  fi
fi


path: scripts/oce-transaction.sh
content: #!/usr/bin/env bash
# oce-transaction — Atomic multi-file operations.
# Usage pattern:
#   TXN=$(oce transaction begin)
#   oce replace foo.js --old X --new Y --txn "$TXN"
#   oce replace bar.js --old X --new Y --txn "$TXN"
#   oce transaction validate "$TXN"
#   oce transaction commit "$TXN"   # or rollback

OCE_CALLER="${BASH_SOURCE[0]}"
source "$(dirname "$0")/lib/paths.sh"
source "$(dirname "$0")/lib/common.sh"

SUBCMD="${1:-}"
shift || true

case "$SUBCMD" in
  begin)
    TXN_ID="txn-$(date +%Y%m%d-%H%M%S)-$$"
    TXN_PATH="$OCE_TXN_DIR/$TXN_ID"
    mkdir -p "$TXN_PATH"
    {
      printf 'started_at\t%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
      printf 'pid\t%s\n' "$$"
      printf 'cwd\t%s\n' "$PWD"
    } > "$TXN_PATH/meta"
    : > "$TXN_PATH/files.log"
    printf '%s\n' "$TXN_ID"
    ;;

  status)
    TXN_ID="$1"
    TXN_PATH="$OCE_TXN_DIR/$TXN_ID"
    [ -d "$TXN_PATH" ] || die "Transaction not found: $TXN_ID"
    info "Transaction: $TXN_ID"
    cat "$TXN_PATH/meta"
    info "Files modified:"
    if [ -s "$TXN_PATH/files.log" ]; then
      while IFS=$'\t' read -r f b; do
        printf '  %s  (backup: %s)\n' "$f" "$b"
      done < "$TXN_PATH/files.log"
    else
      info "  (none yet)"
    fi
    ;;

  validate)
    TXN_ID="$1"
    TXN_PATH="$OCE_TXN_DIR/$TXN_ID"
    [ -d "$TXN_PATH" ] || die "Transaction not found: $TXN_ID"
    failed=0
    while IFS=$'\t' read -r file backup; do
      [ -z "$file" ] && continue
      if [ -f "$file" ]; then
        if bash "$(dirname "$0")/oce-validate.sh" "$file" >/dev/null 2>&1; then
          printf '  %s✓%s %s\n' "$C_GREEN" "$C_RESET" "$file"
        else
          printf '  %s✗%s %s\n' "$C_RED" "$C_RESET" "$file"
          failed=$((failed + 1))
        fi
      fi
    done < "$TXN_PATH/files.log"
    [ "$failed" = "0" ] || exit 1
    ;;

  commit)
    TXN_ID="$1"
    TXN_PATH="$OCE_TXN_DIR/$TXN_ID"
    [ -d "$TXN_PATH" ] || die "Transaction not found: $TXN_ID"

    failed=""
    while IFS=$'\t' read -r file backup; do
      [ -z "$file" ] && continue
      if [ -f "$file" ] && ! bash "$(dirname "$0")/oce-validate.sh" "$file" >/dev/null 2>&1; then
        failed="$failed $file"
      fi
    done < "$TXN_PATH/files.log"

    if [ -n "$failed" ]; then
      die "Validation failed for:$failed — run 'oce transaction rollback $TXN_ID'"
    fi

    mv "$TXN_PATH" "$OCE_TXN_DIR/committed-$TXN_ID"
    success "Transaction $TXN_ID committed"
    ;;

  rollback)
    TXN_ID="$1"
    TXN_PATH="$OCE_TXN_DIR/$TXN_ID"
    [ -d "$TXN_PATH" ] || die "Transaction not found: $TXN_ID"

    count=0
    # Process in reverse order so the *first* backup wins (oldest snapshot)
    tac "$TXN_PATH/files.log" | while IFS=$'\t' read -r file backup; do
      [ -z "$file" ] && continue
      if [ -f "$backup" ]; then
        cp "$backup" "$file"
        count=$((count + 1))
      fi
    done
    # Note: count inside `while | pipe` lives in subshell — recount
    count=$(wc -l < "$TXN_PATH/files.log" | tr -d ' ')

    mv "$TXN_PATH" "$OCE_TXN_DIR/rolled-back-$TXN_ID"
    success "Rolled back $count file(s) from transaction $TXN_ID"
    ;;

  list)
    ls -1t "$OCE_TXN_DIR" 2>/dev/null | head -20 || info "No transactions"
    ;;

  -h|--help|help|"")
    cat <<'EOF'
oce transaction <subcommand>
  begin                    Print a new transaction ID
  status <txn>             Show meta + files in transaction
  validate <txn>           Validate every file (exit 1 if any invalid)
  commit <txn>             Validate + finalize (rename dir to committed-*)
  rollback <txn>           Restore every file from snapshot
  list                     Recent transactions

Pass --txn <id> to oce replace/insert/delete/write/patch to register them.
EOF
    ;;

  *)
    die "Unknown subcommand: $SUBCMD"
    ;;
esac


path: scripts/lib/paths.sh
content: #!/usr/bin/env bash
# paths.sh — Robust path resolution for the agentic_cli_coding skill.
#
# Sourced by every oce-* script BEFORE common.sh. It computes OCE_HOME
# from the actual script location (resolving symlinks) so the skill works
# no matter where it's installed or invoked from. Also picks workspace-
# scoped storage dirs so backups/transactions live next to the project,
# not in the user's home, which keeps the skill self-contained.

# Resolve the directory of the *calling* script, following symlinks.
# Caller must have set OCE_CALLER="${BASH_SOURCE[0]}" or pass $0 in.
_oce_resolve_home() {
  local src="${OCE_CALLER:-${BASH_SOURCE[1]:-$0}}"
  # Follow symlinks
  while [ -h "$src" ]; do
    local dir
    dir="$(cd -P "$(dirname "$src")" && pwd)"
    src="$(readlink "$src")"
    [[ "$src" != /* ]] && src="$dir/$src"
  done
  local script_dir
  script_dir="$(cd -P "$(dirname "$src")" && pwd)"
  # The calling script lives in <OCE_HOME>/scripts/, so go one up.
  printf '%s\n' "$(cd -P "$script_dir/.." && pwd)"
}

# Only set OCE_HOME if not already exported by the caller
if [ -z "${OCE_HOME:-}" ]; then
  OCE_HOME="$(_oce_resolve_home)"
  export OCE_HOME
fi

# Workspace-scoped state. Defaults to a hidden dir in the current working
# directory, falling back to /tmp if cwd isn't writable. This keeps each
# project's edit history isolated and makes the skill safe to install
# read-only at OCE_HOME.
if [ -z "${OCE_STATE_DIR:-}" ]; then
  if [ -w "$PWD" ]; then
    OCE_STATE_DIR="$PWD/.oce"
  else
    OCE_STATE_DIR="${TMPDIR:-/tmp}/oce-$(id -u)"
  fi
  export OCE_STATE_DIR
fi

OCE_BACKUP_DIR="${OCE_BACKUP_DIR:-$OCE_STATE_DIR/backups}"
OCE_TXN_DIR="${OCE_TXN_DIR:-$OCE_STATE_DIR/transactions}"
OCE_LOG="${OCE_LOG:-$OCE_STATE_DIR/edit.log}"
export OCE_BACKUP_DIR OCE_TXN_DIR OCE_LOG

mkdir -p "$OCE_BACKUP_DIR" "$OCE_TXN_DIR" "$(dirname "$OCE_LOG")" 2>/dev/null || true

# NODE_PATH so ast-helper.js can `require('acorn')` regardless of cwd
if [ -d "$OCE_HOME/node_modules" ]; then
  export NODE_PATH="$OCE_HOME/node_modules${NODE_PATH:+:$NODE_PATH}"
fi


path: scripts/lib/common.sh
content: #!/usr/bin/env bash
# common.sh — Shared utilities for oce-* commands.
# Always source paths.sh BEFORE this file in the calling script.

set -euo pipefail

# Colors (stripped if not a tty or NO_COLOR is set)
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
  C_RED=$'\033[0;31m'
  C_GREEN=$'\033[0;32m'
  C_YELLOW=$'\033[0;33m'
  C_BLUE=$'\033[0;34m'
  C_DIM=$'\033[2m'
  C_RESET=$'\033[0m'
else
  C_RED="" C_GREEN="" C_YELLOW="" C_BLUE="" C_DIM="" C_RESET=""
fi

# JSON output mode (set by --json flag in calling scripts)
OCE_JSON_MODE="${OCE_JSON_MODE:-0}"
OCE_DRY_RUN="${OCE_DRY_RUN:-0}"

# ---------- Logging ----------
log() {
  local level="$1"; shift
  local msg="$*"
  printf '[%s] [%s] %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$level" "$msg" >> "$OCE_LOG" 2>/dev/null || true
}

# ---------- JSON helpers ----------
json_escape() {
  # Use Node since it's a hard dependency anyway
  node -e 'process.stdout.write(JSON.stringify(process.argv[1]))' -- "$1"
}

# Emit a JSON object. Args are alternating key/value pairs.
# String values are auto-escaped. Numeric/boolean values can be passed
# with the prefix "raw:" (e.g. "raw:42" or "raw:true").
emit_json() {
  local out="{" first=1
  while [ $# -ge 2 ]; do
    local k="$1" v="$2"; shift 2
    [ "$first" = 1 ] && first=0 || out+=","
    out+="\"$k\":"
    if [[ "$v" == raw:* ]]; then
      out+="${v#raw:}"
    else
      out+=$(json_escape "$v")
    fi
  done
  out+="}"
  printf '%s\n' "$out"
}

# ---------- Output ----------
die() {
  local msg="$*"
  log ERROR "$msg"
  if [ "$OCE_JSON_MODE" = "1" ]; then
    emit_json status error message "$msg" >&2
  else
    printf '%sERROR:%s %s\n' "$C_RED" "$C_RESET" "$msg" >&2
  fi
  exit 1
}

success() {
  local msg="$*"
  log SUCCESS "$msg"
  if [ "$OCE_JSON_MODE" = "1" ]; then
    emit_json status success message "$msg"
  else
    printf '%s✓%s %s\n' "$C_GREEN" "$C_RESET" "$msg"
  fi
}

warn() {
  local msg="$*"
  log WARN "$msg"
  if [ "$OCE_JSON_MODE" = "1" ]; then
    emit_json status warning message "$msg" >&2
  else
    printf '%s⚠%s  %s\n' "$C_YELLOW" "$C_RESET" "$msg" >&2
  fi
}

info() {
  [ "$OCE_JSON_MODE" = "1" ] && return 0
  printf '%s%s%s\n' "$C_DIM" "$*" "$C_RESET"
}

# ---------- File guards ----------
require_file() {
  local f="$1"
  [ -f "$f" ] || die "File not found: $f"
  [ -r "$f" ] || die "File not readable: $f"
}

require_writable() {
  local f="$1"
  if [ -e "$f" ]; then
    [ -w "$f" ] || die "File not writable: $f"
  else
    local dir; dir="$(dirname "$f")"
    [ -w "$dir" ] || die "Directory not writable: $dir"
  fi
}

# Refuse binary files and oversized files. Warns about lock files.
preflight_check() {
  local file="$1"
  require_file "$file"

  # Binary detection: look for NUL bytes in the first 8KB. Use Node since
  # bash strings can't reliably hold NULs and `file` may not be installed.
  if node -e "
    const fs=require('fs');
    const buf=Buffer.alloc(8192);
    const fd=fs.openSync(process.argv[1],'r');
    const n=fs.readSync(fd,buf,0,8192,0);
    fs.closeSync(fd);
    process.exit(buf.slice(0,n).includes(0) ? 0 : 1);
  " "$file" 2>/dev/null; then
    die "Refusing to edit binary file: $file"
  fi

  local size
  size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null || wc -c < "$file")
  local max_size="${OCE_MAX_FILE_SIZE:-5242880}"
  if [ "$size" -gt "$max_size" ]; then
    die "File too large ($size bytes > $max_size). Set OCE_MAX_FILE_SIZE to override."
  fi

  if [ -e "${file}.swp" ] || [ -e "${file}.lock" ]; then
    warn "Lock file detected near $file — another process may be editing it"
  fi
}

# ---------- Backups ----------
# Encode an absolute path into a single safe filename
_encode_path() {
  local p; p="$(cd -P "$(dirname "$1")" 2>/dev/null && pwd)/$(basename "$1")"
  printf '%s' "${p//\//__}"
}

create_backup() {
  local file="$1"
  require_file "$file"
  local hash ts encoded backup_path
  hash=$(sha256sum "$file" | cut -c1-12)
  ts=$(date +%Y%m%d-%H%M%S)
  encoded=$(_encode_path "$file")
  backup_path="$OCE_BACKUP_DIR/${encoded}.${ts}.${hash}.bak"
  cp -p "$file" "$backup_path"
  printf '%s\n' "$backup_path"
}

# List backups for a file, newest first
list_backups() {
  local file="$1"
  local encoded; encoded=$(_encode_path "$file")
  ls -t "$OCE_BACKUP_DIR"/"${encoded}".*.bak 2>/dev/null || true
}

# ---------- Language detection ----------
detect_language() {
  local file="$1"
  local base; base="$(basename "$file")"
  local ext="${base##*.}"
  # Lowercase
  ext="${ext,,}"
  case "$ext" in
    js|mjs|cjs)        echo "javascript" ;;
    ts)                echo "typescript" ;;
    tsx)               echo "tsx" ;;
    jsx)               echo "jsx" ;;
    vue)               echo "vue" ;;
    svelte)            echo "svelte" ;;
    py|pyi)            echo "python" ;;
    rb)                echo "ruby" ;;
    go)                echo "go" ;;
    rs)                echo "rust" ;;
    java)              echo "java" ;;
    kt|kts)            echo "kotlin" ;;
    swift)             echo "swift" ;;
    c|h)               echo "c" ;;
    cpp|cc|cxx|hpp|hh) echo "cpp" ;;
    cs)                echo "csharp" ;;
    sh|bash)           echo "bash" ;;
    zsh)               echo "zsh" ;;
    php)               echo "php" ;;
    json)              echo "json" ;;
    jsonc|json5)       echo "jsonc" ;;
    yaml|yml)          echo "yaml" ;;
    toml)              echo "toml" ;;
    xml)               echo "xml" ;;
    html|htm)          echo "html" ;;
    css)               echo "css" ;;
    scss|sass)         echo "scss" ;;
    less)              echo "less" ;;
    md|markdown|mdx)   echo "markdown" ;;
    sql)               echo "sql" ;;
    dockerfile)        echo "dockerfile" ;;
    *)
      # Filename-based fallback
      case "$base" in
        Dockerfile*|dockerfile*) echo "dockerfile" ;;
        Makefile|makefile|GNUmakefile) echo "makefile" ;;
        *.config.*) echo "javascript" ;;
        *) echo "unknown" ;;
      esac
      ;;
  esac
}

count_lines() {
  awk 'END{print NR}' "$1"
}

# Parse common flags from an arg list.
# Mutates the global OCE_JSON_MODE / OCE_DRY_RUN; returns the remaining
# args in a global array OCE_ARGS.
parse_global_flags() {
  OCE_ARGS=()
  while [ $# -gt 0 ]; do
    case "$1" in
      --json)    OCE_JSON_MODE=1; shift ;;
      --dry-run) OCE_DRY_RUN=1; shift ;;
      --no-color) C_RED="" C_GREEN="" C_YELLOW="" C_BLUE="" C_DIM="" C_RESET=""; shift ;;
      *) OCE_ARGS+=("$1"); shift ;;
    esac
  done
}


path: scripts/lib/ast-helper.js
content: #!/usr/bin/env node
// ast-helper.js — AST utilities for JS/TS via acorn.
// Resolves acorn from the skill's own node_modules so it works regardless
// of cwd or NODE_PATH.

const fs = require('fs');
const path = require('path');

const SKILL_ROOT = path.resolve(__dirname, '..', '..');

function loadAcorn() {
  // 1) Bundled with the skill
  try { return require(path.join(SKILL_ROOT, 'node_modules', 'acorn')); }
  catch (_) {}
  // 2) NODE_PATH (set by lib/paths.sh)
  try { return require('acorn'); }
  catch (_) {}
  // 3) Project-local
  try { return require(path.join(process.cwd(), 'node_modules', 'acorn')); }
  catch (_) {}
  console.error(JSON.stringify({
    status: 'error',
    error: 'acorn not available. Install the skill with its bundled node_modules, or `npm install acorn` in the project.'
  }));
  process.exit(1);
}

const acorn = loadAcorn();

function parse(source, options = {}) {
  return acorn.parse(source, {
    ecmaVersion: 'latest',
    sourceType: 'module',
    locations: true,
    allowHashBang: true,
    ...options,
  });
}

function walk(node, visitor, parent = null) {
  if (!node || typeof node !== 'object') return;
  if (node.type && visitor[node.type]) visitor[node.type](node, parent);
  for (const key of Object.keys(node)) {
    if (key === 'loc' || key === 'range' || key === 'parent') continue;
    const child = node[key];
    if (Array.isArray(child)) child.forEach(c => walk(c, visitor, node));
    else if (child && typeof child === 'object' && child.type) walk(child, visitor, node);
  }
}

function findSymbols(source) {
  const ast = parse(source);
  const symbols = [];
  walk(ast, {
    FunctionDeclaration(n) {
      symbols.push({ type: 'function', name: n.id?.name, line: n.loc.start.line, end: n.loc.end.line });
    },
    ClassDeclaration(n) {
      symbols.push({ type: 'class', name: n.id?.name, line: n.loc.start.line, end: n.loc.end.line });
    },
    VariableDeclaration(n) {
      for (const decl of n.declarations) {
        if (decl.init && (decl.init.type === 'FunctionExpression' || decl.init.type === 'ArrowFunctionExpression')) {
          symbols.push({ type: 'function', name: decl.id?.name, line: n.loc.start.line, end: n.loc.end.line });
        }
      }
    },
    MethodDefinition(n) {
      symbols.push({
        type: 'method',
        name: n.key?.name || (n.key?.value),
        line: n.loc.start.line,
        end: n.loc.end.line,
      });
    },
  });
  return symbols;
}

function renameSymbol(source, oldName, newName) {
  const ast = parse(source);
  const edits = [];
  const seen = new Set();
  walk(ast, {
    Identifier(n) {
      if (n.name === oldName && n.loc) {
        // Object shorthand `{ foo }` creates two Identifier nodes with
        // identical positions (key + value). Deduplicate by start position.
        const key = `${n.start}:${n.end}`;
        if (seen.has(key)) return;
        seen.add(key);
        edits.push({ start: n.start, end: n.end, replacement: newName });
      }
    },
  });
  edits.sort((a, b) => b.start - a.start);
  let result = source;
  for (const e of edits) result = result.slice(0, e.start) + e.replacement + result.slice(e.end);
  return { source: result, count: edits.length };
}

function extractSymbol(source, name) {
  const ast = parse(source);
  let result = null;
  walk(ast, {
    FunctionDeclaration(n) { if (n.id?.name === name) result = source.slice(n.start, n.end); },
    ClassDeclaration(n)    { if (n.id?.name === name) result = source.slice(n.start, n.end); },
    VariableDeclaration(n) {
      for (const decl of n.declarations) {
        if (decl.id?.name === name && decl.init &&
            (decl.init.type === 'FunctionExpression' || decl.init.type === 'ArrowFunctionExpression')) {
          result = source.slice(n.start, n.end);
        }
      }
    },
  });
  return result;
}

function replaceSymbol(source, name, newCode) {
  const ast = parse(source);
  let edit = null;
  walk(ast, {
    FunctionDeclaration(n) { if (n.id?.name === name) edit = { start: n.start, end: n.end }; },
    ClassDeclaration(n)    { if (n.id?.name === name) edit = { start: n.start, end: n.end }; },
  });
  if (!edit) throw new Error(`Symbol not found: ${name}`);
  return source.slice(0, edit.start) + newCode + source.slice(edit.end);
}

// CLI
const [,, cmd, ...args] = process.argv;
try {
  switch (cmd) {
    case 'symbols': {
      const src = fs.readFileSync(args[0], 'utf8');
      process.stdout.write(JSON.stringify({ status: 'success', file: args[0], symbols: findSymbols(src) }) + '\n');
      break;
    }
    case 'rename': {
      const [file, oldName, newName] = args;
      const src = fs.readFileSync(file, 'utf8');
      const { source: out, count } = renameSymbol(src, oldName, newName);
      const pending = file + '.oce.pending';
      fs.writeFileSync(pending, out);
      process.stdout.write(JSON.stringify({ status: 'ready', count, pending }) + '\n');
      break;
    }
    case 'extract': {
      const [file, name] = args;
      const src = fs.readFileSync(file, 'utf8');
      const code = extractSymbol(src, name);
      if (code) process.stdout.write(code);
      else { console.error(`Symbol not found: ${name}`); process.exit(1); }
      break;
    }
    case 'replace-symbol': {
      const [file, name] = args;
      const src = fs.readFileSync(file, 'utf8');
      const newCode = fs.readFileSync(0, 'utf8'); // stdin
      const out = replaceSymbol(src, name, newCode);
      const pending = file + '.oce.pending';
      fs.writeFileSync(pending, out);
      process.stdout.write(JSON.stringify({ status: 'ready', pending }) + '\n');
      break;
    }
    default:
      console.error('Usage: ast-helper.js <symbols|rename|extract|replace-symbol> [args]');
      process.exit(1);
  }
} catch (e) {
  console.error(JSON.stringify({ status: 'error', error: e.message }));
  process.exit(1);
}


path: scripts/oce-patch.sh
content: #!/usr/bin/env bash
# oce-patch — Apply unified diffs with backup + validation rollback.

OCE_CALLER="${BASH_SOURCE[0]}"
source "$(dirname "$0")/lib/paths.sh"
source "$(dirname "$0")/lib/common.sh"

SUBCMD="${1:-}"
shift || true

case "$SUBCMD" in
  apply)
    PATCHFILE=""; FUZZ=0; REVERSE=0; TXN=""; STRIP=1
    while [ $# -gt 0 ]; do
      case "$1" in
        --fuzz)    FUZZ="$2"; shift 2 ;;
        --reverse|-R) REVERSE=1; shift ;;
        --strip|-p) STRIP="$2"; shift 2 ;;
        --txn)     TXN="$2"; shift 2 ;;
        --json)    OCE_JSON_MODE="1"; shift ;;
        --dry-run) OCE_DRY_RUN="1"; shift ;;
        -*) die "Unknown flag: $1" ;;
        *)  PATCHFILE="$1"; shift ;;
      esac
    done

    [ -n "$PATCHFILE" ] || die "Usage: oce patch apply <patchfile>"
    require_file "$PATCHFILE"

    # Dry-run check first
    DRY_OUT=$(mktemp)
    trap 'rm -f "$DRY_OUT"' EXIT
    if ! patch --dry-run -p"$STRIP" --fuzz="$FUZZ" $([ "$REVERSE" = "1" ] && echo "-R") < "$PATCHFILE" > "$DRY_OUT" 2>&1; then
      cat "$DRY_OUT" >&2
      die "Patch does not apply cleanly. Adjust the diff or pass --fuzz N."
    fi

    if [ "$OCE_DRY_RUN" = "1" ]; then
      cat "$DRY_OUT"
      exit 0
    fi

    # Extract destination filenames from `+++ ` lines, strip a/ or b/ prefix
    FILES=$(grep -E '^\+\+\+ ' "$PATCHFILE" | awk '{print $2}' | sed 's|^[ab]/||' | sed 's|	.*$||')

    BACKUPS=""
    for f in $FILES; do
      [ "$f" = "/dev/null" ] && continue
      if [ -f "$f" ]; then
        BK=$(create_backup "$f")
        BACKUPS="$BACKUPS $f|$BK"
        [ -n "$TXN" ] && [ -d "$OCE_TXN_DIR/$TXN" ] && \
          printf '%s\t%s\n' "$f" "$BK" >> "$OCE_TXN_DIR/$TXN/files.log"
      fi
    done

    if patch -p"$STRIP" --fuzz="$FUZZ" $([ "$REVERSE" = "1" ] && echo "-R") < "$PATCHFILE"; then
      # Validate every affected file
      FAILED=""
      for f in $FILES; do
        [ "$f" = "/dev/null" ] && continue
        if [ -f "$f" ] && ! bash "$(dirname "$0")/oce-validate.sh" "$f" >/dev/null 2>&1; then
          FAILED="$FAILED $f"
        fi
      done
      if [ -n "$FAILED" ]; then
        warn "Validation failed for:$FAILED — rolling back"
        for pair in $BACKUPS; do
          file="${pair%|*}"; backup="${pair#*|}"
          [ -f "$backup" ] && cp "$backup" "$file"
        done
        die "Patch applied but validation failed — rolled back"
      fi
      n=$(echo "$FILES" | wc -w | tr -d ' ')
      success "Patch applied to $n file(s)"
    else
      die "Patch application failed"
    fi
    ;;

  create)
    FILE="$1"
    [ -n "$FILE" ] || die "Usage: oce patch create <file> < new_content"
    require_file "$FILE"
    TMPNEW=$(mktemp)
    trap 'rm -f "$TMPNEW"' EXIT
    cat > "$TMPNEW"
    diff -u "$FILE" "$TMPNEW" | sed "s|$TMPNEW|b/$FILE|; s|$FILE|a/$FILE|"
    ;;

  -h|--help|help|"")
    cat <<'EOF'
oce patch <apply|create>
  apply <patchfile>       Apply unified diff. Auto-backs-up affected files,
                          validates after, rolls back on validation failure.
    --fuzz N              Allow fuzzy context matching
    --reverse | -R        Apply patch in reverse
    --strip N | -p N      Strip path components (default 1)
    --txn ID              Register backups with a transaction
    --dry-run             Show what would be patched, change nothing
  create <file> < new     Generate a unified diff between <file> and stdin
EOF
    ;;

  *)
    die "Unknown subcommand: $SUBCMD"
    ;;
esac


path: scripts/oce-grep-context.sh
content: #!/usr/bin/env bash
# oce-grep-context — Grep with surrounding lines (handy for understanding a match in situ).

OCE_CALLER="${BASH_SOURCE[0]}"
source "$(dirname "$0")/lib/paths.sh"
source "$(dirname "$0")/lib/common.sh"

PATTERN=""; FILE=""; CONTEXT=5; USE_REGEX=0; CASE_INSENSITIVE=0

while [ $# -gt 0 ]; do
  case "$1" in
    --context|-c)     CONTEXT="$2"; shift 2 ;;
    --context=*)      CONTEXT="${1#*=}"; shift ;;
    --regex)          USE_REGEX=1; shift ;;
    -i|--ignore-case) CASE_INSENSITIVE=1; shift ;;
    --json)           OCE_JSON_MODE="1"; shift ;;
    -h|--help)
      cat <<'EOF'
oce grep-context <pattern> <file> [-c N] [--regex] [-i]
  Print N lines of context around every match (default 5).
  Default is fixed-string match; use --regex for patterns.
EOF
      exit 0 ;;
    -*) die "Unknown flag: $1" ;;
    *)  if [ -z "$PATTERN" ]; then PATTERN="$1"
        elif [ -z "$FILE" ]; then FILE="$1"
        else die "Unexpected arg: $1"; fi
        shift ;;
  esac
done

[ -n "$PATTERN" ] && [ -n "$FILE" ] || die "Usage: oce grep-context <pattern> <file>"
require_file "$FILE"

FLAGS=(-n -C "$CONTEXT")
[ "$USE_REGEX" = "0" ] && FLAGS+=(-F)
[ "$CASE_INSENSITIVE" = "1" ] && FLAGS+=(-i)

if [ "$OCE_JSON_MODE" = "1" ]; then
  RAW=$(grep "${FLAGS[@]}" -- "$PATTERN" "$FILE" 2>/dev/null || true)
  node - "$FILE" "$CONTEXT" <<NODE
const fs = require('fs');
const [,, file, ctx] = process.argv;
const raw = $(printf '%s' "$RAW" | node -e 'let d=""; process.stdin.on("data",c=>d+=c).on("end",()=>process.stdout.write(JSON.stringify(d)))');
const lines = raw ? raw.split("\n").filter(l => l !== "--") : [];
const parsed = lines.map(l => {
  const m = l.match(/^(\d+)([:-])(.*)$/);
  return m ? { line: +m[1], match: m[2] === ':', text: m[3] } : { line: null, match: false, text: l };
});
process.stdout.write(JSON.stringify({status:"success", file, context:+ctx, hits:parsed}) + "\n");
NODE
else
  grep "${FLAGS[@]}" --color=auto -- "$PATTERN" "$FILE" || die "No matches in $FILE"
fi


path: scripts/oce-read.sh
content: #!/usr/bin/env bash
# oce-read — Smart file reader with line numbers and context modes.

OCE_CALLER="${BASH_SOURCE[0]}"
source "$(dirname "$0")/lib/paths.sh"
source "$(dirname "$0")/lib/common.sh"

FILE=""; LINES=""; AROUND=""; CONTEXT="10"; SHOW_LINES="1"

while [ $# -gt 0 ]; do
  case "$1" in
    --lines)      LINES="$2"; shift 2 ;;
    --lines=*)    LINES="${1#*=}"; shift ;;
    --around)     AROUND="$2"; shift 2 ;;
    --around=*)   AROUND="${1#*=}"; shift ;;
    --context|-c) CONTEXT="$2"; shift 2 ;;
    --context=*)  CONTEXT="${1#*=}"; shift ;;
    --no-numbers) SHOW_LINES="0"; shift ;;
    --json)       OCE_JSON_MODE="1"; shift ;;
    --no-color)   C_BLUE="" C_RESET=""; shift ;;
    -h|--help)
      cat <<'EOF'
oce read <file> [options]
  --lines A:B          Show lines A through B (B may be omitted for single line)
  --around PATTERN     Show CONTEXT lines around the first match
  --context N | -c N   Lines of context for --around (default 10)
  --no-numbers         Omit line number gutter
  --json               JSON output
EOF
      exit 0 ;;
    -*) die "Unknown flag: $1" ;;
    *)  FILE="$1"; shift ;;
  esac
done

[ -n "$FILE" ] || die "Usage: oce read <file> [--lines A:B | --around PATTERN]"
require_file "$FILE"

TOTAL=$(count_lines "$FILE")
LANG=$(detect_language "$FILE")

if [ -n "$LINES" ]; then
  START="${LINES%:*}"
  END="${LINES#*:}"
  [ "$START" = "$LINES" ] && END="$START"
elif [ -n "$AROUND" ]; then
  MATCH=$(grep -n -F -- "$AROUND" "$FILE" | head -1 | cut -d: -f1 || true)
  [ -n "$MATCH" ] || die "Pattern not found: $AROUND"
  START=$((MATCH - CONTEXT))
  END=$((MATCH + CONTEXT))
  [ "$START" -lt 1 ] && START=1
  [ "$END" -gt "$TOTAL" ] && END="$TOTAL"
else
  START=1
  END="$TOTAL"
fi

# Validate
case "$START" in ''|*[!0-9]*) die "Invalid start line: $START" ;; esac
case "$END" in ''|*[!0-9]*) die "Invalid end line: $END" ;; esac
[ "$START" -ge 1 ] || die "Start line must be >= 1"
[ "$END" -le "$TOTAL" ] || die "End line $END exceeds file length $TOTAL"
[ "$START" -le "$END" ] || die "Start must be <= end"

if [ "$OCE_JSON_MODE" = "1" ]; then
  node - "$FILE" "$START" "$END" "$LANG" "$TOTAL" <<'NODE'
const fs = require('fs');
const [,, file, s, e, lang, total] = process.argv;
const lines = fs.readFileSync(file, 'utf8').split('\n');
const start = +s, end = +e;
const slice = lines.slice(start - 1, end).map((content, i) => ({
  line: start + i, content
}));
process.stdout.write(JSON.stringify({
  status: 'success', file, language: lang,
  total_lines: +total, range: { start, end }, lines: slice
}) + '\n');
NODE
else
  printf '%s═══ %s ═══%s\n' "$C_BLUE" "$FILE" "$C_RESET"
  printf '%slang:%s %s  %slines:%s %d-%d of %d\n' \
    "$C_BLUE" "$C_RESET" "$LANG" "$C_BLUE" "$C_RESET" "$START" "$END" "$TOTAL"
  printf '%s───────────────────────%s\n' "$C_BLUE" "$C_RESET"
  if [ "$SHOW_LINES" = "1" ]; then
    awk -v s="$START" -v e="$END" \
      'NR>=s && NR<=e { printf "%5d │ %s\n", NR, $0 } NR>e{exit}' "$FILE"
  else
    sed -n "${START},${END}p" "$FILE"
  fi
fi


path: scripts/oce-insert.sh
content: #!/usr/bin/env bash
# oce-insert — Insert content at a line, before or after a pattern match.

OCE_CALLER="${BASH_SOURCE[0]}"
source "$(dirname "$0")/lib/paths.sh"
source "$(dirname "$0")/lib/common.sh"

FILE=""; LINE=""; BEFORE=""; AFTER=""
CONTENT=""; CONTENT_FILE=""; TXN=""; SKIP_VALIDATE="0"

while [ $# -gt 0 ]; do
  case "$1" in
    --line)         LINE="$2"; shift 2 ;;
    --before-match) BEFORE="$2"; shift 2 ;;
    --after-match)  AFTER="$2"; shift 2 ;;
    --content)      CONTENT="$2"; shift 2 ;;
    --content-file) CONTENT_FILE="$2"; shift 2 ;;
    --txn)          TXN="$2"; shift 2 ;;
    --no-validate)  SKIP_VALIDATE="1"; shift ;;
    --json)         OCE_JSON_MODE="1"; shift ;;
    --dry-run)      OCE_DRY_RUN="1"; shift ;;
    -h|--help)
      cat <<'EOF'
oce insert <file> [--line N | --before-match P | --after-match P] (--content S | --content-file F | < stdin)
  Inserts the given content at the chosen anchor.
  Anchor exactly one:
    --line N           Insert before line N (1-indexed)
    --before-match P   Insert before the first line containing literal P
    --after-match P    Insert after the first line containing literal P
EOF
      exit 0 ;;
    -*) die "Unknown flag: $1" ;;
    *)  FILE="$1"; shift ;;
  esac
done

[ -n "$FILE" ] || die "Usage: oce insert <file> [--line N | --before-match P | --after-match P]"
require_file "$FILE"
preflight_check "$FILE"

# Resolve content
if [ -n "$CONTENT_FILE" ]; then
  CONTENT=$(cat "$CONTENT_FILE")
elif [ -z "$CONTENT" ] && [ ! -t 0 ]; then
  CONTENT=$(cat)
fi
[ -n "$CONTENT" ] || die "No content provided (use --content, --content-file, or pipe to stdin)"

# Resolve target line
ANCHOR_COUNT=0
[ -n "$LINE" ]   && ANCHOR_COUNT=$((ANCHOR_COUNT + 1))
[ -n "$BEFORE" ] && ANCHOR_COUNT=$((ANCHOR_COUNT + 1))
[ -n "$AFTER" ]  && ANCHOR_COUNT=$((ANCHOR_COUNT + 1))
[ "$ANCHOR_COUNT" -eq 1 ] || die "Specify exactly one of --line, --before-match, --after-match"

if [ -n "$LINE" ]; then
  TARGET="$LINE"
elif [ -n "$BEFORE" ]; then
  M=$(grep -n -F -- "$BEFORE" "$FILE" | head -1 | cut -d: -f1 || true)
  [ -n "$M" ] || die "Pattern not found: $BEFORE"
  TARGET="$M"
else
  M=$(grep -n -F -- "$AFTER" "$FILE" | head -1 | cut -d: -f1 || true)
  [ -n "$M" ] || die "Pattern not found: $AFTER"
  TARGET=$((M + 1))
fi

if [ "$OCE_DRY_RUN" = "1" ]; then
  inserted_lines=$(printf '%s\n' "$CONTENT" | wc -l)
  if [ "$OCE_JSON_MODE" = "1" ]; then
    emit_json status dry_run file "$FILE" target_line "raw:$TARGET" \
      lines_to_insert "raw:$inserted_lines"
  else
    info "DRY RUN: would insert $inserted_lines line(s) at line $TARGET in $FILE"
  fi
  exit 0
fi

BACKUP=$(create_backup "$FILE")
[ -n "$TXN" ] && [ -d "$OCE_TXN_DIR/$TXN" ] && \
  printf '%s\t%s\n' "$FILE" "$BACKUP" >> "$OCE_TXN_DIR/$TXN/files.log"

node - "$FILE" "$TARGET" "$CONTENT" <<'NODE'
const fs = require('fs');
const [,, file, target, content] = process.argv;
const lines = fs.readFileSync(file, 'utf8').split('\n');
const at = parseInt(target, 10) - 1;
const newLines = content.split('\n');
if (newLines[newLines.length - 1] === '') newLines.pop();
lines.splice(at, 0, ...newLines);
fs.writeFileSync(file, lines.join('\n'));
NODE

if [ "$SKIP_VALIDATE" = "0" ]; then
  if ! bash "$(dirname "$0")/oce-validate.sh" "$FILE" >/dev/null 2>&1; then
    cp "$BACKUP" "$FILE"
    die "Insertion caused validation failure — rolled back. Backup: $BACKUP"
  fi
fi

INSERTED=$(printf '%s\n' "$CONTENT" | wc -l)
if [ "$OCE_JSON_MODE" = "1" ]; then
  emit_json status success file "$FILE" target_line "raw:$TARGET" \
    inserted_lines "raw:$INSERTED" backup "$BACKUP"
else
  success "Inserted $INSERTED line(s) at line $TARGET in $FILE"
fi


path: scripts/oce-delete.sh
content: #!/usr/bin/env bash
# oce-delete — Remove lines by range or pattern.

OCE_CALLER="${BASH_SOURCE[0]}"
source "$(dirname "$0")/lib/paths.sh"
source "$(dirname "$0")/lib/common.sh"

FILE=""; LINES=""; MATCH=""; TXN=""; SKIP_VALIDATE="0"

while [ $# -gt 0 ]; do
  case "$1" in
    --lines)        LINES="$2"; shift 2 ;;
    --match)        MATCH="$2"; shift 2 ;;
    --txn)          TXN="$2"; shift 2 ;;
    --no-validate)  SKIP_VALIDATE="1"; shift ;;
    --json)         OCE_JSON_MODE="1"; shift ;;
    --dry-run)      OCE_DRY_RUN="1"; shift ;;
    -h|--help)
      cat <<'EOF'
oce delete <file> [--lines A:B | --match LITERAL]
  --lines A:B   Delete inclusive line range
  --match S     Delete every line containing literal string S
EOF
      exit 0 ;;
    -*) die "Unknown flag: $1" ;;
    *)  FILE="$1"; shift ;;
  esac
done

[ -n "$FILE" ] || die "Usage: oce delete <file> [--lines A:B | --match S]"
require_file "$FILE"
preflight_check "$FILE"
[ -n "$LINES" ] || [ -n "$MATCH" ] || die "Must specify --lines or --match"
[ -n "$LINES" ] && [ -n "$MATCH" ] && die "Specify only one of --lines or --match"

# Compute deletion count for dry-run / reporting
if [ -n "$LINES" ]; then
  S="${LINES%:*}"; E="${LINES#*:}"
  case "$S" in ''|*[!0-9]*) die "Invalid start: $S" ;; esac
  case "$E" in ''|*[!0-9]*) die "Invalid end: $E" ;; esac
  COUNT=$((E - S + 1))
else
  COUNT=$(grep -c -F -- "$MATCH" "$FILE" || true)
  [ -n "$COUNT" ] || COUNT=0
  [ "$COUNT" -gt 0 ] || die "Pattern not found: $MATCH"
fi

if [ "$OCE_DRY_RUN" = "1" ]; then
  if [ "$OCE_JSON_MODE" = "1" ]; then
    emit_json status dry_run file "$FILE" lines_to_delete "raw:$COUNT"
  else
    info "DRY RUN: would delete $COUNT line(s) from $FILE"
  fi
  exit 0
fi

BACKUP=$(create_backup "$FILE")
[ -n "$TXN" ] && [ -d "$OCE_TXN_DIR/$TXN" ] && \
  printf '%s\t%s\n' "$FILE" "$BACKUP" >> "$OCE_TXN_DIR/$TXN/files.log"

if [ -n "$LINES" ]; then
  # Use Node — sed -i is not portable across Linux/macOS
  node - "$FILE" "$S" "$E" <<'NODE'
const fs = require('fs');
const [,, file, s, e] = process.argv;
const lines = fs.readFileSync(file, 'utf8').split('\n');
lines.splice(parseInt(s, 10) - 1, parseInt(e, 10) - parseInt(s, 10) + 1);
fs.writeFileSync(file, lines.join('\n'));
NODE
else
  TMP="${FILE}.oce.tmp.$$"
  grep -v -F -- "$MATCH" "$FILE" > "$TMP" || true
  mv "$TMP" "$FILE"
fi

if [ "$SKIP_VALIDATE" = "0" ]; then
  if ! bash "$(dirname "$0")/oce-validate.sh" "$FILE" >/dev/null 2>&1; then
    cp "$BACKUP" "$FILE"
    die "Deletion caused validation failure — rolled back. Backup: $BACKUP"
  fi
fi

if [ "$OCE_JSON_MODE" = "1" ]; then
  emit_json status success file "$FILE" deleted_lines "raw:$COUNT" backup "$BACKUP"
else
  success "Deleted $COUNT line(s) from $FILE"
fi


path: scripts/oce-diff.sh
content: #!/usr/bin/env bash
# oce-diff — Show what changed since the last backup.

OCE_CALLER="${BASH_SOURCE[0]}"
source "$(dirname "$0")/lib/paths.sh"
source "$(dirname "$0")/lib/common.sh"

FILE=""
while [ $# -gt 0 ]; do
  case "$1" in
    --json) OCE_JSON_MODE="1"; shift ;;
    -h|--help)
      cat <<'EOF'
oce diff <file>
  Show unified diff against the most recent backup of <file>.
  Backups are created automatically by oce-replace, oce-insert, oce-delete,
  oce-write, oce-patch.
EOF
      exit 0 ;;
    *) FILE="$1"; shift ;;
  esac
done

[ -n "$FILE" ] || die "Usage: oce diff <file>"
require_file "$FILE"

LATEST=$(list_backups "$FILE" | head -1)
[ -n "$LATEST" ] || { warn "No backup to compare against for $FILE"; exit 0; }

if [ "$OCE_JSON_MODE" = "1" ]; then
  DIFF=$(diff -u "$LATEST" "$FILE" 2>/dev/null || true)
  emit_json status success file "$FILE" backup "$LATEST" diff "$DIFF"
else
  diff -u "$LATEST" "$FILE" | awk '
    /^---/ { print "\033[1m" $0 "\033[0m"; next }
    /^\+\+\+/ { print "\033[1m" $0 "\033[0m"; next }
    /^@@/  { print "\033[36m" $0 "\033[0m"; next }
    /^-/   { print "\033[31m" $0 "\033[0m"; next }
    /^\+/  { print "\033[32m" $0 "\033[0m"; next }
    { print }
  '
fi


path: scripts/oce-format.sh
content: #!/usr/bin/env bash
# oce-format — Run the canonical formatter for a file's language, if available.

OCE_CALLER="${BASH_SOURCE[0]}"
source "$(dirname "$0")/lib/paths.sh"
source "$(dirname "$0")/lib/common.sh"

FILE=""; CHECK_ONLY=0

while [ $# -gt 0 ]; do
  case "$1" in
    --check)  CHECK_ONLY=1; shift ;;
    --json)   OCE_JSON_MODE="1"; shift ;;
    -h|--help)
      cat <<'EOF'
oce format <file> [--check]
  Runs the language's canonical formatter:
    JS/TS/JSON/CSS/MD: prettier (project-local preferred)
    Python:            black, then ruff format
    Go:                gofmt
    Rust:              rustfmt
    PHP:               php-cs-fixer
    Ruby:              rubocop -A
  --check    Don't write — just report whether the file is formatted
EOF
      exit 0 ;;
    -*) die "Unknown flag: $1" ;;
    *)  FILE="$1"; shift ;;
  esac
done

[ -n "$FILE" ] || die "Usage: oce format <file> [--check]"
require_file "$FILE"
LANG=$(detect_language "$FILE")
FORMATTER="none"
RESULT="not_run"

resolve_local() {
  if [ -x "./node_modules/.bin/$1" ]; then echo "./node_modules/.bin/$1"
  elif command -v "$1" >/dev/null; then echo "$1"
  else echo ""; fi
}

run_check_or_write() {
  local writer_cmd="$1" check_cmd="$2"
  if [ "$CHECK_ONLY" = "1" ]; then
    if eval "$check_cmd" >/dev/null 2>&1; then RESULT="formatted"; else RESULT="needs_format"; return 1; fi
  else
    BACKUP=$(create_backup "$FILE")
    if eval "$writer_cmd"; then RESULT="reformatted"
    else cp "$BACKUP" "$FILE"; RESULT="failed"; return 1; fi
  fi
}

case "$LANG" in
  javascript|typescript|jsx|tsx|json|css|scss|less|markdown|html|vue|svelte)
    P=$(resolve_local prettier)
    if [ -n "$P" ]; then
      FORMATTER="prettier"
      run_check_or_write "$P --write '$FILE'" "$P --check '$FILE'" || true
    fi ;;
  python)
    if command -v black >/dev/null; then
      FORMATTER="black"
      run_check_or_write "black '$FILE'" "black --check '$FILE'" || true
    elif command -v ruff >/dev/null; then
      FORMATTER="ruff format"
      run_check_or_write "ruff format '$FILE'" "ruff format --check '$FILE'" || true
    fi ;;
  go)
    if command -v gofmt >/dev/null; then
      FORMATTER="gofmt"
      run_check_or_write "gofmt -w '$FILE'" "gofmt -l '$FILE' | grep -q . && exit 1; exit 0" || true
    fi ;;
  rust)
    if command -v rustfmt >/dev/null; then
      FORMATTER="rustfmt"
      run_check_or_write "rustfmt '$FILE'" "rustfmt --check '$FILE'" || true
    fi ;;
  php)
    if command -v php-cs-fixer >/dev/null; then
      FORMATTER="php-cs-fixer"
      run_check_or_write "php-cs-fixer fix '$FILE'" "php-cs-fixer fix --dry-run '$FILE'" || true
    fi ;;
  ruby)
    if command -v rubocop >/dev/null; then
      FORMATTER="rubocop"
      run_check_or_write "rubocop -A '$FILE'" "rubocop '$FILE'" || true
    fi ;;
esac

if [ "$OCE_JSON_MODE" = "1" ]; then
  emit_json status success file "$FILE" language "$LANG" \
    formatter "$FORMATTER" result "$RESULT"
else
  if [ "$FORMATTER" = "none" ]; then
    info "$FILE: no formatter configured for $LANG"
  else
    success "$FILE: $FORMATTER → $RESULT"
  fi
fi


path: scripts/oce-doctor.sh
content: #!/usr/bin/env bash
# oce-doctor — Verify the toolkit is installed correctly.

OCE_CALLER="${BASH_SOURCE[0]}"
source "$(dirname "$0")/lib/paths.sh"
source "$(dirname "$0")/lib/common.sh"

OCE_JSON_MODE="${OCE_JSON_MODE:-0}"
for a in "$@"; do
  [ "$a" = "--json" ] && OCE_JSON_MODE=1
done

errors=0
warnings=0
declare -A RESULTS

check() {
  local label="$1" cmd="$2" required="${3:-1}"
  if eval "$cmd" >/dev/null 2>&1; then
    RESULTS["$label"]="ok"
    [ "$OCE_JSON_MODE" = "0" ] && printf '  %s✓%s %s\n' "$C_GREEN" "$C_RESET" "$label"
  else
    if [ "$required" = "1" ]; then
      RESULTS["$label"]="missing"
      errors=$((errors + 1))
      [ "$OCE_JSON_MODE" = "0" ] && printf '  %s✗%s %s\n' "$C_RED" "$C_RESET" "$label"
    else
      RESULTS["$label"]="absent"
      warnings=$((warnings + 1))
      [ "$OCE_JSON_MODE" = "0" ] && printf '  %s○%s %s (optional)\n' "$C_YELLOW" "$C_RESET" "$label"
    fi
  fi
  return 0
}

if [ "$OCE_JSON_MODE" = "0" ]; then
  printf '%sEnvironment%s\n' "$C_BLUE" "$C_RESET"
  printf '  OCE_HOME      = %s\n' "$OCE_HOME"
  printf '  OCE_STATE_DIR = %s\n\n' "$OCE_STATE_DIR"
  printf '%sCore tools%s\n' "$C_BLUE" "$C_RESET"
fi

check "node"  "command -v node"
check "patch" "command -v patch"
check "diff"  "command -v diff"
check "grep"  "command -v grep"
check "acorn" "node -e \"require('$OCE_HOME/node_modules/acorn')\""

[ "$OCE_JSON_MODE" = "0" ] && printf '\n%sOptional formatters/validators%s\n' "$C_BLUE" "$C_RESET"

check "prettier"   "command -v prettier || [ -x ./node_modules/.bin/prettier ]" 0
check "eslint"     "command -v eslint   || [ -x ./node_modules/.bin/eslint ]"   0
check "tsc"        "command -v tsc      || [ -x ./node_modules/.bin/tsc ]"      0
check "gofmt"      "command -v gofmt"   0
check "rustfmt"    "command -v rustfmt" 0
check "php"        "command -v php"     0
check "ruby"       "command -v ruby"    0
check "python3"    "command -v python3" 0
check "rubocop"    "command -v rubocop" 0
check "black"      "command -v black"   0

if [ "$OCE_JSON_MODE" = "1" ]; then
  checks="{"
  first=1
  for k in "${!RESULTS[@]}"; do
    [ $first = 1 ] && first=0 || checks+=","
    checks+="\"$k\":\"${RESULTS[$k]}\""
  done
  checks+="}"
  emit_json status "$([ $errors -eq 0 ] && echo ok || echo error)" \
    errors "raw:$errors" warnings "raw:$warnings" \
    home "$OCE_HOME" state_dir "$OCE_STATE_DIR" \
    checks "raw:$checks"
else
  printf '\n'
  if [ $errors -eq 0 ]; then
    success "Setup OK ($warnings optional tool(s) missing — that's fine)"
  else
    die "$errors required tool(s) missing"
  fi
fi


path: scripts/oce-tree.sh
content: #!/usr/bin/env bash
# oce-tree — Project structure viewer with sane excludes.

OCE_CALLER="${BASH_SOURCE[0]}"
source "$(dirname "$0")/lib/paths.sh"
source "$(dirname "$0")/lib/common.sh"

DEPTH=3; ROOT="."; SHOW_HIDDEN=0

while [ $# -gt 0 ]; do
  case "$1" in
    --depth|-d)   DEPTH="$2"; shift 2 ;;
    --depth=*)    DEPTH="${1#*=}"; shift ;;
    --hidden)     SHOW_HIDDEN=1; shift ;;
    --json)       OCE_JSON_MODE="1"; shift ;;
    -h|--help)
      cat <<'EOF'
oce tree [path] [--depth N] [--hidden]
  Show project structure, skipping noise dirs.
  --depth N   Max depth (default 3)
  --hidden    Include dotfiles
EOF
      exit 0 ;;
    -*) die "Unknown flag: $1" ;;
    *)  ROOT="$1"; shift ;;
  esac
done

[ -d "$ROOT" ] || die "Not a directory: $ROOT"

# Build a Node script that walks the tree with filters
node - "$ROOT" "$DEPTH" "$SHOW_HIDDEN" "$OCE_JSON_MODE" <<'NODE'
const fs = require('fs');
const path = require('path');
const [,, root, depth, showHidden, jsonMode] = process.argv;
const maxDepth = parseInt(depth, 10);
const json = jsonMode === '1';
const SKIP = new Set([
  'node_modules','.git','dist','build','.next','__pycache__',
  'vendor','target','.venv','venv','.oce','coverage','.cache',
  '.idea','.vscode','.DS_Store','out','.turbo','.parcel-cache'
]);

function walk(dir, level, prefix, lines, treeJson) {
  if (level > maxDepth) return;
  let entries;
  try { entries = fs.readdirSync(dir, { withFileTypes: true }); }
  catch { return; }
  entries = entries
    .filter(e => showHidden === '1' || !e.name.startsWith('.'))
    .filter(e => !SKIP.has(e.name))
    .sort((a, b) => {
      if (a.isDirectory() !== b.isDirectory()) return a.isDirectory() ? -1 : 1;
      return a.name.localeCompare(b.name);
    });
  entries.forEach((e, i) => {
    const last = i === entries.length - 1;
    const branch = last ? '└── ' : '├── ';
    const full = path.join(dir, e.name);
    const node = { name: e.name, type: e.isDirectory() ? 'dir' : 'file', children: [] };
    treeJson.push(node);
    lines.push(prefix + branch + e.name + (e.isDirectory() ? '/' : ''));
    if (e.isDirectory()) {
      walk(full, level + 1, prefix + (last ? '    ' : '│   '), lines, node.children);
    }
  });
}

const lines = [root];
const tree = { name: root, type: 'dir', children: [] };
walk(root, 1, '', lines, tree.children);

if (json) {
  process.stdout.write(JSON.stringify({status:'success', root, depth: maxDepth, tree}) + '\n');
} else {
  process.stdout.write(lines.join('\n') + '\n');
}
NODE


path: scripts/oce-validate.sh
content: #!/usr/bin/env bash
# oce-validate — Multi-language syntax validation. Best-effort: when a
# language-specific validator isn't installed, the file passes (we can't
# claim it's invalid). Validators are checked in order of reliability.

OCE_CALLER="${BASH_SOURCE[0]}"
source "$(dirname "$0")/lib/paths.sh"
source "$(dirname "$0")/lib/common.sh"

FILE=""
while [ $# -gt 0 ]; do
  case "$1" in
    --json)  OCE_JSON_MODE="1"; shift ;;
    -h|--help)
      cat <<'EOF'
oce validate <file>
  Auto-detects language from extension and runs the appropriate syntax check.
  Returns exit 0 if valid OR no validator available; non-zero on definite syntax error.
  Supported (when tools installed): JS/TS, JSON, YAML, Python, Bash, Go, Rust,
  PHP, Ruby, HTML, CSS. See references/language-support.md for details.
EOF
      exit 0 ;;
    *) FILE="$1"; shift ;;
  esac
done

[ -n "$FILE" ] || die "Usage: oce validate <file>"
require_file "$FILE"

LANG=$(detect_language "$FILE")
VALID=1
OUTPUT=""
VALIDATOR="none"

run() { OUTPUT=$("$@" 2>&1); local rc=$?; return $rc; }

case "$LANG" in
  javascript|jsx)
    if command -v node >/dev/null; then
      VALIDATOR="node --check"
      run node --check "$FILE" || VALID=0
    fi ;;
  typescript|tsx)
    if [ -x "./node_modules/.bin/tsc" ]; then
      VALIDATOR="local tsc"
      run ./node_modules/.bin/tsc --noEmit --allowJs --skipLibCheck "$FILE" || VALID=0
    elif command -v tsc >/dev/null; then
      VALIDATOR="tsc"
      run tsc --noEmit --allowJs --skipLibCheck "$FILE" || VALID=0
    elif command -v node >/dev/null; then
      # Last resort: parse with acorn after stripping types (best-effort only)
      VALIDATOR="acorn (TS stripped, syntax-only)"
      OUTPUT=$(node -e "
        try {
          const fs = require('fs');
          const acorn = require('acorn');
          let src = fs.readFileSync('$FILE','utf8');
          // Naive type stripping — only catches gross syntax issues
          src = src.replace(/:\s*[A-Za-z_\$][\w\.<>\[\]\|& ,?]*/g,'');
          src = src.replace(/\binterface\s+\w+\s*\{[\s\S]*?\}/g,'');
          src = src.replace(/\btype\s+\w+\s*=[^;]+;/g,'');
          acorn.parse(src,{ecmaVersion:'latest',sourceType:'module'});
        } catch(e) { console.error(e.message); process.exit(1); }
      " 2>&1) || VALID=0
    fi ;;
  vue|svelte)
    # No bundled validator — these are component formats. Defer to project tooling.
    VALIDATOR="none (use project linter)"
    ;;
  python)
    if command -v python3 >/dev/null; then
      VALIDATOR="python3 -m py_compile"
      run python3 -m py_compile "$FILE" || VALID=0
    elif command -v python >/dev/null; then
      VALIDATOR="python -m py_compile"
      run python -m py_compile "$FILE" || VALID=0
    fi ;;
  json)
    VALIDATOR="JSON.parse"
    run node -e "JSON.parse(require('fs').readFileSync('$FILE','utf8'))" || VALID=0
    ;;
  yaml)
    if [ -d "$OCE_HOME/node_modules/js-yaml" ]; then
      VALIDATOR="js-yaml"
      run node -e "require('js-yaml').load(require('fs').readFileSync('$FILE','utf8'))" || VALID=0
    elif command -v python3 >/dev/null; then
      VALIDATOR="python yaml"
      run python3 -c "import sys,yaml; yaml.safe_load(open('$FILE'))" || VALID=0
    fi ;;
  bash|zsh)
    VALIDATOR="bash -n"
    run bash -n "$FILE" || VALID=0
    ;;
  go)
    if command -v gofmt >/dev/null; then
      VALIDATOR="gofmt -e"
      run gofmt -e "$FILE" >/dev/null || VALID=0
    fi ;;
  rust)
    # rustc as a syntax-only check is heavy; prefer rustfmt for parse validation
    if command -v rustfmt >/dev/null; then
      VALIDATOR="rustfmt --check"
      run rustfmt --check "$FILE" || VALID=0
    fi ;;
  php)
    if command -v php >/dev/null; then
      VALIDATOR="php -l"
      run php -l "$FILE" || VALID=0
    fi ;;
  ruby)
    if command -v ruby >/dev/null; then
      VALIDATOR="ruby -c"
      run ruby -c "$FILE" || VALID=0
    fi ;;
  java)
    # No lightweight syntax-only checker; defer to build system
    VALIDATOR="none (use project compiler)"
    ;;
  html)
    if command -v node >/dev/null; then
      # Best-effort: just confirm balanced angle brackets at gross level
      VALIDATOR="basic HTML check"
      run node -e "
        const html = require('fs').readFileSync('$FILE','utf8');
        const opens = (html.match(/</g) || []).length;
        const closes = (html.match(/>/g) || []).length;
        if (opens !== closes) { console.error('Mismatched <> count'); process.exit(1); }
      " || VALID=0
    fi ;;
  *)
    # markdown, xml, css, scss, sql, toml, dockerfile, makefile, unknown:
    # nothing reliable to run with zero deps. Pass through.
    VALIDATOR="none (no validator for $LANG)"
    ;;
esac

if [ "$OCE_JSON_MODE" = "1" ]; then
  emit_json status "$([ $VALID -eq 1 ] && echo success || echo error)" \
    file "$FILE" language "$LANG" validator "$VALIDATOR" \
    valid "raw:$([ $VALID -eq 1 ] && echo true || echo false)" \
    output "$OUTPUT"
else
  if [ $VALID -eq 1 ]; then
    if [ "$VALIDATOR" = "none" ] || [[ "$VALIDATOR" == "none "* ]]; then
      success "$FILE: $VALIDATOR — accepted ($LANG)"
    else
      success "$FILE: valid ($LANG via $VALIDATOR)"
    fi
  else
    [ -n "$OUTPUT" ] && printf '%s\n' "$OUTPUT" >&2
    die "$FILE: validation failed ($LANG via $VALIDATOR)"
  fi
fi

exit $((1 - VALID))


path: scripts/oce-ast.sh
content: #!/usr/bin/env bash
# oce-ast — Structural edits for JS/TS via acorn AST.

OCE_CALLER="${BASH_SOURCE[0]}"
source "$(dirname "$0")/lib/paths.sh"
source "$(dirname "$0")/lib/common.sh"

AST_HELPER="$OCE_HOME/scripts/lib/ast-helper.js"
SUBCMD="${1:-}"
shift || true

case "$SUBCMD" in
  symbols)
    FILE="${1:-}"
    [ -n "$FILE" ] || die "Usage: oce ast symbols <file>"
    require_file "$FILE"
    node "$AST_HELPER" symbols "$FILE"
    ;;

  rename)
    FILE=""; OLD=""; NEW=""; TXN=""
    while [ $# -gt 0 ]; do
      case "$1" in
        --from) OLD="$2"; shift 2 ;;
        --to)   NEW="$2"; shift 2 ;;
        --txn)  TXN="$2"; shift 2 ;;
        --json) OCE_JSON_MODE="1"; shift ;;
        *)      FILE="$1"; shift ;;
      esac
    done
    [ -n "$FILE" ] && [ -n "$OLD" ] && [ -n "$NEW" ] || \
      die "Usage: oce ast rename <file> --from NAME --to NAME"
    require_file "$FILE"

    BACKUP=$(create_backup "$FILE")
    [ -n "$TXN" ] && [ -d "$OCE_TXN_DIR/$TXN" ] && \
      printf '%s\t%s\n' "$FILE" "$BACKUP" >> "$OCE_TXN_DIR/$TXN/files.log"

    RES=$(node "$AST_HELPER" rename "$FILE" "$OLD" "$NEW") || {
      printf '%s\n' "$RES" >&2
      cp "$BACKUP" "$FILE"
      die "AST rename failed — rolled back"
    }
    mv "${FILE}.oce.pending" "$FILE"

    if ! bash "$(dirname "$0")/oce-validate.sh" "$FILE" >/dev/null 2>&1; then
      cp "$BACKUP" "$FILE"
      die "Rename caused validation failure — rolled back"
    fi

    COUNT=$(printf '%s' "$RES" | sed -n 's/.*"count":\([0-9]*\).*/\1/p')
    if [ "$OCE_JSON_MODE" = "1" ]; then
      emit_json status success file "$FILE" replacements "raw:$COUNT" \
        from "$OLD" to "$NEW" backup "$BACKUP"
    else
      success "Renamed '$OLD' → '$NEW' in $FILE ($COUNT identifier reference(s))"
    fi
    ;;

  extract)
    FILE="${1:-}"; NAME="${2:-}"
    [ -n "$FILE" ] && [ -n "$NAME" ] || die "Usage: oce ast extract <file> <symbol>"
    require_file "$FILE"
    node "$AST_HELPER" extract "$FILE" "$NAME"
    ;;

  replace-symbol)
    FILE=""; NAME=""; TXN=""
    while [ $# -gt 0 ]; do
      case "$1" in
        --txn)  TXN="$2"; shift 2 ;;
        --json) OCE_JSON_MODE="1"; shift ;;
        *)      if [ -z "$FILE" ]; then FILE="$1"
                elif [ -z "$NAME" ]; then NAME="$1"
                else die "Unexpected arg: $1"; fi
                shift ;;
      esac
    done
    [ -n "$FILE" ] && [ -n "$NAME" ] || die "Usage: oce ast replace-symbol <file> <symbol> < new_code"
    require_file "$FILE"

    BACKUP=$(create_backup "$FILE")
    [ -n "$TXN" ] && [ -d "$OCE_TXN_DIR/$TXN" ] && \
      printf '%s\t%s\n' "$FILE" "$BACKUP" >> "$OCE_TXN_DIR/$TXN/files.log"

    if ! node "$AST_HELPER" replace-symbol "$FILE" "$NAME"; then
      cp "$BACKUP" "$FILE"
      die "AST replace-symbol failed — rolled back"
    fi
    mv "${FILE}.oce.pending" "$FILE"

    if ! bash "$(dirname "$0")/oce-validate.sh" "$FILE" >/dev/null 2>&1; then
      cp "$BACKUP" "$FILE"
      die "Replacement caused validation failure — rolled back"
    fi

    if [ "$OCE_JSON_MODE" = "1" ]; then
      emit_json status success file "$FILE" symbol "$NAME" backup "$BACKUP"
    else
      success "Replaced symbol '$NAME' in $FILE"
    fi
    ;;

  -h|--help|help|"")
    cat <<'EOF'
oce ast <subcommand>
  symbols <file>                  List functions/classes with line ranges
  rename <file> --from N --to N   Rename every identifier reference
  extract <file> <symbol>         Print the source of a function/class
  replace-symbol <file> <symbol>  Replace a function/class body from stdin

WORKS ON: .js, .mjs, .cjs, .jsx (acorn parses these natively)
LIMITED ON: .ts, .tsx (TypeScript-specific syntax may not parse)
              For TypeScript, prefer text-based edits (replace, patch).
EOF
    ;;

  *)
    die "Unknown subcommand: $SUBCMD"
    ;;
esac


path: README.md
content: # agentic_cli_coding

CLI code-editing toolkit for agentic coders. Provides the `oce` command — a unified, validation-aware, transaction-capable editing interface that backs up every change and rolls back on syntax failure.

## Quick start

```bash
# One-time per session — set up shorthand:
alias oce="bash /path/to/agentic_cli_coding/scripts/oce.sh"

# Verify:
oce doctor

# Use:
oce tree --depth 2
oce find "TODO" --type js
oce read src/server.js --around foo
oce replace src/server.js --old X --new Y
oce diff src/server.js
```

For the full skill description and methodology, see `SKILL.md`. For deeper docs:

- `references/json-schema.md` — output schema for every command
- `references/workflows.md` — worked examples
- `references/language-support.md` — what's validated/formatted per language
- `references/troubleshooting.md` — symptom-to-fix table

## Layout

```
agentic_cli_coding/
├── SKILL.md                    Skill instructions and methodology
├── scripts/
│   ├── oce.sh                  Main dispatcher — entry point
│   ├── oce-*.sh                Subcommand implementations
│   ├── lib/
│   │   ├── paths.sh            Path bootstrap (sourced by every script)
│   │   ├── common.sh           Shared shell helpers
│   │   └── ast-helper.js       JS/TS AST utilities (acorn)
│   └── install.sh              Optional: install an `oce` wrapper on PATH
├── references/                 Deeper docs loaded on demand
├── node_modules/               Bundled (acorn, acorn-walk, js-yaml)
└── package.json                Dependency manifest
```

## Optional install

```bash
bash scripts/install.sh                     # → ~/.local/bin/oce wrapper
bash scripts/install.sh /opt/local/bin      # custom location
```

The skill works without installing — invoke directly via `bash scripts/oce.sh <command>` or via an alias. The install script just creates a one-line wrapper for convenience.


path: node_modules/.package-lock.json
content: {
  "name": "agentic_cli_coding",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {}
}


path: node_modules/acorn/LICENSE.txt
content: MIT License

Copyright (C) 2012-2022 by various contributors (see AUTHORS)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


path: node_modules/acorn/README.md
content: # Acorn

A tiny, fast JavaScript parser written in JavaScript.

## Community

Acorn is open source software released under an
[MIT license](https://github.com/acornjs/acorn/blob/master/acorn/LICENSE).

You are welcome to
[report bugs](https://github.com/acornjs/acorn/issues) or create pull
requests on [github](https://github.com/acornjs/acorn).

## Installation

The easiest way to install acorn is from [`npm`](https://www.npmjs.com/):

```sh
npm install acorn
```

Alternately, you can download the source and build acorn yourself:

```sh
git clone https://github.com/acornjs/acorn.git
cd acorn
npm install
```
## Importing acorn

ESM as well as CommonJS is supported for all 3: `acorn`, `acorn-walk` and `acorn-loose`.

ESM example for `acorn`:

```js
import * as acorn from "acorn"
```

CommonJS example for `acorn`:

```js
let acorn = require("acorn")
```

ESM is preferred, as it allows better editor auto-completions by offering TypeScript support.
For this reason, following examples will use ESM imports.

## Interface

**parse**`(input, options)` is the main interface to the library. The
`input` parameter is a string, `options` must be an object setting
some of the options listed below. The return value will be an abstract
syntax tree object as specified by the [ESTree
spec](https://github.com/estree/estree).

```javascript
import * as acorn from "acorn"
console.log(acorn.parse("1 + 1", {ecmaVersion: 2020}))
```

When encountering a syntax error, the parser will raise a
`SyntaxError` object with a meaningful message. The error object will
have a `pos` property that indicates the string offset at which the
error occurred, and a `loc` object that contains a `{line, column}`
object referring to that same position.

Options are provided by in a second argument, which should be an
object containing any of these fields (only `ecmaVersion` is
required):

- **ecmaVersion**: Indicates the ECMAScript version to parse. Can be a
  number, either in year (`2022`) or plain version number (`6`) form,
  or `"latest"` (the latest the library supports). This influences
  support for strict mode, the set of reserved words, and support for
  new syntax features.

  **NOTE**: Only 'stage 4' (finalized) ECMAScript features are being
  implemented by Acorn. Other proposed new features must be
  implemented through plugins.

- **sourceType**: Indicate the mode the code should be parsed in. Can be
  either `"script"`, `"module"` or `"commonjs"`. This influences global strict mode
  and parsing of `import` and `export` declarations.

  **NOTE**: If set to `"module"`, then static `import` / `export` syntax
  will be valid, even if `ecmaVersion` is less than 6. If set to `"commonjs"`,
  it is the same as `"script"` except that the top-level scope behaves like a function.

- **onInsertedSemicolon**: If given a callback, that callback will be
  called whenever a missing semicolon is inserted by the parser. The
  callback will be given the character offset of the point where the
  semicolon is inserted as argument, and if `locations` is on, also a
  `{line, column}` object representing this position.

- **onTrailingComma**: Like `onInsertedSemicolon`, but for trailing
  commas.

- **allowReserved**: If `false`, using a reserved word will generate
  an error. Defaults to `true` for `ecmaVersion` 3, `false` for higher
  versions. When given the value `"never"`, reserved words and
  keywords can also not be used as property names (as in Internet
  Explorer's old parser).

- **allowReturnOutsideFunction**: By default, a return statement at
  the top level raises an error. Set this to `true` to accept such
  code.

- **allowImportExportEverywhere**: By default, `import` and `export`
  declarations can only appear at a program's top level. Setting this
  option to `true` allows them anywhere where a statement is allowed,
  and also allows `import.meta` expressions to appear in scripts
  (when `sourceType` is not `"module"`).

- **allowAwaitOutsideFunction**: If `false`, `await` expressions can
  only appear inside `async` functions. Defaults to `true` in modules
  for `ecmaVersion` 2022 and later, `false` for lower versions.
  Setting this option to `true` allows to have top-level `await`
  expressions. They are still not allowed in non-`async` functions,
  though. Setting this option to `true` is not allowed when `sourceType: "commonjs"`.

- **allowSuperOutsideMethod**: By default, `super` outside a method
  raises an error. Set this to `true` to accept such code.

- **allowHashBang**: When this is enabled, if the code starts with the
  characters `#!` (as in a shellscript), the first line will be
  treated as a comment. Defaults to true when `ecmaVersion` >= 2023.

- **checkPrivateFields**: By default, the parser will verify that
  private properties are only used in places where they are valid and
  have been declared. Set this to false to turn such checks off.

- **locations**: When `true`, each node has a `loc` object attached
  with `start` and `end` subobjects, each of which contains the
  one-based line and zero-based column numbers in `{line, column}`
  form. Default is `false`.

- **onToken**: If a function is passed for this option, each found
  token will be passed in same format as tokens returned from
  `tokenizer().getToken()`.

  If array is passed, each found token is pushed to it.

  Note that you are not allowed to call the parser from the
  callback—that will corrupt its internal state.

- **onComment**: If a function is passed for this option, whenever a
  comment is encountered the function will be called with the
  following parameters:

  - `block`: `true` if the comment is a block comment, false if it
    is a line comment.
  - `text`: The content of the comment.
  - `start`: Character offset of the start of the comment.
  - `end`: Character offset of the end of the comment.

  When the `locations` options is on, the `{line, column}` locations
  of the comment’s start and end are passed as two additional
  parameters.

  If array is passed for this option, each found comment is pushed
  to it as object in Esprima format:

  ```javascript
  {
    "type": "Line" | "Block",
    "value": "comment text",
    "start": Number,
    "end": Number,
    // If `locations` option is on:
    "loc": {
      "start": {line: Number, column: Number}
      "end": {line: Number, column: Number}
    },
    // If `ranges` option is on:
    "range": [Number, Number]
  }
  ```

  Note that you are not allowed to call the parser from the
  callback—that will corrupt its internal state.

- **ranges**: Nodes have their start and end characters offsets
  recorded in `start` and `end` properties (directly on the node,
  rather than the `loc` object, which holds line/column data. To also
  add a
  [semi-standardized](https://bugzilla.mozilla.org/show_bug.cgi?id=745678)
  `range` property holding a `[start, end]` array with the same
  numbers, set the `ranges` option to `true`.

- **program**: It is possible to parse multiple files into a single
  AST by passing the tree produced by parsing the first file as the
  `program` option in subsequent parses. This will add the toplevel
  forms of the parsed file to the "Program" (top) node of an existing
  parse tree.

- **sourceFile**: When the `locations` option is `true`, you can pass
  this option to add a `source` attribute in every node’s `loc`
  object. Note that the contents of this option are not examined or
  processed in any way; you are free to use whatever format you
  choose.

- **directSourceFile**: Like `sourceFile`, but a `sourceFile` property
  will be added (regardless of the `location` option) directly to the
  nodes, rather than the `loc` object.

- **preserveParens**: If this option is `true`, parenthesized expressions
  are represented by (non-standard) `ParenthesizedExpression` nodes
  that have a single `expression` property containing the expression
  inside parentheses.

**parseExpressionAt**`(input, offset, options)` will parse a single
expression in a string, and return its AST. It will not complain if
there is more of the string left after the expression.

**tokenizer**`(input, options)` returns an object with a `getToken`
method that can be called repeatedly to get the next token, a `{start,
end, type, value}` object (with added `loc` property when the
`locations` option is enabled and `range` property when the `ranges`
option is enabled). When the token's type is `tokTypes.eof`, you
should stop calling the method, since it will keep returning that same
token forever.

Note that tokenizing JavaScript without parsing it is, in modern
versions of the language, not really possible due to the way syntax is
overloaded in ways that can only be disambiguated by the parse
context. This package applies a bunch of heuristics to try and do a
reasonable job, but you are advised to use `parse` with the `onToken`
option instead of this.

In ES6 environment, returned result can be used as any other
protocol-compliant iterable:

```javascript
for (let token of acorn.tokenizer(str)) {
  // iterate over the tokens
}

// transform code to array of tokens:
var tokens = [...acorn.tokenizer(str)]
```

**tokTypes** holds an object mapping names to the token type objects
that end up in the `type` properties of tokens.

**getLineInfo**`(input, offset)` can be used to get a `{line,
column}` object for a given program string and offset.

### The `Parser` class

Instances of the **`Parser`** class contain all the state and logic
that drives a parse. It has static methods `parse`,
`parseExpressionAt`, and `tokenizer` that match the top-level
functions by the same name.

When extending the parser with plugins, you need to call these methods
on the extended version of the class. To extend a parser with plugins,
you can use its static `extend` method.

```javascript
var acorn = require("acorn")
var jsx = require("acorn-jsx")
var JSXParser = acorn.Parser.extend(jsx())
JSXParser.parse("foo(<bar/>)", {ecmaVersion: 2020})
```

The `extend` method takes any number of plugin values, and returns a
new `Parser` class that includes the extra parser logic provided by
the plugins.

## Command line interface

The `bin/acorn` utility can be used to parse a file from the command
line. It accepts as arguments its input file and the following
options:

- `--ecma3|--ecma5|--ecma6|--ecma7|--ecma8|--ecma9|--ecma10`: Sets the ECMAScript version
  to parse. Default is version 9.

- `--module`: Sets the parsing mode to `"module"`. Is set to `"script"` otherwise.

- `--locations`: Attaches a "loc" object to each node with "start" and
  "end" subobjects, each of which contains the one-based line and
  zero-based column numbers in `{line, column}` form.

- `--allow-hash-bang`: If the code starts with the characters #! (as
  in a shellscript), the first line will be treated as a comment.

- `--allow-await-outside-function`: Allows top-level `await` expressions.
  See the `allowAwaitOutsideFunction` option for more information.

- `--compact`: No whitespace is used in the AST output.

- `--silent`: Do not output the AST, just return the exit status.

- `--help`: Print the usage information and quit.

The utility spits out the syntax tree as JSON data.

## Existing plugins

 - [`acorn-jsx`](https://github.com/RReverser/acorn-jsx): Parse [Facebook JSX syntax extensions](https://github.com/facebook/jsx)


path: node_modules/acorn/package.json
content: {
  "name": "acorn",
  "description": "ECMAScript parser",
  "homepage": "https://github.com/acornjs/acorn",
  "main": "dist/acorn.js",
  "types": "dist/acorn.d.ts",
  "module": "dist/acorn.mjs",
  "exports": {
    ".": [
      {
        "import": "./dist/acorn.mjs",
        "require": "./dist/acorn.js",
        "default": "./dist/acorn.js"
      },
      "./dist/acorn.js"
    ],
    "./package.json": "./package.json"
  },
  "version": "8.16.0",
  "engines": {
    "node": ">=0.4.0"
  },
  "maintainers": [
    {
      "name": "Marijn Haverbeke",
      "email": "[REDACTED_SECRET]",
      "web": "https://marijnhaverbeke.nl"
    },
    {
      "name": "Ingvar Stepanyan",
      "email": "[REDACTED_SECRET]",
      "web": "https://rreverser.com/"
    },
    {
      "name": "Adrian Heine",
      "web": "http://adrianheine.de"
    }
  ],
  "repository": {
    "type": "git",
    "url": "git+https://github.com/acornjs/acorn.git"
  },
  "license": "MIT",
  "scripts": {
    "prepare": "cd ..; npm run build:main"
  },
  "bin": {
    "acorn": "bin/acorn"
  }
}


path: node_modules/js-yaml/LICENSE.txt
content: (The MIT License)

Copyright (C) 2011-2015 by Vitaly Puzrin

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


path: node_modules/js-yaml/index.js
content: 'use strict';


var loader = require('./lib/loader');
var dumper = require('./lib/dumper');


function renamed(from, to) {
  return function () {
    throw new Error('Function yaml.' + from + ' is removed in js-yaml 4. ' +
      'Use yaml.' + to + ' instead, which is now safe by default.');
  };
}


module.exports.Type                = require('./lib/type');
module.exports.Schema              = require('./lib/schema');
module.exports.FAILSAFE_SCHEMA     = require('./lib/schema/failsafe');
module.exports.JSON_SCHEMA         = require('./lib/schema/json');
module.exports.CORE_SCHEMA         = require('./lib/schema/core');
module.exports.DEFAULT_SCHEMA      = require('./lib/schema/default');
module.exports.load                = loader.load;
module.exports.loadAll             = loader.loadAll;
module.exports.dump                = dumper.dump;
module.exports.YAMLException       = require('./lib/exception');

// Re-export all types in case user wants to create custom schema
module.exports.types = {
  binary:    require('./lib/type/binary'),
  float:     require('./lib/type/float'),
  map:       require('./lib/type/map'),
  null:      require('./lib/type/null'),
  pairs:     require('./lib/type/pairs'),
  set:       require('./lib/type/set'),
  timestamp: require('./lib/type/timestamp'),
  bool:      require('./lib/type/bool'),
  int:       require('./lib/type/int'),
  merge:     require('./lib/type/merge'),
  omap:      require('./lib/type/omap'),
  seq:       require('./lib/type/seq'),
  str:       require('./lib/type/str')
};

// Removed functions from JS-YAML 3.0.x
module.exports.safeLoad            = renamed('safeLoad', 'load');
module.exports.safeLoadAll         = renamed('safeLoadAll', 'loadAll');
module.exports.safeDump            = renamed('safeDump', 'dump');


path: node_modules/js-yaml/lib/loader.js
content: 'use strict';

/*eslint-disable max-len,no-use-before-define*/

var common              = require('./common');
var YAMLException       = require('./exception');
var makeSnippet         = require('./snippet');
var DEFAULT_SCHEMA      = require('./schema/default');


var _hasOwnProperty = Object.prototype.hasOwnProperty;


var CONTEXT_FLOW_IN   = 1;
var CONTEXT_FLOW_OUT  = 2;
var CONTEXT_BLOCK_IN  = 3;
var CONTEXT_BLOCK_OUT = 4;


var CHOMPING_CLIP  = 1;
var CHOMPING_STRIP = 2;
var CHOMPING_KEEP  = 3;


var PATTERN_NON_PRINTABLE         = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/;
var PATTERN_NON_ASCII_LINE_BREAKS = /[\x85\u2028\u2029]/;
var PATTERN_FLOW_INDICATORS       = /[,\[\]\{\}]/;
var PATTERN_TAG_HANDLE            = /^(?:!|!!|![a-z\-]+!)$/i;
var PATTERN_TAG_URI               = /^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i;


function _class(obj) { return Object.prototype.toString.call(obj); }

function is_EOL(c) {
  return (c === 0x0A/* LF */) || (c === 0x0D/* CR */);
}

function is_WHITE_SPACE(c) {
  return (c === 0x09/* Tab */) || (c === 0x20/* Space */);
}

function is_WS_OR_EOL(c) {
  return (c === 0x09/* Tab */) ||
         (c === 0x20/* Space */) ||
         (c === 0x0A/* LF */) ||
         (c === 0x0D/* CR */);
}

function is_FLOW_INDICATOR(c) {
  return c === 0x2C/* , */ ||
         c === 0x5B/* [ */ ||
         c === 0x5D/* ] */ ||
         c === 0x7B/* { */ ||
         c === 0x7D/* } */;
}

function fromHexCode(c) {
  var lc;

  if ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) {
    return c - 0x30;
  }

  /*eslint-disable no-bitwise*/
  lc = c | 0x20;

  if ((0x61/* a */ <= lc) && (lc <= 0x66/* f */)) {
    return lc - 0x61 + 10;
  }

  return -1;
}

function escapedHexLen(c) {
  if (c === 0x78/* x */) { return 2; }
  if (c === 0x75/* u */) { return 4; }
  if (c === 0x55/* U */) { return 8; }
  return 0;
}

function fromDecimalCode(c) {
  if ((0x30/* 0 */ <= c) && (c <= 0x39/* 9 */)) {
    return c - 0x30;
  }

  return -1;
}

function simpleEscapeSequence(c) {
  /* eslint-disable indent */
  return (c === 0x30/* 0 */) ? '\x00' :
        (c === 0x61/* a */) ? '\x07' :
        (c === 0x62/* b */) ? '\x08' :
        (c === 0x74/* t */) ? '\x09' :
        (c === 0x09/* Tab */) ? '\x09' :
        (c === 0x6E/* n */) ? '\x0A' :
        (c === 0x76/* v */) ? '\x0B' :
        (c === 0x66/* f */) ? '\x0C' :
        (c === 0x72/* r */) ? '\x0D' :
        (c === 0x65/* e */) ? '\x1B' :
        (c === 0x20/* Space */) ? ' ' :
        (c === 0x22/* " */) ? '\x22' :
        (c === 0x2F/* / */) ? '/' :
        (c === 0x5C/* \ */) ? '\x5C' :
        (c === 0x4E/* N */) ? '\x85' :
        (c === 0x5F/* _ */) ? '\xA0' :
        (c === 0x4C/* L */) ? '\u2028' :
        (c === 0x50/* P */) ? '\u2029' : '';
}

function charFromCodepoint(c) {
  if (c <= 0xFFFF) {
    return String.fromCharCode(c);
  }
  // Encode UTF-16 surrogate pair
  // https://en.wikipedia.org/wiki/UTF-16#Code_points_U.2B010000_to_U.2B10FFFF
  return String.fromCharCode(
    ((c - 0x010000) >> 10) + 0xD800,
    ((c - 0x010000) & 0x03FF) + 0xDC00
  );
}

// set a property of a literal object, while protecting against prototype pollution,
// see https://github.com/nodeca/js-yaml/issues/164 for more details
function setProperty(object, key, value) {
  // used for this specific key only because Object.defineProperty is slow
  if (key === '__proto__') {
    Object.defineProperty(object, key, {
      configurable: true,
      enumerable: true,
      writable: true,
      value: value
    });
  } else {
    object[key] = value;
  }
}

var simpleEscapeCheck = new Array(256); // integer, for fast access
var simpleEscapeMap = new Array(256);
for (var i = 0; i < 256; i++) {
  simpleEscapeCheck[i] = simpleEscapeSequence(i) ? 1 : 0;
  simpleEscapeMap[i] = simpleEscapeSequence(i);
}


function State(input, options) {
  this.input = input;

  this.filename  = options['filename']  || null;
  this.schema    = options['schema']    || DEFAULT_SCHEMA;
  this.onWarning = options['onWarning'] || null;
  // (Hidden) Remove? makes the loader to expect YAML 1.1 documents
  // if such documents have no explicit %YAML directive
  this.legacy    = options['legacy']    || false;

  this.json      = options['json']      || false;
  this.listener  = options['listener']  || null;

  this.implicitTypes = this.schema.compiledImplicit;
  this.typeMap       = this.schema.compiledTypeMap;

  this.length     = input.length;
  this.position   = 0;
  this.line       = 0;
  this.lineStart  = 0;
  this.lineIndent = 0;

  // position of first leading tab in the current line,
  // used to make sure there are no tabs in the indentation
  this.firstTabInLine = -1;

  this.documents = [];

  /*
  this.version;
  this.checkLineBreaks;
  this.tagMap;
  this.anchorMap;
  this.tag;
  this.anchor;
  this.kind;
  this.result;*/

}


function generateError(state, message) {
  var mark = {
    name:     state.filename,
    buffer:   state.input.slice(0, -1), // omit trailing \0
    position: state.position,
    line:     state.line,
    column:   state.position - state.lineStart
  };

  mark.snippet = makeSnippet(mark);

  return new YAMLException(message, mark);
}

function throwError(state, message) {
  throw generateError(state, message);
}

function throwWarning(state, message) {
  if (state.onWarning) {
    state.onWarning.call(null, generateError(state, message));
  }
}


var directiveHandlers = {

  YAML: function handleYamlDirective(state, name, args) {

    var match, major, minor;

    if (state.version !== null) {
      throwError(state, 'duplication of %YAML directive');
    }

    if (args.length !== 1) {
      throwError(state, 'YAML directive accepts exactly one argument');
    }

    match = /^([0-9]+)\.([0-9]+)$/.exec(args[0]);

    if (match === null) {
      throwError(state, 'ill-formed argument of the YAML directive');
    }

    major = parseInt(match[1], 10);
    minor = parseInt(match[2], 10);

    if (major !== 1) {
      throwError(state, 'unacceptable YAML version of the document');
    }

    state.version = args[0];
    state.checkLineBreaks = (minor < 2);

    if (minor !== 1 && minor !== 2) {
      throwWarning(state, 'unsupported YAML version of the document');
    }
  },

  TAG: function handleTagDirective(state, name, args) {

    var handle, prefix;

    if (args.length !== 2) {
      throwError(state, 'TAG directive accepts exactly two arguments');
    }

    handle = args[0];
    prefix = args[1];

    if (!PATTERN_TAG_HANDLE.test(handle)) {
      throwError(state, 'ill-formed tag handle (first argument) of the TAG directive');
    }

    if (_hasOwnProperty.call(state.tagMap, handle)) {
      throwError(state, 'there is a previously declared suffix for "' + handle + '" tag handle');
    }

    if (!PATTERN_TAG_URI.test(prefix)) {
      throwError(state, 'ill-formed tag prefix (second argument) of the TAG directive');
    }

    try {
      prefix = decodeURIComponent(prefix);
    } catch (err) {
      throwError(state, 'tag prefix is malformed: ' + prefix);
    }

    state.tagMap[handle] = prefix;
  }
};


function captureSegment(state, start, end, checkJson) {
  var _position, _length, _character, _result;

  if (start < end) {
    _result = state.input.slice(start, end);

    if (checkJson) {
      for (_position = 0, _length = _result.length; _position < _length; _position += 1) {
        _character = _result.charCodeAt(_position);
        if (!(_character === 0x09 ||
              (0x20 <= _character && _character <= 0x10FFFF))) {
          throwError(state, 'expected valid JSON character');
        }
      }
    } else if (PATTERN_NON_PRINTABLE.test(_result)) {
      throwError(state, 'the stream contains non-printable characters');
    }

    state.result += _result;
  }
}

function mergeMappings(state, destination, source, overridableKeys) {
  var sourceKeys, key, index, quantity;

  if (!common.isObject(source)) {
    throwError(state, 'cannot merge mappings; the provided source object is unacceptable');
  }

  sourceKeys = Object.keys(source);

  for (index = 0, quantity = sourceKeys.length; index < quantity; index += 1) {
    key = sourceKeys[index];

    if (!_hasOwnProperty.call(destination, key)) {
      setProperty(destination, key, source[key]);
      overridableKeys[key] = true;
    }
  }
}

function storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode,
  startLine, startLineStart, startPos) {

  var index, quantity;

  // The output is a plain object here, so keys can only be strings.
  // We need to convert keyNode to a string, but doing so can hang the process
  // (deeply nested arrays that explode exponentially using aliases).
  if (Array.isArray(keyNode)) {
    keyNode = Array.prototype.slice.call(keyNode);

    for (index = 0, quantity = keyNode.length; index < quantity; index += 1) {
      if (Array.isArray(keyNode[index])) {
        throwError(state, 'nested arrays are not supported inside keys');
      }

      if (typeof keyNode === 'object' && _class(keyNode[index]) === '[object Object]') {
        keyNode[index] = '[object Object]';
      }
    }
  }

  // Avoid code execution in load() via toString property
  // (still use its own toString for arrays, timestamps,
  // and whatever user schema extensions happen to have @@toStringTag)
  if (typeof keyNode === 'object' && _class(keyNode) === '[object Object]') {
    keyNode = '[object Object]';
  }


  keyNode = String(keyNode);

  if (_result === null) {
    _result = {};
  }

  if (keyTag === 'tag:yaml.org,2002:merge') {
    if (Array.isArray(valueNode)) {
      for (index = 0, quantity = valueNode.length; index < quantity; index += 1) {
        mergeMappings(state, _result, valueNode[index], overridableKeys);
      }
    } else {
      mergeMappings(state, _result, valueNode, overridableKeys);
    }
  } else {
    if (!state.json &&
        !_hasOwnProperty.call(overridableKeys, keyNode) &&
        _hasOwnProperty.call(_result, keyNode)) {
      state.line = startLine || state.line;
      state.lineStart = startLineStart || state.lineStart;
      state.position = startPos || state.position;
      throwError(state, 'duplicated mapping key');
    }

    setProperty(_result, keyNode, valueNode);
    delete overridableKeys[keyNode];
  }

  return _result;
}

function readLineBreak(state) {
  var ch;

  ch = state.input.charCodeAt(state.position);

  if (ch === 0x0A/* LF */) {
    state.position++;
  } else if (ch === 0x0D/* CR */) {
    state.position++;
    if (state.input.charCodeAt(state.position) === 0x0A/* LF */) {
      state.position++;
    }
  } else {
    throwError(state, 'a line break is expected');
  }

  state.line += 1;
  state.lineStart = state.position;
  state.firstTabInLine = -1;
}

function skipSeparationSpace(state, allowComments, checkIndent) {
  var lineBreaks = 0,
      ch = state.input.charCodeAt(state.position);

  while (ch !== 0) {
    while (is_WHITE_SPACE(ch)) {
      if (ch === 0x09/* Tab */ && state.firstTabInLine === -1) {
        state.firstTabInLine = state.position;
      }
      ch = state.input.charCodeAt(++state.position);
    }

    if (allowComments && ch === 0x23/* # */) {
      do {
        ch = state.input.charCodeAt(++state.position);
      } while (ch !== 0x0A/* LF */ && ch !== 0x0D/* CR */ && ch !== 0);
    }

    if (is_EOL(ch)) {
      readLineBreak(state);

      ch = state.input.charCodeAt(state.position);
      lineBreaks++;
      state.lineIndent = 0;

      while (ch === 0x20/* Space */) {
        state.lineIndent++;
        ch = state.input.charCodeAt(++state.position);
      }
    } else {
      break;
    }
  }

  if (checkIndent !== -1 && lineBreaks !== 0 && state.lineIndent < checkIndent) {
    throwWarning(state, 'deficient indentation');
  }

  return lineBreaks;
}

function testDocumentSeparator(state) {
  var _position = state.position,
      ch;

  ch = state.input.charCodeAt(_position);

  // Condition state.position === state.lineStart is tested
  // in parent on each call, for efficiency. No needs to test here again.
  if ((ch === 0x2D/* - */ || ch === 0x2E/* . */) &&
      ch === state.input.charCodeAt(_position + 1) &&
      ch === state.input.charCodeAt(_position + 2)) {

    _position += 3;

    ch = state.input.charCodeAt(_position);

    if (ch === 0 || is_WS_OR_EOL(ch)) {
      return true;
    }
  }

  return false;
}

function writeFoldedLines(state, count) {
  if (count === 1) {
    state.result += ' ';
  } else if (count > 1) {
    state.result += common.repeat('\n', count - 1);
  }
}


function readPlainScalar(state, nodeIndent, withinFlowCollection) {
  var preceding,
      following,
      captureStart,
      captureEnd,
      hasPendingContent,
      _line,
      _lineStart,
      _lineIndent,
      _kind = state.kind,
      _result = state.result,
      ch;

  ch = state.input.charCodeAt(state.position);

  if (is_WS_OR_EOL(ch)      ||
      is_FLOW_INDICATOR(ch) ||
      ch === 0x23/* # */    ||
      ch === 0x26/* & */    ||
      ch === 0x2A/* * */    ||
      ch === 0x21/* ! */    ||
      ch === 0x7C/* | */    ||
      ch === 0x3E/* > */    ||
      ch === 0x27/* ' */    ||
      ch === 0x22/* " */    ||
      ch === 0x25/* % */    ||
      ch === 0x40/* @ */    ||
      ch === 0x60/* ` */) {
    return false;
  }

  if (ch === 0x3F/* ? */ || ch === 0x2D/* - */) {
    following = state.input.charCodeAt(state.position + 1);

    if (is_WS_OR_EOL(following) ||
        withinFlowCollection && is_FLOW_INDICATOR(following)) {
      return false;
    }
  }

  state.kind = 'scalar';
  state.result = '';
  captureStart = captureEnd = state.position;
  hasPendingContent = false;

  while (ch !== 0) {
    if (ch === 0x3A/* : */) {
      following = state.input.charCodeAt(state.position + 1);

      if (is_WS_OR_EOL(following) ||
          withinFlowCollection && is_FLOW_INDICATOR(following)) {
        break;
      }

    } else if (ch === 0x23/* # */) {
      preceding = state.input.charCodeAt(state.position - 1);

      if (is_WS_OR_EOL(preceding)) {
        break;
      }

    } else if ((state.position === state.lineStart && testDocumentSeparator(state)) ||
               withinFlowCollection && is_FLOW_INDICATOR(ch)) {
      break;

    } else if (is_EOL(ch)) {
      _line = state.line;
      _lineStart = state.lineStart;
      _lineIndent = state.lineIndent;
      skipSeparationSpace(state, false, -1);

      if (state.lineIndent >= nodeIndent) {
        hasPendingContent = true;
        ch = state.input.charCodeAt(state.position);
        continue;
      } else {
        state.position = captureEnd;
        state.line = _line;
        state.lineStart = _lineStart;
        state.lineIndent = _lineIndent;
        break;
      }
    }

    if (hasPendingContent) {
      captureSegment(state, captureStart, captureEnd, false);
      writeFoldedLines(state, state.line - _line);
      captureStart = captureEnd = state.position;
      hasPendingContent = false;
    }

    if (!is_WHITE_SPACE(ch)) {
      captureEnd = state.position + 1;
    }

    ch = state.input.charCodeAt(++state.position);
  }

  captureSegment(state, captureStart, captureEnd, false);

  if (state.result) {
    return true;
  }

  state.kind = _kind;
  state.result = _result;
  return false;
}

function readSingleQuotedScalar(state, nodeIndent) {
  var ch,
      captureStart, captureEnd;

  ch = state.input.charCodeAt(state.position);

  if (ch !== 0x27/* ' */) {
    return false;
  }

  state.kind = 'scalar';
  state.result = '';
  state.position++;
  captureStart = captureEnd = state.position;

  while ((ch = state.input.charCodeAt(state.position)) !== 0) {
    if (ch === 0x27/* ' */) {
      captureSegment(state, captureStart, state.position, true);
      ch = state.input.charCodeAt(++state.position);

      if (ch === 0x27/* ' */) {
        captureStart = state.position;
        state.position++;
        captureEnd = state.position;
      } else {
        return true;
      }

    } else if (is_EOL(ch)) {
      captureSegment(state, captureStart, captureEnd, true);
      writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent));
      captureStart = captureEnd = state.position;

    } else if (state.position === state.lineStart && testDocumentSeparator(state)) {
      throwError(state, 'unexpected end of the document within a single quoted scalar');

    } else {
      state.position++;
      captureEnd = state.position;
    }
  }

  throwError(state, 'unexpected end of the stream within a single quoted scalar');
}

function readDoubleQuotedScalar(state, nodeIndent) {
  var captureStart,
      captureEnd,
      hexLength,
      hexResult,
      tmp,
      ch;

  ch = state.input.charCodeAt(state.position);

  if (ch !== 0x22/* " */) {
    return false;
  }

  state.kind = 'scalar';
  state.result = '';
  state.position++;
  captureStart = captureEnd = state.position;

  while ((ch = state.input.charCodeAt(state.position)) !== 0) {
    if (ch === 0x22/* " */) {
      captureSegment(state, captureStart, state.position, true);
      state.position++;
      return true;

    } else if (ch === 0x5C/* \ */) {
      captureSegment(state, captureStart, state.position, true);
      ch = state.input.charCodeAt(++state.position);

      if (is_EOL(ch)) {
        skipSeparationSpace(state, false, nodeIndent);

        // TODO: rework to inline fn with no type cast?
      } else if (ch < 256 && simpleEscapeCheck[ch]) {
        state.result += simpleEscapeMap[ch];
        state.position++;

      } else if ((tmp = escapedHexLen(ch)) > 0) {
        hexLength = tmp;
        hexResult = 0;

        for (; hexLength > 0; hexLength--) {
          ch = state.input.charCodeAt(++state.position);

          if ((tmp = fromHexCode(ch)) >= 0) {
            hexResult = (hexResult << 4) + tmp;

          } else {
            throwError(state, 'expected hexadecimal character');
          }
        }

        state.result += charFromCodepoint(hexResult);

        state.position++;

      } else {
        throwError(state, 'unknown escape sequence');
      }

      captureStart = captureEnd = state.position;

    } else if (is_EOL(ch)) {
      captureSegment(state, captureStart, captureEnd, true);
      writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent));
      captureStart = captureEnd = state.position;

    } else if (state.position === state.lineStart && testDocumentSeparator(state)) {
      throwError(state, 'unexpected end of the document within a double quoted scalar');

    } else {
      state.position++;
      captureEnd = state.position;
    }
  }

  throwError(state, 'unexpected end of the stream within a double quoted scalar');
}

function readFlowCollection(state, nodeIndent) {
  var readNext = true,
      _line,
      _lineStart,
      _pos,
      _tag     = state.tag,
      _result,
      _anchor  = state.anchor,
      following,
      terminator,
      isPair,
      isExplicitPair,
      isMapping,
      overridableKeys = Object.create(null),
      keyNode,
      keyTag,
      valueNode,
      ch;

  ch = state.input.charCodeAt(state.position);

  if (ch === 0x5B/* [ */) {
    terminator = 0x5D;/* ] */
    isMapping = false;
    _result = [];
  } else if (ch === 0x7B/* { */) {
    terminator = 0x7D;/* } */
    isMapping = true;
    _result = {};
  } else {
    return false;
  }

  if (state.anchor !== null) {
    state.anchorMap[state.anchor] = _result;
  }

  ch = state.input.charCodeAt(++state.position);

  while (ch !== 0) {
    skipSeparationSpace(state, true, nodeIndent);

    ch = state.input.charCodeAt(state.position);

    if (ch === terminator) {
      state.position++;
      state.tag = _tag;
      state.anchor = _anchor;
      state.kind = isMapping ? 'mapping' : 'sequence';
      state.result = _result;
      return true;
    } else if (!readNext) {
      throwError(state, 'missed comma between flow collection entries');
    } else if (ch === 0x2C/* , */) {
      // "flow collection entries can never be completely empty", as per YAML 1.2, section 7.4
      throwError(state, "expected the node content, but found ','");
    }

    keyTag = keyNode = valueNode = null;
    isPair = isExplicitPair = false;

    if (ch === 0x3F/* ? */) {
      following = state.input.charCodeAt(state.position + 1);

      if (is_WS_OR_EOL(following)) {
        isPair = isExplicitPair = true;
        state.position++;
        skipSeparationSpace(state, true, nodeIndent);
      }
    }

    _line = state.line; // Save the current line.
    _lineStart = state.lineStart;
    _pos = state.position;
    composeNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true);
    keyTag = state.tag;
    keyNode = state.result;
    skipSeparationSpace(state, true, nodeIndent);

    ch = state.input.charCodeAt(state.position);

    if ((isExplicitPair || state.line === _line) && ch === 0x3A/* : */) {
      isPair = true;
      ch = state.input.charCodeAt(++state.position);
      skipSeparationSpace(state, true, nodeIndent);
      composeNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true);
      valueNode = state.result;
    }

    if (isMapping) {
      storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, _line, _lineStart, _pos);
    } else if (isPair) {
      _result.push(storeMappingPair(state, null, overridableKeys, keyTag, keyNode, valueNode, _line, _lineStart, _pos));
    } else {
      _result.push(keyNode);
    }

    skipSeparationSpace(state, true, nodeIndent);

    ch = state.input.charCodeAt(state.position);

    if (ch === 0x2C/* , */) {
      readNext = true;
      ch = state.input.charCodeAt(++state.position);
    } else {
      readNext = false;
    }
  }

  throwError(state, 'unexpected end of the stream within a flow collection');
}

function readBlockScalar(state, nodeIndent) {
  var captureStart,
      folding,
      chomping       = CHOMPING_CLIP,
      didReadContent = false,
      detectedIndent = false,
      textIndent     = nodeIndent,
      emptyLines     = 0,
      atMoreIndented = false,
      tmp,
      ch;

  ch = state.input.charCodeAt(state.position);

  if (ch === 0x7C/* | */) {
    folding = false;
  } else if (ch === 0x3E/* > */) {
    folding = true;
  } else {
    return false;
  }

  state.kind = 'scalar';
  state.result = '';

  while (ch !== 0) {
    ch = state.input.charCodeAt(++state.position);

    if (ch === 0x2B/* + */ || ch === 0x2D/* - */) {
      if (CHOMPING_CLIP === chomping) {
        chomping = (ch === 0x2B/* + */) ? CHOMPING_KEEP : CHOMPING_STRIP;
      } else {
        throwError(state, 'repeat of a chomping mode identifier');
      }

    } else if ((tmp = fromDecimalCode(ch)) >= 0) {
      if (tmp === 0) {
        throwError(state, 'bad explicit indentation width of a block scalar; it cannot be less than one');
      } else if (!detectedIndent) {
        textIndent = nodeIndent + tmp - 1;
        detectedIndent = true;
      } else {
        throwError(state, 'repeat of an indentation width identifier');
      }

    } else {
      break;
    }
  }

  if (is_WHITE_SPACE(ch)) {
    do { ch = state.input.charCodeAt(++state.position); }
    while (is_WHITE_SPACE(ch));

    if (ch === 0x23/* # */) {
      do { ch = state.input.charCodeAt(++state.position); }
      while (!is_EOL(ch) && (ch !== 0));
    }
  }

  while (ch !== 0) {
    readLineBreak(state);
    state.lineIndent = 0;

    ch = state.input.charCodeAt(state.position);

    while ((!detectedIndent || state.lineIndent < textIndent) &&
           (ch === 0x20/* Space */)) {
      state.lineIndent++;
      ch = state.input.charCodeAt(++state.position);
    }

    if (!detectedIndent && state.lineIndent > textIndent) {
      textIndent = state.lineIndent;
    }

    if (is_EOL(ch)) {
      emptyLines++;
      continue;
    }

    // End of the scalar.
    if (state.lineIndent < textIndent) {

      // Perform the chomping.
      if (chomping === CHOMPING_KEEP) {
        state.result += common.repeat('\n', didReadContent ? 1 + emptyLines : emptyLines);
      } else if (chomping === CHOMPING_CLIP) {
        if (didReadContent) { // i.e. only if the scalar is not empty.
          state.result += '\n';
        }
      }

      // Break this `while` cycle and go to the funciton's epilogue.
      break;
    }

    // Folded style: use fancy rules to handle line breaks.
    if (folding) {

      // Lines starting with white space characters (more-indented lines) are not folded.
      if (is_WHITE_SPACE(ch)) {
        atMoreIndented = true;
        // except for the first content line (cf. Example 8.1)
        state.result += common.repeat('\n', didReadContent ? 1 + emptyLines : emptyLines);

      // End of more-indented block.
      } else if (atMoreIndented) {
        atMoreIndented = false;
        state.result += common.repeat('\n', emptyLines + 1);

      // Just one line break - perceive as the same line.
      } else if (emptyLines === 0) {
        if (didReadContent) { // i.e. only if we have already read some scalar content.
          state.result += ' ';
        }

      // Several line breaks - perceive as different lines.
      } else {
        state.result += common.repeat('\n', emptyLines);
      }

    // Literal style: just add exact number of line breaks between content lines.
    } else {
      // Keep all line breaks except the header line break.
      state.result += common.repeat('\n', didReadContent ? 1 + emptyLines : emptyLines);
    }

    didReadContent = true;
    detectedIndent = true;
    emptyLines = 0;
    captureStart = state.position;

    while (!is_EOL(ch) && (ch !== 0)) {
      ch = state.input.charCodeAt(++state.position);
    }

    captureSegment(state, captureStart, state.position, false);
  }

  return true;
}

function readBlockSequence(state, nodeIndent) {
  var _line,
      _tag      = state.tag,
      _anchor   = state.anchor,
      _result   = [],
      following,
      detected  = false,
      ch;

  // there is a leading tab before this token, so it can't be a block sequence/mapping;
  // it can still be flow sequence/mapping or a scalar
  if (state.firstTabInLine !== -1) return false;

  if (state.anchor !== null) {
    state.anchorMap[state.anchor] = _result;
  }

  ch = state.input.charCodeAt(state.position);

  while (ch !== 0) {
    if (state.firstTabInLine !== -1) {
      state.position = state.firstTabInLine;
      throwError(state, 'tab characters must not be used in indentation');
    }

    if (ch !== 0x2D/* - */) {
      break;
    }

    following = state.input.charCodeAt(state.position + 1);

    if (!is_WS_OR_EOL(following)) {
      break;
    }

    detected = true;
    state.position++;

    if (skipSeparationSpace(state, true, -1)) {
      if (state.lineIndent <= nodeIndent) {
        _result.push(null);
        ch = state.input.charCodeAt(state.position);
        continue;
      }
    }

    _line = state.line;
    composeNode(state, nodeIndent, CONTEXT_BLOCK_IN, false, true);
    _result.push(state.result);
    skipSeparationSpace(state, true, -1);

    ch = state.input.charCodeAt(state.position);

    if ((state.line === _line || state.lineIndent > nodeIndent) && (ch !== 0)) {
      throwError(state, 'bad indentation of a sequence entry');
    } else if (state.lineIndent < nodeIndent) {
      break;
    }
  }

  if (detected) {
    state.tag = _tag;
    state.anchor = _anchor;
    state.kind = 'sequence';
    state.result = _result;
    return true;
  }
  return false;
}

function readBlockMapping(state, nodeIndent, flowIndent) {
  var following,
      allowCompact,
      _line,
      _keyLine,
      _keyLineStart,
      _keyPos,
      _tag          = state.tag,
      _anchor       = state.anchor,
      _result       = {},
      overridableKeys = Object.create(null),
      keyTag        = null,
      keyNode       = null,
      valueNode     = null,
      atExplicitKey = false,
      detected      = false,
      ch;

  // there is a leading tab before this token, so it can't be a block sequence/mapping;
  // it can still be flow sequence/mapping or a scalar
  if (state.firstTabInLine !== -1) return false;

  if (state.anchor !== null) {
    state.anchorMap[state.anchor] = _result;
  }

  ch = state.input.charCodeAt(state.position);

  while (ch !== 0) {
    if (!atExplicitKey && state.firstTabInLine !== -1) {
      state.position = state.firstTabInLine;
      throwError(state, 'tab characters must not be used in indentation');
    }

    following = state.input.charCodeAt(state.position + 1);
    _line = state.line; // Save the current line.

    //
    // Explicit notation case. There are two separate blocks:
    // first for the key (denoted by "?") and second for the value (denoted by ":")
    //
    if ((ch === 0x3F/* ? */ || ch === 0x3A/* : */) && is_WS_OR_EOL(following)) {

      if (ch === 0x3F/* ? */) {
        if (atExplicitKey) {
          storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null, _keyLine, _keyLineStart, _keyPos);
          keyTag = keyNode = valueNode = null;
        }

        detected = true;
        atExplicitKey = true;
        allowCompact = true;

      } else if (atExplicitKey) {
        // i.e. 0x3A/* : */ === character after the explicit key.
        atExplicitKey = false;
        allowCompact = true;

      } else {
        throwError(state, 'incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line');
      }

      state.position += 1;
      ch = following;

    //
    // Implicit notation case. Flow-style node as the key first, then ":", and the value.
    //
    } else {
      _keyLine = state.line;
      _keyLineStart = state.lineStart;
      _keyPos = state.position;

      if (!composeNode(state, flowIndent, CONTEXT_FLOW_OUT, false, true)) {
        // Neither implicit nor explicit notation.
        // Reading is done. Go to the epilogue.
        break;
      }

      if (state.line === _line) {
        ch = state.input.charCodeAt(state.position);

        while (is_WHITE_SPACE(ch)) {
          ch = state.input.charCodeAt(++state.position);
        }

        if (ch === 0x3A/* : */) {
          ch = state.input.charCodeAt(++state.position);

          if (!is_WS_OR_EOL(ch)) {
            throwError(state, 'a whitespace character is expected after the key-value separator within a block mapping');
          }

          if (atExplicitKey) {
            storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null, _keyLine, _keyLineStart, _keyPos);
            keyTag = keyNode = valueNode = null;
          }

          detected = true;
          atExplicitKey = false;
          allowCompact = false;
          keyTag = state.tag;
          keyNode = state.result;

        } else if (detected) {
          throwError(state, 'can not read an implicit mapping pair; a colon is missed');

        } else {
          state.tag = _tag;
          state.anchor = _anchor;
          return true; // Keep the result of `composeNode`.
        }

      } else if (detected) {
        throwError(state, 'can not read a block mapping entry; a multiline key may not be an implicit key');

      } else {
        state.tag = _tag;
        state.anchor = _anchor;
        return true; // Keep the result of `composeNode`.
      }
    }

    //
    // Common reading code for both explicit and implicit notations.
    //
    if (state.line === _line || state.lineIndent > nodeIndent) {
      if (atExplicitKey) {
        _keyLine = state.line;
        _keyLineStart = state.lineStart;
        _keyPos = state.position;
      }

      if (composeNode(state, nodeIndent, CONTEXT_BLOCK_OUT, true, allowCompact)) {
        if (atExplicitKey) {
          keyNode = state.result;
        } else {
          valueNode = state.result;
        }
      }

      if (!atExplicitKey) {
        storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, _keyLine, _keyLineStart, _keyPos);
        keyTag = keyNode = valueNode = null;
      }

      skipSeparationSpace(state, true, -1);
      ch = state.input.charCodeAt(state.position);
    }

    if ((state.line === _line || state.lineIndent > nodeIndent) && (ch !== 0)) {
      throwError(state, 'bad indentation of a mapping entry');
    } else if (state.lineIndent < nodeIndent) {
      break;
    }
  }

  //
  // Epilogue.
  //

  // Special case: last mapping's node contains only the key in explicit notation.
  if (atExplicitKey) {
    storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null, _keyLine, _keyLineStart, _keyPos);
  }

  // Expose the resulting mapping.
  if (detected) {
    state.tag = _tag;
    state.anchor = _anchor;
    state.kind = 'mapping';
    state.result = _result;
  }

  return detected;
}

function readTagProperty(state) {
  var _position,
      isVerbatim = false,
      isNamed    = false,
      tagHandle,
      tagName,
      ch;

  ch = state.input.charCodeAt(state.position);

  if (ch !== 0x21/* ! */) return false;

  if (state.tag !== null) {
    throwError(state, 'duplication of a tag property');
  }

  ch = state.input.charCodeAt(++state.position);

  if (ch === 0x3C/* < */) {
    isVerbatim = true;
    ch = state.input.charCodeAt(++state.position);

  } else if (ch === 0x21/* ! */) {
    isNamed = true;
    tagHandle = '!!';
    ch = state.input.charCodeAt(++state.position);

  } else {
    tagHandle = '!';
  }

  _position = state.position;

  if (isVerbatim) {
    do { ch = state.input.charCodeAt(++state.position); }
    while (ch !== 0 && ch !== 0x3E/* > */);

    if (state.position < state.length) {
      tagName = state.input.slice(_position, state.position);
      ch = state.input.charCodeAt(++state.position);
    } else {
      throwError(state, 'unexpected end of the stream within a verbatim tag');
    }
  } else {
    while (ch !== 0 && !is_WS_OR_EOL(ch)) {

      if (ch === 0x21/* ! */) {
        if (!isNamed) {
          tagHandle = state.input.slice(_position - 1, state.position + 1);

          if (!PATTERN_TAG_HANDLE.test(tagHandle)) {
            throwError(state, 'named tag handle cannot contain such characters');
          }

          isNamed = true;
          _position = state.position + 1;
        } else {
          throwError(state, 'tag suffix cannot contain exclamation marks');
        }
      }

      ch = state.input.charCodeAt(++state.position);
    }

    tagName = state.input.slice(_position, state.position);

    if (PATTERN_FLOW_INDICATORS.test(tagName)) {
      throwError(state, 'tag suffix cannot contain flow indicator characters');
    }
  }

  if (tagName && !PATTERN_TAG_URI.test(tagName)) {
    throwError(state, 'tag name cannot contain such characters: ' + tagName);
  }

  try {
    tagName = decodeURIComponent(tagName);
  } catch (err) {
    throwError(state, 'tag name is malformed: ' + tagName);
  }

  if (isVerbatim) {
    state.tag = tagName;

  } else if (_hasOwnProperty.call(state.tagMap, tagHandle)) {
    state.tag = state.tagMap[tagHandle] + tagName;

  } else if (tagHandle === '!') {
    state.tag = '!' + tagName;

  } else if (tagHandle === '!!') {
    state.tag = 'tag:yaml.org,2002:' + tagName;

  } else {
    throwError(state, 'undeclared tag handle "' + tagHandle + '"');
  }

  return true;
}

function readAnchorProperty(state) {
  var _position,
      ch;

  ch = state.input.charCodeAt(state.position);

  if (ch !== 0x26/* & */) return false;

  if (state.anchor !== null) {
    throwError(state, 'duplication of an anchor property');
  }

  ch = state.input.charCodeAt(++state.position);
  _position = state.position;

  while (ch !== 0 && !is_WS_OR_EOL(ch) && !is_FLOW_INDICATOR(ch)) {
    ch = state.input.charCodeAt(++state.position);
  }

  if (state.position === _position) {
    throwError(state, 'name of an anchor node must contain at least one character');
  }

  state.anchor = state.input.slice(_position, state.position);
  return true;
}

function readAlias(state) {
  var _position, alias,
      ch;

  ch = state.input.charCodeAt(state.position);

  if (ch !== 0x2A/* * */) return false;

  ch = state.input.charCodeAt(++state.position);
  _position = state.position;

  while (ch !== 0 && !is_WS_OR_EOL(ch) && !is_FLOW_INDICATOR(ch)) {
    ch = state.input.charCodeAt(++state.position);
  }

  if (state.position === _position) {
    throwError(state, 'name of an alias node must contain at least one character');
  }

  alias = state.input.slice(_position, state.position);

  if (!_hasOwnProperty.call(state.anchorMap, alias)) {
    throwError(state, 'unidentified alias "' + alias + '"');
  }

  state.result = state.anchorMap[alias];
  skipSeparationSpace(state, true, -1);
  return true;
}

function composeNode(state, parentIndent, nodeContext, allowToSeek, allowCompact) {
  var allowBlockStyles,
      allowBlockScalars,
      allowBlockCollections,
      indentStatus = 1, // 1: this>parent, 0: this=parent, -1: this<parent
      atNewLine  = false,
      hasContent = false,
      typeIndex,
      typeQuantity,
      typeList,
      type,
      flowIndent,
      blockIndent;

  if (state.listener !== null) {
    state.listener('open', state);
  }

  state.tag    = null;
  state.anchor = null;
  state.kind   = null;
  state.result = null;

  allowBlockStyles = allowBlockScalars = allowBlockCollections =
    CONTEXT_BLOCK_OUT === nodeContext ||
    CONTEXT_BLOCK_IN  === nodeContext;

  if (allowToSeek) {
    if (skipSeparationSpace(state, true, -1)) {
      atNewLine = true;

      if (state.lineIndent > parentIndent) {
        indentStatus = 1;
      } else if (state.lineIndent === parentIndent) {
        indentStatus = 0;
      } else if (state.lineIndent < parentIndent) {
        indentStatus = -1;
      }
    }
  }

  if (indentStatus === 1) {
    while (readTagProperty(state) || readAnchorProperty(state)) {
      if (skipSeparationSpace(state, true, -1)) {
        atNewLine = true;
        allowBlockCollections = allowBlockStyles;

        if (state.lineIndent > parentIndent) {
          indentStatus = 1;
        } else if (state.lineIndent === parentIndent) {
          indentStatus = 0;
        } else if (state.lineIndent < parentIndent) {
          indentStatus = -1;
        }
      } else {
        allowBlockCollections = false;
      }
    }
  }

  if (allowBlockCollections) {
    allowBlockCollections = atNewLine || allowCompact;
  }

  if (indentStatus === 1 || CONTEXT_BLOCK_OUT === nodeContext) {
    if (CONTEXT_FLOW_IN === nodeContext || CONTEXT_FLOW_OUT === nodeContext) {
      flowIndent = parentIndent;
    } else {
      flowIndent = parentIndent + 1;
    }

    blockIndent = state.position - state.lineStart;

    if (indentStatus === 1) {
      if (allowBlockCollections &&
          (readBlockSequence(state, blockIndent) ||
           readBlockMapping(state, blockIndent, flowIndent)) ||
          readFlowCollection(state, flowIndent)) {
        hasContent = true;
      } else {
        if ((allowBlockScalars && readBlockScalar(state, flowIndent)) ||
            readSingleQuotedScalar(state, flowIndent) ||
            readDoubleQuotedScalar(state, flowIndent)) {
          hasContent = true;

        } else if (readAlias(state)) {
          hasContent = true;

          if (state.tag !== null || state.anchor !== null) {
            throwError(state, 'alias node should not have any properties');
          }

        } else if (readPlainScalar(state, flowIndent, CONTEXT_FLOW_IN === nodeContext)) {
          hasContent = true;

          if (state.tag === null) {
            state.tag = '?';
          }
        }

        if (state.anchor !== null) {
          state.anchorMap[state.anchor] = state.result;
        }
      }
    } else if (indentStatus === 0) {
      // Special case: block sequences are allowed to have same indentation level as the parent.
      // http://www.yaml.org/spec/1.2/spec.html#id2799784
      hasContent = allowBlockCollections && readBlockSequence(state, blockIndent);
    }
  }

  if (state.tag === null) {
    if (state.anchor !== null) {
      state.anchorMap[state.anchor] = state.result;
    }

  } else if (state.tag === '?') {
    // Implicit resolving is not allowed for non-scalar types, and '?'
    // non-specific tag is only automatically assigned to plain scalars.
    //
    // We only need to check kind conformity in case user explicitly assigns '?'
    // tag, for example like this: "!<?> [0]"
    //
    if (state.result !== null && state.kind !== 'scalar') {
      throwError(state, 'unacceptable node kind for !<?> tag; it should be "scalar", not "' + state.kind + '"');
    }

    for (typeIndex = 0, typeQuantity = state.implicitTypes.length; typeIndex < typeQuantity; typeIndex += 1) {
      type = state.implicitTypes[typeIndex];

      if (type.resolve(state.result)) { // `state.result` updated in resolver if matched
        state.result = type.construct(state.result);
        state.tag = type.tag;
        if (state.anchor !== null) {
          state.anchorMap[state.anchor] = state.result;
        }
        break;
      }
    }
  } else if (state.tag !== '!') {
    if (_hasOwnProperty.call(state.typeMap[state.kind || 'fallback'], state.tag)) {
      type = state.typeMap[state.kind || 'fallback'][state.tag];
    } else {
      // looking for multi type
      type = null;
      typeList = state.typeMap.multi[state.kind || 'fallback'];

      for (typeIndex = 0, typeQuantity = typeList.length; typeIndex < typeQuantity; typeIndex += 1) {
        if (state.tag.slice(0, typeList[typeIndex].tag.length) === typeList[typeIndex].tag) {
          type = typeList[typeIndex];
          break;
        }
      }
    }

    if (!type) {
      throwError(state, 'unknown tag !<' + state.tag + '>');
    }

    if (state.result !== null && type.kind !== state.kind) {
      throwError(state, 'unacceptable node kind for !<' + state.tag + '> tag; it should be "' + type.kind + '", not "' + state.kind + '"');
    }

    if (!type.resolve(state.result, state.tag)) { // `state.result` updated in resolver if matched
      throwError(state, 'cannot resolve a node with !<' + state.tag + '> explicit tag');
    } else {
      state.result = type.construct(state.result, state.tag);
      if (state.anchor !== null) {
        state.anchorMap[state.anchor] = state.result;
      }
    }
  }

  if (state.listener !== null) {
    state.listener('close', state);
  }
  return state.tag !== null ||  state.anchor !== null || hasContent;
}

function readDocument(state) {
  var documentStart = state.position,
      _position,
      directiveName,
      directiveArgs,
      hasDirectives = false,
      ch;

  state.version = null;
  state.checkLineBreaks = state.legacy;
  state.tagMap = Object.create(null);
  state.anchorMap = Object.create(null);

  while ((ch = state.input.charCodeAt(state.position)) !== 0) {
    skipSeparationSpace(state, true, -1);

    ch = state.input.charCodeAt(state.position);

    if (state.lineIndent > 0 || ch !== 0x25/* % */) {
      break;
    }

    hasDirectives = true;
    ch = state.input.charCodeAt(++state.position);
    _position = state.position;

    while (ch !== 0 && !is_WS_OR_EOL(ch)) {
      ch = state.input.charCodeAt(++state.position);
    }

    directiveName = state.input.slice(_position, state.position);
    directiveArgs = [];

    if (directiveName.length < 1) {
      throwError(state, 'directive name must not be less than one character in length');
    }

    while (ch !== 0) {
      while (is_WHITE_SPACE(ch)) {
        ch = state.input.charCodeAt(++state.position);
      }

      if (ch === 0x23/* # */) {
        do { ch = state.input.charCodeAt(++state.position); }
        while (ch !== 0 && !is_EOL(ch));
        break;
      }

      if (is_EOL(ch)) break;

      _position = state.position;

      while (ch !== 0 && !is_WS_OR_EOL(ch)) {
        ch = state.input.charCodeAt(++state.position);
      }

      directiveArgs.push(state.input.slice(_position, state.position));
    }

    if (ch !== 0) readLineBreak(state);

    if (_hasOwnProperty.call(directiveHandlers, directiveName)) {
      directiveHandlers[directiveName](state, directiveName, directiveArgs);
    } else {
      throwWarning(state, 'unknown document directive "' + directiveName + '"');
    }
  }

  skipSeparationSpace(state, true, -1);

  if (state.lineIndent === 0 &&
      state.input.charCodeAt(state.position)     === 0x2D/* - */ &&
      state.input.charCodeAt(state.position + 1) === 0x2D/* - */ &&
      state.input.charCodeAt(state.position + 2) === 0x2D/* - */) {
    state.position += 3;
    skipSeparationSpace(state, true, -1);

  } else if (hasDirectives) {
    throwError(state, 'directives end mark is expected');
  }

  composeNode(state, state.lineIndent - 1, CONTEXT_BLOCK_OUT, false, true);
  skipSeparationSpace(state, true, -1);

  if (state.checkLineBreaks &&
      PATTERN_NON_ASCII_LINE_BREAKS.test(state.input.slice(documentStart, state.position))) {
    throwWarning(state, 'non-ASCII line breaks are interpreted as content');
  }

  state.documents.push(state.result);

  if (state.position === state.lineStart && testDocumentSeparator(state)) {

    if (state.input.charCodeAt(state.position) === 0x2E/* . */) {
      state.position += 3;
      skipSeparationSpace(state, true, -1);
    }
    return;
  }

  if (state.position < (state.length - 1)) {
    throwError(state, 'end of the stream or a document separator is expected');
  } else {
    return;
  }
}


function loadDocuments(input, options) {
  input = String(input);
  options = options || {};

  if (input.length !== 0) {

    // Add tailing `\n` if not exists
    if (input.charCodeAt(input.length - 1) !== 0x0A/* LF */ &&
        input.charCodeAt(input.length - 1) !== 0x0D/* CR */) {
      input += '\n';
    }

    // Strip BOM
    if (input.charCodeAt(0) === 0xFEFF) {
      input = input.slice(1);
    }
  }

  var state = new State(input, options);

  var nullpos = input.indexOf('\0');

  if (nullpos !== -1) {
    state.position = nullpos;
    throwError(state, 'null byte is not allowed in input');
  }

  // Use 0 as string terminator. That significantly simplifies bounds check.
  state.input += '\0';

  while (state.input.charCodeAt(state.position) === 0x20/* Space */) {
    state.lineIndent += 1;
    state.position += 1;
  }

  while (state.position < (state.length - 1)) {
    readDocument(state);
  }

  return state.documents;
}


function loadAll(input, iterator, options) {
  if (iterator !== null && typeof iterator === 'object' && typeof options === 'undefined') {
    options = iterator;
    iterator = null;
  }

  var documents = loadDocuments(input, options);

  if (typeof iterator !== 'function') {
    return documents;
  }

  for (var index = 0, length = documents.length; index < length; index += 1) {
    iterator(documents[index]);
  }
}


function load(input, options) {
  var documents = loadDocuments(input, options);

  if (documents.length === 0) {
    /*eslint-disable no-undefined*/
    return undefined;
  } else if (documents.length === 1) {
    return documents[0];
  }
  throw new YAMLException('expected a single document in the stream, but found more');
}


module.exports.loadAll = loadAll;
module.exports.load    = load;


path: node_modules/js-yaml/lib/dumper.js
content: 'use strict';

/*eslint-disable no-use-before-define*/

var common              = require('./common');
var YAMLException       = require('./exception');
var DEFAULT_SCHEMA      = require('./schema/default');

var _toString       = Object.prototype.toString;
var _hasOwnProperty = Object.prototype.hasOwnProperty;

var CHAR_BOM                  = 0xFEFF;
var CHAR_TAB                  = 0x09; /* Tab */
var CHAR_LINE_FEED            = 0x0A; /* LF */
var CHAR_CARRIAGE_RETURN      = 0x0D; /* CR */
var CHAR_SPACE                = 0x20; /* Space */
var CHAR_EXCLAMATION          = 0x21; /* ! */
var CHAR_DOUBLE_QUOTE         = 0x22; /* " */
var CHAR_SHARP                = 0x23; /* # */
var CHAR_PERCENT              = 0x25; /* % */
var CHAR_AMPERSAND            = 0x26; /* & */
var CHAR_SINGLE_QUOTE         = 0x27; /* ' */
var CHAR_ASTERISK             = 0x2A; /* * */
var CHAR_COMMA                = 0x2C; /* , */
var CHAR_MINUS                = 0x2D; /* - */
var CHAR_COLON                = 0x3A; /* : */
var CHAR_EQUALS               = 0x3D; /* = */
var CHAR_GREATER_THAN         = 0x3E; /* > */
var CHAR_QUESTION             = 0x3F; /* ? */
var CHAR_COMMERCIAL_AT        = 0x40; /* @ */
var CHAR_LEFT_SQUARE_BRACKET  = 0x5B; /* [ */
var CHAR_RIGHT_SQUARE_BRACKET = 0x5D; /* ] */
var CHAR_GRAVE_ACCENT         = 0x60; /* ` */
var CHAR_LEFT_CURLY_BRACKET   = 0x7B; /* { */
var CHAR_VERTICAL_LINE        = 0x7C; /* | */
var CHAR_RIGHT_CURLY_BRACKET  = 0x7D; /* } */

var ESCAPE_SEQUENCES = {};

ESCAPE_SEQUENCES[0x00]   = '\\0';
ESCAPE_SEQUENCES[0x07]   = '\\a';
ESCAPE_SEQUENCES[0x08]   = '\\b';
ESCAPE_SEQUENCES[0x09]   = '\\t';
ESCAPE_SEQUENCES[0x0A]   = '\\n';
ESCAPE_SEQUENCES[0x0B]   = '\\v';
ESCAPE_SEQUENCES[0x0C]   = '\\f';
ESCAPE_SEQUENCES[0x0D]   = '\\r';
ESCAPE_SEQUENCES[0x1B]   = '\\e';
ESCAPE_SEQUENCES[0x22]   = '\\"';
ESCAPE_SEQUENCES[0x5C]   = '\\\\';
ESCAPE_SEQUENCES[0x85]   = '\\N';
ESCAPE_SEQUENCES[0xA0]   = '\\_';
ESCAPE_SEQUENCES[0x2028] = '\\L';
ESCAPE_SEQUENCES[0x2029] = '\\P';

var DEPRECATED_BOOLEANS_SYNTAX = [
  'y', 'Y', 'yes', 'Yes', 'YES', 'on', 'On', 'ON',
  'n', 'N', 'no', 'No', 'NO', 'off', 'Off', 'OFF'
];

var DEPRECATED_BASE60_SYNTAX = /^[-+]?[0-9_]+(?::[0-9_]+)+(?:\.[0-9_]*)?$/;

function compileStyleMap(schema, map) {
  var result, keys, index, length, tag, style, type;

  if (map === null) return {};

  result = {};
  keys = Object.keys(map);

  for (index = 0, length = keys.length; index < length; index += 1) {
    tag = keys[index];
    style = String(map[tag]);

    if (tag.slice(0, 2) === '!!') {
      tag = 'tag:yaml.org,2002:' + tag.slice(2);
    }
    type = schema.compiledTypeMap['fallback'][tag];

    if (type && _hasOwnProperty.call(type.styleAliases, style)) {
      style = type.styleAliases[style];
    }

    result[tag] = style;
  }

  return result;
}

function encodeHex(character) {
  var string, handle, length;

  string = character.toString(16).toUpperCase();

  if (character <= 0xFF) {
    handle = 'x';
    length = 2;
  } else if (character <= 0xFFFF) {
    handle = 'u';
    length = 4;
  } else if (character <= 0xFFFFFFFF) {
    handle = 'U';
    length = 8;
  } else {
    throw new YAMLException('code point within a string may not be greater than 0xFFFFFFFF');
  }

  return '\\' + handle + common.repeat('0', length - string.length) + string;
}


var QUOTING_TYPE_SINGLE = 1,
    QUOTING_TYPE_DOUBLE = 2;

function State(options) {
  this.schema        = options['schema'] || DEFAULT_SCHEMA;
  this.indent        = Math.max(1, (options['indent'] || 2));
  this.noArrayIndent = options['noArrayIndent'] || false;
  this.skipInvalid   = options['skipInvalid'] || false;
  this.flowLevel     = (common.isNothing(options['flowLevel']) ? -1 : options['flowLevel']);
  this.styleMap      = compileStyleMap(this.schema, options['styles'] || null);
  this.sortKeys      = options['sortKeys'] || false;
  this.lineWidth     = options['lineWidth'] || 80;
  this.noRefs        = options['noRefs'] || false;
  this.noCompatMode  = options['noCompatMode'] || false;
  this.condenseFlow  = options['condenseFlow'] || false;
  this.quotingType   = options['quotingType'] === '"' ? QUOTING_TYPE_DOUBLE : QUOTING_TYPE_SINGLE;
  this.forceQuotes   = options['forceQuotes'] || false;
  this.replacer      = typeof options['replacer'] === 'function' ? options['replacer'] : null;

  this.implicitTypes = this.schema.compiledImplicit;
  this.explicitTypes = this.schema.compiledExplicit;

  this.tag = null;
  this.result = '';

  this.duplicates = [];
  this.usedDuplicates = null;
}

// Indents every line in a string. Empty lines (\n only) are not indented.
function indentString(string, spaces) {
  var ind = common.repeat(' ', spaces),
      position = 0,
      next = -1,
      result = '',
      line,
      length = string.length;

  while (position < length) {
    next = string.indexOf('\n', position);
    if (next === -1) {
      line = string.slice(position);
      position = length;
    } else {
      line = string.slice(position, next + 1);
      position = next + 1;
    }

    if (line.length && line !== '\n') result += ind;

    result += line;
  }

  return result;
}

function generateNextLine(state, level) {
  return '\n' + common.repeat(' ', state.indent * level);
}

function testImplicitResolving(state, str) {
  var index, length, type;

  for (index = 0, length = state.implicitTypes.length; index < length; index += 1) {
    type = state.implicitTypes[index];

    if (type.resolve(str)) {
      return true;
    }
  }

  return false;
}

// [33] s-white ::= s-space | s-tab
function isWhitespace(c) {
  return c === CHAR_SPACE || c === CHAR_TAB;
}

// Returns true if the character can be printed without escaping.
// From YAML 1.2: "any allowed characters known to be non-printable
// should also be escaped. [However,] This isn’t mandatory"
// Derived from nb-char - \t - #x85 - #xA0 - #x2028 - #x2029.
function isPrintable(c) {
  return  (0x00020 <= c && c <= 0x00007E)
      || ((0x000A1 <= c && c <= 0x00D7FF) && c !== 0x2028 && c !== 0x2029)
      || ((0x0E000 <= c && c <= 0x00FFFD) && c !== CHAR_BOM)
      ||  (0x10000 <= c && c <= 0x10FFFF);
}

// [34] ns-char ::= nb-char - s-white
// [27] nb-char ::= c-printable - b-char - c-byte-order-mark
// [26] b-char  ::= b-line-feed | b-carriage-return
// Including s-white (for some reason, examples doesn't match specs in this aspect)
// ns-char ::= c-printable - b-line-feed - b-carriage-return - c-byte-order-mark
function isNsCharOrWhitespace(c) {
  return isPrintable(c)
    && c !== CHAR_BOM
    // - b-char
    && c !== CHAR_CARRIAGE_RETURN
    && c !== CHAR_LINE_FEED;
}

// [127]  ns-plain-safe(c) ::= c = flow-out  ⇒ ns-plain-safe-out
//                             c = flow-in   ⇒ ns-plain-safe-in
//                             c = block-key ⇒ ns-plain-safe-out
//                             c = flow-key  ⇒ ns-plain-safe-in
// [128] ns-plain-safe-out ::= ns-char
// [129]  ns-plain-safe-in ::= ns-char - c-flow-indicator
// [130]  ns-plain-char(c) ::=  ( ns-plain-safe(c) - “:” - “#” )
//                            | ( /* An ns-char preceding */ “#” )
//                            | ( “:” /* Followed by an ns-plain-safe(c) */ )
function isPlainSafe(c, prev, inblock) {
  var cIsNsCharOrWhitespace = isNsCharOrWhitespace(c);
  var cIsNsChar = cIsNsCharOrWhitespace && !isWhitespace(c);
  return (
    // ns-plain-safe
    inblock ? // c = flow-in
      cIsNsCharOrWhitespace
      : cIsNsCharOrWhitespace
        // - c-flow-indicator
        && c !== CHAR_COMMA
        && c !== CHAR_LEFT_SQUARE_BRACKET
        && c !== CHAR_RIGHT_SQUARE_BRACKET
        && c !== CHAR_LEFT_CURLY_BRACKET
        && c !== CHAR_RIGHT_CURLY_BRACKET
  )
    // ns-plain-char
    && c !== CHAR_SHARP // false on '#'
    && !(prev === CHAR_COLON && !cIsNsChar) // false on ': '
    || (isNsCharOrWhitespace(prev) && !isWhitespace(prev) && c === CHAR_SHARP) // change to true on '[^ ]#'
    || (prev === CHAR_COLON && cIsNsChar); // change to true on ':[^ ]'
}

// Simplified test for values allowed as the first character in plain style.
function isPlainSafeFirst(c) {
  // Uses a subset of ns-char - c-indicator
  // where ns-char = nb-char - s-white.
  // No support of ( ( “?” | “:” | “-” ) /* Followed by an ns-plain-safe(c)) */ ) part
  return isPrintable(c) && c !== CHAR_BOM
    && !isWhitespace(c) // - s-white
    // - (c-indicator ::=
    // “-” | “?” | “:” | “,” | “[” | “]” | “{” | “}”
    && c !== CHAR_MINUS
    && c !== CHAR_QUESTION
    && c !== CHAR_COLON
    && c !== CHAR_COMMA
    && c !== CHAR_LEFT_SQUARE_BRACKET
    && c !== CHAR_RIGHT_SQUARE_BRACKET
    && c !== CHAR_LEFT_CURLY_BRACKET
    && c !== CHAR_RIGHT_CURLY_BRACKET
    // | “#” | “&” | “*” | “!” | “|” | “=” | “>” | “'” | “"”
    && c !== CHAR_SHARP
    && c !== CHAR_AMPERSAND
    && c !== CHAR_ASTERISK
    && c !== CHAR_EXCLAMATION
    && c !== CHAR_VERTICAL_LINE
    && c !== CHAR_EQUALS
    && c !== CHAR_GREATER_THAN
    && c !== CHAR_SINGLE_QUOTE
    && c !== CHAR_DOUBLE_QUOTE
    // | “%” | “@” | “`”)
    && c !== CHAR_PERCENT
    && c !== CHAR_COMMERCIAL_AT
    && c !== CHAR_GRAVE_ACCENT;
}

// Simplified test for values allowed as the last character in plain style.
function isPlainSafeLast(c) {
  // just not whitespace or colon, it will be checked to be plain character later
  return !isWhitespace(c) && c !== CHAR_COLON;
}

// Same as 'string'.codePointAt(pos), but works in older browsers.
function codePointAt(string, pos) {
  var first = string.charCodeAt(pos), second;
  if (first >= 0xD800 && first <= 0xDBFF && pos + 1 < string.length) {
    second = string.charCodeAt(pos + 1);
    if (second >= 0xDC00 && second <= 0xDFFF) {
      // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
      return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
    }
  }
  return first;
}

// Determines whether block indentation indicator is required.
function needIndentIndicator(string) {
  var leadingSpaceRe = /^\n* /;
  return leadingSpaceRe.test(string);
}

var STYLE_PLAIN   = 1,
    STYLE_SINGLE  = 2,
    STYLE_LITERAL = 3,
    STYLE_FOLDED  = 4,
    STYLE_DOUBLE  = 5;

// Determines which scalar styles are possible and returns the preferred style.
// lineWidth = -1 => no limit.
// Pre-conditions: str.length > 0.
// Post-conditions:
//    STYLE_PLAIN or STYLE_SINGLE => no \n are in the string.
//    STYLE_LITERAL => no lines are suitable for folding (or lineWidth is -1).
//    STYLE_FOLDED => a line > lineWidth and can be folded (and lineWidth != -1).
function chooseScalarStyle(string, singleLineOnly, indentPerLevel, lineWidth,
  testAmbiguousType, quotingType, forceQuotes, inblock) {

  var i;
  var char = 0;
  var prevChar = null;
  var hasLineBreak = false;
  var hasFoldableLine = false; // only checked if shouldTrackWidth
  var shouldTrackWidth = lineWidth !== -1;
  var previousLineBreak = -1; // count the first line correctly
  var plain = isPlainSafeFirst(codePointAt(string, 0))
          && isPlainSafeLast(codePointAt(string, string.length - 1));

  if (singleLineOnly || forceQuotes) {
    // Case: no block styles.
    // Check for disallowed characters to rule out plain and single.
    for (i = 0; i < string.length; char >= 0x10000 ? i += 2 : i++) {
      char = codePointAt(string, i);
      if (!isPrintable(char)) {
        return STYLE_DOUBLE;
      }
      plain = plain && isPlainSafe(char, prevChar, inblock);
      prevChar = char;
    }
  } else {
    // Case: block styles permitted.
    for (i = 0; i < string.length; char >= 0x10000 ? i += 2 : i++) {
      char = codePointAt(string, i);
      if (char === CHAR_LINE_FEED) {
        hasLineBreak = true;
        // Check if any line can be folded.
        if (shouldTrackWidth) {
          hasFoldableLine = hasFoldableLine ||
            // Foldable line = too long, and not more-indented.
            (i - previousLineBreak - 1 > lineWidth &&
             string[previousLineBreak + 1] !== ' ');
          previousLineBreak = i;
        }
      } else if (!isPrintable(char)) {
        return STYLE_DOUBLE;
      }
      plain = plain && isPlainSafe(char, prevChar, inblock);
      prevChar = char;
    }
    // in case the end is missing a \n
    hasFoldableLine = hasFoldableLine || (shouldTrackWidth &&
      (i - previousLineBreak - 1 > lineWidth &&
       string[previousLineBreak + 1] !== ' '));
  }
  // Although every style can represent \n without escaping, prefer block styles
  // for multiline, since they're more readable and they don't add empty lines.
  // Also prefer folding a super-long line.
  if (!hasLineBreak && !hasFoldableLine) {
    // Strings interpretable as another type have to be quoted;
    // e.g. the string 'true' vs. the boolean true.
    if (plain && !forceQuotes && !testAmbiguousType(string)) {
      return STYLE_PLAIN;
    }
    return quotingType === QUOTING_TYPE_DOUBLE ? STYLE_DOUBLE : STYLE_SINGLE;
  }
  // Edge case: block indentation indicator can only have one digit.
  if (indentPerLevel > 9 && needIndentIndicator(string)) {
    return STYLE_DOUBLE;
  }
  // At this point we know block styles are valid.
  // Prefer literal style unless we want to fold.
  if (!forceQuotes) {
    return hasFoldableLine ? STYLE_FOLDED : STYLE_LITERAL;
  }
  return quotingType === QUOTING_TYPE_DOUBLE ? STYLE_DOUBLE : STYLE_SINGLE;
}

// Note: line breaking/folding is implemented for only the folded style.
// NB. We drop the last trailing newline (if any) of a returned block scalar
//  since the dumper adds its own newline. This always works:
//    • No ending newline => unaffected; already using strip "-" chomping.
//    • Ending newline    => removed then restored.
//  Importantly, this keeps the "+" chomp indicator from gaining an extra line.
function writeScalar(state, string, level, iskey, inblock) {
  state.dump = (function () {
    if (string.length === 0) {
      return state.quotingType === QUOTING_TYPE_DOUBLE ? '""' : "''";
    }
    if (!state.noCompatMode) {
      if (DEPRECATED_BOOLEANS_SYNTAX.indexOf(string) !== -1 || DEPRECATED_BASE60_SYNTAX.test(string)) {
        return state.quotingType === QUOTING_TYPE_DOUBLE ? ('"' + string + '"') : ("'" + string + "'");
      }
    }

    var indent = state.indent * Math.max(1, level); // no 0-indent scalars
    // As indentation gets deeper, let the width decrease monotonically
    // to the lower bound min(state.lineWidth, 40).
    // Note that this implies
    //  state.lineWidth ≤ 40 + state.indent: width is fixed at the lower bound.
    //  state.lineWidth > 40 + state.indent: width decreases until the lower bound.
    // This behaves better than a constant minimum width which disallows narrower options,
    // or an indent threshold which causes the width to suddenly increase.
    var lineWidth = state.lineWidth === -1
      ? -1 : Math.max(Math.min(state.lineWidth, 40), state.lineWidth - indent);

    // Without knowing if keys are implicit/explicit, assume implicit for safety.
    var singleLineOnly = iskey
      // No block styles in flow mode.
      || (state.flowLevel > -1 && level >= state.flowLevel);
    function testAmbiguity(string) {
      return testImplicitResolving(state, string);
    }

    switch (chooseScalarStyle(string, singleLineOnly, state.indent, lineWidth,
      testAmbiguity, state.quotingType, state.forceQuotes && !iskey, inblock)) {

      case STYLE_PLAIN:
        return string;
      case STYLE_SINGLE:
        return "'" + string.replace(/'/g, "''") + "'";
      case STYLE_LITERAL:
        return '|' + blockHeader(string, state.indent)
          + dropEndingNewline(indentString(string, indent));
      case STYLE_FOLDED:
        return '>' + blockHeader(string, state.indent)
          + dropEndingNewline(indentString(foldString(string, lineWidth), indent));
      case STYLE_DOUBLE:
        return '"' + escapeString(string, lineWidth) + '"';
      default:
        throw new YAMLException('impossible error: invalid scalar style');
    }
  }());
}

// Pre-conditions: string is valid for a block scalar, 1 <= indentPerLevel <= 9.
function blockHeader(string, indentPerLevel) {
  var indentIndicator = needIndentIndicator(string) ? String(indentPerLevel) : '';

  // note the special case: the string '\n' counts as a "trailing" empty line.
  var clip =          string[string.length - 1] === '\n';
  var keep = clip && (string[string.length - 2] === '\n' || string === '\n');
  var chomp = keep ? '+' : (clip ? '' : '-');

  return indentIndicator + chomp + '\n';
}

// (See the note for writeScalar.)
function dropEndingNewline(string) {
  return string[string.length - 1] === '\n' ? string.slice(0, -1) : string;
}

// Note: a long line without a suitable break point will exceed the width limit.
// Pre-conditions: every char in str isPrintable, str.length > 0, width > 0.
function foldString(string, width) {
  // In folded style, $k$ consecutive newlines output as $k+1$ newlines—
  // unless they're before or after a more-indented line, or at the very
  // beginning or end, in which case $k$ maps to $k$.
  // Therefore, parse each chunk as newline(s) followed by a content line.
  var lineRe = /(\n+)([^\n]*)/g;

  // first line (possibly an empty line)
  var result = (function () {
    var nextLF = string.indexOf('\n');
    nextLF = nextLF !== -1 ? nextLF : string.length;
    lineRe.lastIndex = nextLF;
    return foldLine(string.slice(0, nextLF), width);
  }());
  // If we haven't reached the first content line yet, don't add an extra \n.
  var prevMoreIndented = string[0] === '\n' || string[0] === ' ';
  var moreIndented;

  // rest of the lines
  var match;
  while ((match = lineRe.exec(string))) {
    var prefix = match[1], line = match[2];
    moreIndented = (line[0] === ' ');
    result += prefix
      + (!prevMoreIndented && !moreIndented && line !== ''
        ? '\n' : '')
      + foldLine(line, width);
    prevMoreIndented = moreIndented;
  }

  return result;
}

// Greedy line breaking.
// Picks the longest line under the limit each time,
// otherwise settles for the shortest line over the limit.
// NB. More-indented lines *cannot* be folded, as that would add an extra \n.
function foldLine(line, width) {
  if (line === '' || line[0] === ' ') return line;

  // Since a more-indented line adds a \n, breaks can't be followed by a space.
  var breakRe = / [^ ]/g; // note: the match index will always be <= length-2.
  var match;
  // start is an inclusive index. end, curr, and next are exclusive.
  var start = 0, end, curr = 0, next = 0;
  var result = '';

  // Invariants: 0 <= start <= length-1.
  //   0 <= curr <= next <= max(0, length-2). curr - start <= width.
  // Inside the loop:
  //   A match implies length >= 2, so curr and next are <= length-2.
  while ((match = breakRe.exec(line))) {
    next = match.index;
    // maintain invariant: curr - start <= width
    if (next - start > width) {
      end = (curr > start) ? curr : next; // derive end <= length-2
      result += '\n' + line.slice(start, end);
      // skip the space that was output as \n
      start = end + 1;                    // derive start <= length-1
    }
    curr = next;
  }

  // By the invariants, start <= length-1, so there is something left over.
  // It is either the whole string or a part starting from non-whitespace.
  result += '\n';
  // Insert a break if the remainder is too long and there is a break available.
  if (line.length - start > width && curr > start) {
    result += line.slice(start, curr) + '\n' + line.slice(curr + 1);
  } else {
    result += line.slice(start);
  }

  return result.slice(1); // drop extra \n joiner
}

// Escapes a double-quoted string.
function escapeString(string) {
  var result = '';
  var char = 0;
  var escapeSeq;

  for (var i = 0; i < string.length; char >= 0x10000 ? i += 2 : i++) {
    char = codePointAt(string, i);
    escapeSeq = ESCAPE_SEQUENCES[char];

    if (!escapeSeq && isPrintable(char)) {
      result += string[i];
      if (char >= 0x10000) result += string[i + 1];
    } else {
      result += escapeSeq || encodeHex(char);
    }
  }

  return result;
}

function writeFlowSequence(state, level, object) {
  var _result = '',
      _tag    = state.tag,
      index,
      length,
      value;

  for (index = 0, length = object.length; index < length; index += 1) {
    value = object[index];

    if (state.replacer) {
      value = state.replacer.call(object, String(index), value);
    }

    // Write only valid elements, put null instead of invalid elements.
    if (writeNode(state, level, value, false, false) ||
        (typeof value === 'undefined' &&
         writeNode(state, level, null, false, false))) {

      if (_result !== '') _result += ',' + (!state.condenseFlow ? ' ' : '');
      _result += state.dump;
    }
  }

  state.tag = _tag;
  state.dump = '[' + _result + ']';
}

function writeBlockSequence(state, level, object, compact) {
  var _result = '',
      _tag    = state.tag,
      index,
      length,
      value;

  for (index = 0, length = object.length; index < length; index += 1) {
    value = object[index];

    if (state.replacer) {
      value = state.replacer.call(object, String(index), value);
    }

    // Write only valid elements, put null instead of invalid elements.
    if (writeNode(state, level + 1, value, true, true, false, true) ||
        (typeof value === 'undefined' &&
         writeNode(state, level + 1, null, true, true, false, true))) {

      if (!compact || _result !== '') {
        _result += generateNextLine(state, level);
      }

      if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {
        _result += '-';
      } else {
        _result += '- ';
      }

      _result += state.dump;
    }
  }

  state.tag = _tag;
  state.dump = _result || '[]'; // Empty sequence if no valid values.
}

function writeFlowMapping(state, level, object) {
  var _result       = '',
      _tag          = state.tag,
      objectKeyList = Object.keys(object),
      index,
      length,
      objectKey,
      objectValue,
      pairBuffer;

  for (index = 0, length = objectKeyList.length; index < length; index += 1) {

    pairBuffer = '';
    if (_result !== '') pairBuffer += ', ';

    if (state.condenseFlow) pairBuffer += '"';

    objectKey = objectKeyList[index];
    objectValue = object[objectKey];

    if (state.replacer) {
      objectValue = state.replacer.call(object, objectKey, objectValue);
    }

    if (!writeNode(state, level, objectKey, false, false)) {
      continue; // Skip this pair because of invalid key;
    }

    if (state.dump.length > 1024) pairBuffer += '? ';

    pairBuffer += state.dump + (state.condenseFlow ? '"' : '') + ':' + (state.condenseFlow ? '' : ' ');

    if (!writeNode(state, level, objectValue, false, false)) {
      continue; // Skip this pair because of invalid value.
    }

    pairBuffer += state.dump;

    // Both key and value are valid.
    _result += pairBuffer;
  }

  state.tag = _tag;
  state.dump = '{' + _result + '}';
}

function writeBlockMapping(state, level, object, compact) {
  var _result       = '',
      _tag          = state.tag,
      objectKeyList = Object.keys(object),
      index,
      length,
      objectKey,
      objectValue,
      explicitPair,
      pairBuffer;

  // Allow sorting keys so that the output file is deterministic
  if (state.sortKeys === true) {
    // Default sorting
    objectKeyList.sort();
  } else if (typeof state.sortKeys === 'function') {
    // Custom sort function
    objectKeyList.sort(state.sortKeys);
  } else if (state.sortKeys) {
    // Something is wrong
    throw new YAMLException('sortKeys must be a boolean or a function');
  }

  for (index = 0, length = objectKeyList.length; index < length; index += 1) {
    pairBuffer = '';

    if (!compact || _result !== '') {
      pairBuffer += generateNextLine(state, level);
    }

    objectKey = objectKeyList[index];
    objectValue = object[objectKey];

    if (state.replacer) {
      objectValue = state.replacer.call(object, objectKey, objectValue);
    }

    if (!writeNode(state, level + 1, objectKey, true, true, true)) {
      continue; // Skip this pair because of invalid key.
    }

    explicitPair = (state.tag !== null && state.tag !== '?') ||
                   (state.dump && state.dump.length > 1024);

    if (explicitPair) {
      if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {
        pairBuffer += '?';
      } else {
        pairBuffer += '? ';
      }
    }

    pairBuffer += state.dump;

    if (explicitPair) {
      pairBuffer += generateNextLine(state, level);
    }

    if (!writeNode(state, level + 1, objectValue, true, explicitPair)) {
      continue; // Skip this pair because of invalid value.
    }

    if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {
      pairBuffer += ':';
    } else {
      pairBuffer += ': ';
    }

    pairBuffer += state.dump;

    // Both key and value are valid.
    _result += pairBuffer;
  }

  state.tag = _tag;
  state.dump = _result || '{}'; // Empty mapping if no valid pairs.
}

function detectType(state, object, explicit) {
  var _result, typeList, index, length, type, style;

  typeList = explicit ? state.explicitTypes : state.implicitTypes;

  for (index = 0, length = typeList.length; index < length; index += 1) {
    type = typeList[index];

    if ((type.instanceOf  || type.predicate) &&
        (!type.instanceOf || ((typeof object === 'object') && (object instanceof type.instanceOf))) &&
        (!type.predicate  || type.predicate(object))) {

      if (explicit) {
        if (type.multi && type.representName) {
          state.tag = type.representName(object);
        } else {
          state.tag = type.tag;
        }
      } else {
        state.tag = '?';
      }

      if (type.represent) {
        style = state.styleMap[type.tag] || type.defaultStyle;

        if (_toString.call(type.represent) === '[object Function]') {
          _result = type.represent(object, style);
        } else if (_hasOwnProperty.call(type.represent, style)) {
          _result = type.represent[style](object, style);
        } else {
          throw new YAMLException('!<' + type.tag + '> tag resolver accepts not "' + style + '" style');
        }

        state.dump = _result;
      }

      return true;
    }
  }

  return false;
}

// Serializes `object` and writes it to global `result`.
// Returns true on success, or false on invalid object.
//
function writeNode(state, level, object, block, compact, iskey, isblockseq) {
  state.tag = null;
  state.dump = object;

  if (!detectType(state, object, false)) {
    detectType(state, object, true);
  }

  var type = _toString.call(state.dump);
  var inblock = block;
  var tagStr;

  if (block) {
    block = (state.flowLevel < 0 || state.flowLevel > level);
  }

  var objectOrArray = type === '[object Object]' || type === '[object Array]',
      duplicateIndex,
      duplicate;

  if (objectOrArray) {
    duplicateIndex = state.duplicates.indexOf(object);
    duplicate = duplicateIndex !== -1;
  }

  if ((state.tag !== null && state.tag !== '?') || duplicate || (state.indent !== 2 && level > 0)) {
    compact = false;
  }

  if (duplicate && state.usedDuplicates[duplicateIndex]) {
    state.dump = '*ref_' + duplicateIndex;
  } else {
    if (objectOrArray && duplicate && !state.usedDuplicates[duplicateIndex]) {
      state.usedDuplicates[duplicateIndex] = true;
    }
    if (type === '[object Object]') {
      if (block && (Object.keys(state.dump).length !== 0)) {
        writeBlockMapping(state, level, state.dump, compact);
        if (duplicate) {
          state.dump = '&ref_' + duplicateIndex + state.dump;
        }
      } else {
        writeFlowMapping(state, level, state.dump);
        if (duplicate) {
          state.dump = '&ref_' + duplicateIndex + ' ' + state.dump;
        }
      }
    } else if (type === '[object Array]') {
      if (block && (state.dump.length !== 0)) {
        if (state.noArrayIndent && !isblockseq && level > 0) {
          writeBlockSequence(state, level - 1, state.dump, compact);
        } else {
          writeBlockSequence(state, level, state.dump, compact);
        }
        if (duplicate) {
          state.dump = '&ref_' + duplicateIndex + state.dump;
        }
      } else {
        writeFlowSequence(state, level, state.dump);
        if (duplicate) {
          state.dump = '&ref_' + duplicateIndex + ' ' + state.dump;
        }
      }
    } else if (type === '[object String]') {
      if (state.tag !== '?') {
        writeScalar(state, state.dump, level, iskey, inblock);
      }
    } else if (type === '[object Undefined]') {
      return false;
    } else {
      if (state.skipInvalid) return false;
      throw new YAMLException('unacceptable kind of an object to dump ' + type);
    }

    if (state.tag !== null && state.tag !== '?') {
      // Need to encode all characters except those allowed by the spec:
      //
      // [35] ns-dec-digit    ::=  [#x30-#x39] /* 0-9 */
      // [36] ns-hex-digit    ::=  ns-dec-digit
      //                         | [#x41-#x46] /* A-F */ | [#x61-#x66] /* a-f */
      // [37] ns-ascii-letter ::=  [#x41-#x5A] /* A-Z */ | [#x61-#x7A] /* a-z */
      // [38] ns-word-char    ::=  ns-dec-digit | ns-ascii-letter | “-”
      // [39] ns-uri-char     ::=  “%” ns-hex-digit ns-hex-digit | ns-word-char | “#”
      //                         | “;” | “/” | “?” | “:” | “@” | “&” | “=” | “+” | “$” | “,”
      //                         | “_” | “.” | “!” | “~” | “*” | “'” | “(” | “)” | “[” | “]”
      //
      // Also need to encode '!' because it has special meaning (end of tag prefix).
      //
      tagStr = encodeURI(
        state.tag[0] === '!' ? state.tag.slice(1) : state.tag
      ).replace(/!/g, '%21');

      if (state.tag[0] === '!') {
        tagStr = '!' + tagStr;
      } else if (tagStr.slice(0, 18) === 'tag:yaml.org,2002:') {
        tagStr = '!!' + tagStr.slice(18);
      } else {
        tagStr = '!<' + tagStr + '>';
      }

      state.dump = tagStr + ' ' + state.dump;
    }
  }

  return true;
}

function getDuplicateReferences(object, state) {
  var objects = [],
      duplicatesIndexes = [],
      index,
      length;

  inspectNode(object, objects, duplicatesIndexes);

  for (index = 0, length = duplicatesIndexes.length; index < length; index += 1) {
    state.duplicates.push(objects[duplicatesIndexes[index]]);
  }
  state.usedDuplicates = new Array(length);
}

function inspectNode(object, objects, duplicatesIndexes) {
  var objectKeyList,
      index,
      length;

  if (object !== null && typeof object === 'object') {
    index = objects.indexOf(object);
    if (index !== -1) {
      if (duplicatesIndexes.indexOf(index) === -1) {
        duplicatesIndexes.push(index);
      }
    } else {
      objects.push(object);

      if (Array.isArray(object)) {
        for (index = 0, length = object.length; index < length; index += 1) {
          inspectNode(object[index], objects, duplicatesIndexes);
        }
      } else {
        objectKeyList = Object.keys(object);

        for (index = 0, length = objectKeyList.length; index < length; index += 1) {
          inspectNode(object[objectKeyList[index]], objects, duplicatesIndexes);
        }
      }
    }
  }
}

function dump(input, options) {
  options = options || {};

  var state = new State(options);

  if (!state.noRefs) getDuplicateReferences(input, state);

  var value = input;

  if (state.replacer) {
    value = state.replacer.call({ '': value }, '', value);
  }

  if (writeNode(state, 0, value, true, true)) return state.dump + '\n';

  return '';
}

module.exports.dump = dump;


path: node_modules/js-yaml/lib/type/null.js
content: 'use strict';

var Type = require('../type');

function resolveYamlNull(data) {
  if (data === null) return true;

  var max = data.length;

  return (max === 1 && data === '~') ||
         (max === 4 && (data === 'null' || data === 'Null' || data === 'NULL'));
}

function constructYamlNull() {
  return null;
}

function isNull(object) {
  return object === null;
}

module.exports = new Type('tag:yaml.org,2002:null', {
  kind: 'scalar',
  resolve: resolveYamlNull,
  construct: constructYamlNull,
  predicate: isNull,
  represent: {
    canonical: function () { return '~';    },
    lowercase: function () { return 'null'; },
    uppercase: function () { return 'NULL'; },
    camelcase: function () { return 'Null'; },
    empty:     function () { return '';     }
  },
  defaultStyle: 'lowercase'
});


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)