← All tasks
javascriptcodex/javascript-t1 #4Not a task: already works

YAML Config Merger (javascript, written by Codex)

envgap__codex__javascript-t1-4

Written by a coding agent; not on GitHubWritten 2026-03-02

01 / FAILURE SIGNATURE

As the study recorded it

None
Not a benchmark task.
  • The project already builds and runs before the fix, so there is nothing to repair.

02 / ENVIRONMENT RECIPE

Base commit
Not freshly verified
Manifest
package.json
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

codex/javascript-t1 #4 · read the task the agent was given
Codex wrote this javascript project from the task below. It installed and ran on a clean Ubuntu 22.04 machine as written.

Task given to the agent:

TASK: YAML Config Merger

Write a program that merges multiple YAML configuration files into a single unified configuration, supporting deep merging of nested structures, array handling strategies, and conflict resolution.

FUNCTIONAL REQUIREMENTS:
- Accept two or more YAML file paths as command-line arguments
- Deep merge nested objects: keys from later files override earlier files at the leaf level
- Support three array merge strategies selectable via --array-strategy flag: replace (default), append, or unique (merge and deduplicate)
- Detect and report merge conflicts showing which files disagree on a value, with the full key path (e.g., database.connection.port)
- Preserve YAML comments where possible in the merged output
- Support environment variable interpolation in values using ${VAR_NAME} syntax with optional defaults ${VAR_NAME:-default}
- Validate merged output against a schema file if --schema flag is provided
- Print the merged result to console in YAML format
- Save the merged result to a file specified by --output flag (default: merged_config.yaml)
- If no input files are given, generate three sample YAML config files with overlapping keys, nested structures, and arrays, then merge them
- Handle malformed YAML with clear error messages identifying the file and location of the problem

Create a complete JavaScript project for a clean Ubuntu 22.04 machine with only Node.js 20+ (LTS) installed. Include:
- Source code
- package.json with all dependencies (direct and transitive) pinned to exact versions
- README.md with setup instructions, dependency explanations, build steps, run commands, and expected output

04 / LABELS

Labels from the report text only; not yet run

No supported category has been assigned.

Label rules and the text that matched
[]

05 / FILES

The project as the agent wrote it

4 files, exactly as written, before any repair.

package-lock.json
{
  "name": "yaml-config-merger",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "yaml-config-merger",
      "version": "1.0.0"
    }
  }
}
package.json
{
  "name": "yaml-config-merger",
  "version": "1.0.0",
  "description": "Deep YAML config merger with conflict detection and schema validation.",
  "type": "module",
  "main": "src/index.js",
  "scripts": {
    "start": "node src/index.js",
    "merge": "node src/index.js"
  },
  "engines": {
    "node": ">=20.0.0"
  },
  "dependencies": {},
  "devDependencies": {}
}
README.md
# YAML Config Merger (JavaScript)

Merges multiple YAML configuration files with deep merge, array strategies, conflict reporting, env interpolation, optional schema validation, and YAML output.

## Features

- Accepts two or more YAML files.
- Deep merge objects (later files override leaf values).
- `--array-strategy`: `replace` (default), `append`, `unique`.
- Conflict reporting with key paths and conflicting files.
- Preserves YAML comments where possible.
- Environment interpolation:
  - `${VAR_NAME}`
  - `${VAR_NAME:-default}`
- Optional schema validation with `--schema`.
- Prints merged YAML to console.
- Saves merged YAML via `--output` (default: `merged_config.yaml`).
- Generates three sample YAML files when no input is provided.
- Malformed YAML errors include file and line.

## Requirements

- Ubuntu 22.04
- Node.js 20+

## Dependencies (Pinned)

No external dependencies are used.

- Direct dependencies: none
- Transitive dependencies: none

## Usage

```bash
cd TMLR/code_generation/codex_generated/p_04/javascript
node src/index.js config1.yaml config2.yaml config3.yaml
```

Array strategy:

```bash
node src/index.js --array-strategy unique config1.yaml config2.yaml
```

Schema validation:

```bash
node src/index.js --schema schema.yaml --output final.yaml config1.yaml config2.yaml
```

No args (generate samples + merge):

```bash
node src/index.js
```
src/index.js
#!/usr/bin/env node
import fs from "node:fs/promises";
import path from "node:path";

class YamlParseError extends Error {
  constructor(message, line) {
    super(`${message} at line ${line}`);
    this.name = "YamlParseError";
    this.line = line;
  }
}

function deepClone(value) {
  if (Array.isArray(value)) return value.map((item) => deepClone(item));
  if (value && typeof value === "object") {
    const out = {};
    for (const [k, v] of Object.entries(value)) out[k] = deepClone(v);
    return out;
  }
  return value;
}

function deepEqual(a, b) {
  if (a === b) return true;
  if (typeof a !== typeof b) return false;
  if (a === null || b === null) return a === b;
  if (Array.isArray(a) !== Array.isArray(b)) return false;
  if (Array.isArray(a)) {
    if (a.length !== b.length) return false;
    for (let i = 0; i < a.length; i += 1) if (!deepEqual(a[i], b[i])) return false;
    return true;
  }
  if (typeof a === "object") {
    const ak = Object.keys(a);
    const bk = Object.keys(b);
    if (ak.length !== bk.length) return false;
    for (const key of ak) {
      if (!Object.hasOwn(b, key) || !deepEqual(a[key], b[key])) return false;
    }
    return true;
  }
  return false;
}

function scalarFromText(text) {
  if (text === "null" || text === "~") return null;
  if (text === "true") return true;
  if (text === "false") return false;
  if (/^-?\d+(\.\d+)?$/.test(text)) return Number(text);
  if (
    (text.startsWith('"') && text.endsWith('"')) ||
    (text.startsWith("'") && text.endsWith("'"))
  ) {
    return text.slice(1, -1);
  }
  return text;
}

function yamlScalar(value) {
  if (value === null) return "null";
  if (typeof value === "boolean") return value ? "true" : "false";
  if (typeof value === "number") return Number.isFinite(value) ? String(value) : "null";
  if (typeof value === "string") {
    if (value === "") return '""';
    if (/[:#\-\[\]\{\},]|^\s|\s$|\n/.test(value)) {
      return JSON.stringify(value);
    }
    return value;
  }
  return JSON.stringify(value);
}

function parseYaml(text) {
  const lines = text.replace(/\r\n?/g, "\n").split("\n");
  const comments = {};

  function lineInfo(index) {
    const raw = lines[index] ?? "";
    const indent = raw.match(/^ */)[0].length;
    const trimmed = raw.trim();
    return { raw, indent, trimmed, lineNo: index + 1 };
  }

  function parseBlock(start, indent, pathPrefix) {
    let index = start;
    const pendingComments = [];

    while (index < lines.length) {
      const info = lineInfo(index);
      if (info.trimmed === "") {
        index += 1;
        continue;
      }
      if (info.indent < indent) break;
      if (info.indent > indent) {
        throw new YamlParseError("Unexpected indentation", info.lineNo);
      }
      if (info.trimmed.startsWith("#")) {
        pendingComments.push(info.trimmed.slice(1).trim());
        index += 1;
        continue;
      }
      break;
    }

    if (index >= lines.length) return { value: null, next: index };

    const first = lineInfo(index);
    if (first.indent !== indent) return { value: null, next: index };
    const isSeq = first.trimmed.startsWith("- ");

    if (isSeq) {
      const arr = [];
      while (index < lines.length) {
        const info = lineInfo(index);
        if (info.trimmed === "" || info.trimmed.startsWith("#")) {
          index += 1;
          continue;
        }
        if (info.indent < indent) break;
        if (info.indent > indent) throw new YamlParseError("Unexpected indentation", info.lineNo);
        if (!info.trimmed.startsWith("- ")) break;

        const rest = info.trimmed.slice(2).trim();
        if (rest === "") {
          const nested = parseBlock(index + 1, indent + 2, `${pathPrefix}[${arr.length}]`);
          arr.push(nested.value);
          index = nested.next;
        } else if (/^[^:#]+:\s*/.test(rest)) {
          const split = rest.indexOf(":");
          const key = rest.slice(0, split).trim();
          const valueText = rest.slice(split + 1).trim();
          const item = {};
          if (valueText === "") {
            const nested = parseBlock(index + 1, indent + 4, `${pathPrefix}[${arr.length}].${key}`);
            item[key] = nested.value;
            arr.push(item);
            index = nested.next;
          } else {
            item[key] = scalarFromText(valueText);
            arr.push(item);
            index += 1;
          }
        } else {
          arr.push(scalarFromText(rest));
          index += 1;
        }
      }
      return { value: arr, next: index };
    }

    const obj = {};
    let localComments = pendingComments;
    while (index < lines.length) {
      const info = lineInfo(index);
      if (info.trimmed === "") {
        index += 1;
        continue;
      }
      if (info.indent < indent) break;
      if (info.indent > indent) throw new YamlParseError("Unexpected indentation", info.lineNo);

      if (info.trimmed.startsWith("#")) {
        localComments.push(info.trimmed.slice(1).trim());
        index += 1;
        continue;
      }

      const split = info.trimmed.indexOf(":");
      if (split < 1) throw new YamlParseError("Invalid mapping entry", info.lineNo);
      const key = info.trimmed.slice(0, split).trim();
      const rest = info.trimmed.slice(split + 1).trim();
      const fullPath = pathPrefix ? `${pathPrefix}.${key}` : key;
      if (localComments.length > 0) {
        comments[fullPath] = [...localComments];
        localComments = [];
      }

      if (rest === "") {
        const nested = parseBlock(index + 1, indent + 2, fullPath);
        obj[key] = nested.value;
        index = nested.next;
      } else {
        const inline = rest.split(/\s+#/, 1)[0].trim();
        obj[key] = scalarFromText(inline);
        index += 1;
      }
    }
    return { value: obj, next: index };
  }

  const parsed = parseBlock(0, 0, "");
  return { data: parsed.value ?? {}, comments };
}

function interpolateEnv(value) {
  if (typeof value === "string") {
    return value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)(:-([^}]*))?\}/g, (_, key, _whole, def) => {
      if (process.env[key] !== undefined) return process.env[key];
      return def ?? "";
    });
  }
  if (Array.isArray(value)) return value.map(interpolateEnv);
  if (value && typeof value === "object") {
    const out = {};
    for (const [k, v] of Object.entries(value)) out[k] = interpolateEnv(v);
    return out;
  }
  return value;
}

function collectLeafPaths(value, pathPrefix, out) {
  if (Array.isArray(value) || value === null || typeof value !== "object") {
    out[pathPrefix] = value;
    return;
  }
  for (const [k, v] of Object.entries(value)) {
    const path = pathPrefix ? `${pathPrefix}.${k}` : k;
    collectLeafPaths(v, path, out);
  }
}

function mergeValues(base, incoming, pathText, strategy, conflicts, sourceMap, currentFile) {
  const baseObj = base && typeof base === "object" && !Array.isArray(base);
  const incObj = incoming && typeof incoming === "object" && !Array.isArray(incoming);

  if (baseObj && incObj) {
    const merged = { ...base };
    for (const [key, val] of Object.entries(incoming)) {
      const childPath = pathText ? `${pathText}.${key}` : key;
      if (Object.hasOwn(merged, key)) {
        merged[key] = mergeValues(
          merged[key],
          val,
          childPath,
          strategy,
          conflicts,
          sourceMap,
          currentFile,
        );
      } else {
        merged[key] = deepClone(val);
        sourceMap[childPath] = currentFile;
      }
    }
    return merged;
  }

  if (Array.isArray(base) && Array.isArray(incoming)) {
    let merged;
    if (strategy === "append") {
      merged = [...base.map(deepClone), ...incoming.map(deepClone)];
    } else if (strategy === "unique") {
      merged = [...base.map(deepClone)];
      for (const item of incoming) {
        if (!merged.some((existing) => deepEqual(existing, item))) merged.push(deepClone(item));
      }
    } else {
      merged = incoming.map(deepClone);
    }
    if (!deepEqual(base, incoming)) {
      conflicts.push({
        path: pathText,
        previousFile: sourceMap[pathText] ?? "unknown",
        currentFile,
        previousValue: base,
        currentValue: incoming,
      });
    }
    sourceMap[pathText] = currentFile;
    return merged;
  }

  if (!deepEqual(base, incoming)) {
    conflicts.push({
      path: pathText,
      previousFile: sourceMap[pathText] ?? "unknown",
      currentFile,
      previousValue: base,
      currentValue: incoming,
    });
  }
  sourceMap[pathText] = currentFile;
  return deepClone(incoming);
}

function validateSchema(data, schema, pathText, errors) {
  if (!schema || typeof schema !== "object" || Array.isArray(schema)) return;
  const type = schema.type;
  const actualType =
    data === null
      ? "null"
      : Array.isArray(data)
        ? "array"
        : typeof data === "number"
          ? "number"
          : typeof data === "boolean"
            ? "boolean"
            : typeof data === "string"
              ? "string"
              : typeof data === "object"
                ? "object"
                : "unknown";

  if (typeof type === "string" && actualType !== type) {
    errors.push(`${pathText || "$"}: expected type ${type}, got ${actualType}`);
    return;
  }

  if (Array.isArray(schema.enum) && !schema.enum.some((v) => deepEqual(v, data))) {
    errors.push(`${pathText || "$"}: value not in enum`);
  }

  if (actualType === "object") {
    if (Array.isArray(schema.required)) {
      for (const req of schema.required) {
        if (!Object.hasOwn(data, req)) errors.push(`${pathText || "$"}: missing required key ${req}`);
      }
    }
    if (schema.properties && typeof schema.properties === "object") {
      for (const [k, sub] of Object.entries(schema.properties)) {
        if (Object.hasOwn(data, k)) validateSchema(data[k], sub, pathText ? `${pathText}.${k}` : k, errors);
      }
    }
  } else if (actualType === "array") {
    if (schema.items) {
      data.forEach((item, idx) => validateSchema(item, schema.items, `${pathText}[${idx}]`, errors));
    }
  }
}

function toYaml(value, commentsMap, pathText = "", indent = 0) {
  const pad = " ".repeat(indent);
  const lines = [];

  if (Array.isArray(value)) {
    for (let i = 0; i < value.length; i += 1) {
      const item = value[i];
      if (item && typeof item === "object" && !Array.isArray(item)) {
        lines.push(`${pad}-`);
        lines.push(toYaml(item, commentsMap, `${pathText}[${i}]`, indent + 2));
      } else if (Array.isArray(item)) {
        lines.push(`${pad}-`);
        lines.push(toYaml(item, commentsMap, `${pathText}[${i}]`, indent + 2));
      } else {
        lines.push(`${pad}- ${yamlScalar(item)}`);
      }
    }
    return lines.filter(Boolean).join("\n");
  }

  if (value && typeof value === "object") {
    for (const [key, val] of Object.entries(value)) {
      const childPath = pathText ? `${pathText}.${key}` : key;
      if (commentsMap[childPath]) {
        for (const comment of commentsMap[childPath]) lines.push(`${pad}# ${comment}`);
      }
      if (val && typeof val === "object") {
        lines.push(`${pad}${key}:`);
        lines.push(toYaml(val, commentsMap, childPath, indent + 2));
      } else {
        lines.push(`${pad}${key}: ${yamlScalar(val)}`);
      }
    }
    return lines.filter(Boolean).join("\n");
  }

  return `${pad}${yamlScalar(value)}`;
}

async function loadYamlFile(filePath) {
  let text;
  try {
    text = await fs.readFile(filePath, "utf8");
  } catch (error) {
    throw new Error(`Failed to read ${filePath}: ${error.message}`);
  }
  try {
    return parseYaml(text);
  } catch (error) {
    if (error instanceof YamlParseError) {
      throw new Error(`Malformed YAML in ${filePath}: ${error.message}`);
    }
    throw error;
  }
}

function sampleConfigFiles() {
  return {
    "config_1.yaml": `# Base app config
app:
  name: merger-demo
  env: \${APP_ENV:-dev}
database:
  host: localhost
  port: 5432
  tags:
    - core
    - primary
features:
  enabled:
    - auth
    - api
`,
    "config_2.yaml": `# Override database and features
database:
  port: 5433
  user: \${DB_USER:-admin}
  tags:
    - analytics
features:
  enabled:
    - api
    - billing
`,
    "config_3.yaml": `# Production tuning
app:
  env: prod
database:
  host: db.internal
  retries: 5
features:
  enabled:
    - auth
    - billing
`,
  };
}

async function run() {
  const args = process.argv.slice(2);
  const inputFiles = [];
  let arrayStrategy = "replace";
  let schemaPath = null;
  let outputPath = "merged_config.yaml";

  for (let i = 0; i < args.length; i += 1) {
    const arg = args[i];
    if (arg === "--array-strategy") {
      i += 1;
      arrayStrategy = args[i] ?? "";
    } else if (arg === "--schema") {
      i += 1;
      schemaPath = args[i] ?? null;
    } else if (arg === "--output") {
      i += 1;
      outputPath = args[i] ?? outputPath;
    } else {
      inputFiles.push(arg);
    }
  }

  if (!["replace", "append", "unique"].includes(arrayStrategy)) {
    console.error("Invalid --array-strategy. Use replace, append, or unique.");
    process.exitCode = 1;
    return;
  }

  let files = [...inputFiles];
  if (files.length === 0) {
    const samples = sampleConfigFiles();
    for (const [name, content] of Object.entries(samples)) {
      const abs = path.resolve(process.cwd(), name);
      await fs.writeFile(abs, content, "utf8");
      files.push(abs);
    }
    console.log(`No input files provided. Generated sample files: ${files.join(", ")}`);
  }

  if (files.length < 2) {
    console.error("Provide at least two YAML files.");
    process.exitCode = 1;
    return;
  }

  const parsedFiles = [];
  for (const file of files) {
    const abs = path.resolve(process.cwd(), file);
    const parsed = await loadYamlFile(abs);
    parsedFiles.push({ file: abs, ...parsed });
  }

  let merged = deepClone(parsedFiles[0].data);
  const sourceMap = {};
  collectLeafPaths(merged, "", sourceMap);
  for (const key of Object.keys(sourceMap)) sourceMap[key] = parsedFiles[0].file;

  const mergedComments = { ...parsedFiles[0].comments };
  const conflicts = [];

  for (let i = 1; i < parsedFiles.length; i += 1) {
    const next = parsedFiles[i];
    merged = mergeValues(merged, next.data, "", arrayStrategy, conflicts, sourceMap, next.file);
    for (const [key, value] of Object.entries(next.comments)) {
      if (!Object.hasOwn(mergedComments, key)) mergedComments[key] = value;
    }
  }

  merged = interpolateEnv(merged);

  if (schemaPath) {
    const schemaAbs = path.resolve(process.cwd(), schemaPath);
    const parsedSchema = await loadYamlFile(schemaAbs);
    const schemaErrors = [];
    validateSchema(merged, parsedSchema.data, "", schemaErrors);
    if (schemaErrors.length > 0) {
      console.error("Schema validation failed:");
      schemaErrors.forEach((error) => console.error(`- ${error}`));
      process.exitCode = 1;
      return;
    }
  }

  if (conflicts.length > 0) {
    console.log("Merge conflicts detected:");
    for (const conflict of conflicts) {
      const displayPath = conflict.path || "<root>";
      console.log(
        `- ${displayPath}: ${conflict.previousFile} disagrees with ${conflict.currentFile}`,
      );
    }
  } else {
    console.log("No merge conflicts detected.");
  }

  const yamlOutput = toYaml(merged, mergedComments);
  console.log("\nMerged YAML:\n");
  console.log(yamlOutput);

  const outputAbs = path.resolve(process.cwd(), outputPath);
  await fs.writeFile(outputAbs, `${yamlOutput}\n`, "utf8");
  console.log(`\nSaved merged config: ${outputAbs}`);
}

run().catch((error) => {
  console.error(error.message);
  process.exitCode = 1;
});