diff --git a/.github/workflows/improve-docs.yml b/.github/workflows/improve-docs.yml new file mode 100644 index 0000000000..358330edce --- /dev/null +++ b/.github/workflows/improve-docs.yml @@ -0,0 +1,97 @@ +# ============================================================================ +# Improve Documentation (AI-driven) +# ============================================================================ +# Runs daily (or on demand). Pulls the GDevelop repo, reads what has already +# been improved in automated_updates_data.json, and uses an AI agent to +# improve a DIFFERENT aspect of the documentation each run. +# +# To switch from Claude Code to Codex: +# 1. Change AI_PROVIDER to "codex" +# 2. Comment out the "Install Claude Code" step and uncomment "Install Codex" +# 3. Swap the API key secrets (comment ANTHROPIC_API_KEY, uncomment OPENAI_API_KEY) +# ============================================================================ + +name: "Auto: Improve documentation" + +on: + schedule: + - cron: "0 10 * * *" # Every day at 10:00 UTC (offset from the other workflow) + workflow_dispatch: # Manual trigger + pull_request: # For testing in PRs + +permissions: + contents: write + pull-requests: write + +jobs: + improve-docs: + runs-on: ubuntu-latest + if: github.repository == 'GDevelopApp/GDevelop-documentation' + timeout-minutes: 20 + + steps: + # ── Checkout this documentation repo ────────────────────────────── + - name: Checkout documentation repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # ── Node.js ─────────────────────────────────────────────────────── + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + + # ── Install AI CLI ──────────────────────────────────────────────── + - name: Install Claude Code + run: npm install -g @anthropic-ai/claude-code + + # Uncomment below (and comment above) to use Codex instead: + # - name: Install Codex + # run: npm install -g @openai/codex + + # ── Run the improvement script ──────────────────────────────────── + - name: Improve documentation + id: improve + env: + AI_PROVIDER: "claude" # Change to "codex" to use Codex + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + # OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + run: | + node scripts/improve-docs.js + + # Pass the AI summary to subsequent steps + if [ -f /tmp/ai_summary.txt ]; then + delimiter="EOF_$(openssl rand -hex 8)" + { + echo "summary<<${delimiter}" + cat /tmp/ai_summary.txt + echo "" + echo "${delimiter}" + } >> "$GITHUB_OUTPUT" + fi + + # ── Create Pull Request ─────────────────────────────────────────── + - name: Create Pull Request + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "docs: AI-driven documentation improvement" + title: "Auto: Improve documentation" + body: | + This PR was automatically generated by the **Improve documentation** workflow. + + An AI coding agent inspected the [GDevelop](https://github.com/4ian/GDevelop) codebase + and the existing documentation, then chose an aspect to improve. + + The AI agent summary is: + > ${{ steps.improve.outputs.summary }} + + The updated `automated_updates_data.json` tracks what was improved so future + runs will pick a different area. + + **Please review the changes carefully before merging.** + base: main + branch: auto/improve-docs + delete-branch: true + labels: automated,documentation diff --git a/.github/workflows/update-docs-from-commits.yml b/.github/workflows/update-docs-from-commits.yml new file mode 100644 index 0000000000..bc8ae3ae63 --- /dev/null +++ b/.github/workflows/update-docs-from-commits.yml @@ -0,0 +1,108 @@ +# ============================================================================ +# Update Documentation from GDevelop Commits +# ============================================================================ +# Runs daily (or on demand). Pulls the GDevelop repo, inspects recent commits +# since the last tracked commit, and uses an AI agent (Claude Code by default, +# Codex as an alternative) to update the docs accordingly. +# +# To switch from Claude Code to Codex: +# 1. Change AI_PROVIDER to "codex" +# 2. Comment out the "Install Claude Code" step and uncomment "Install Codex" +# 3. Swap the API key secrets (comment ANTHROPIC_API_KEY, uncomment OPENAI_API_KEY) +# ============================================================================ + +name: "Auto: Update docs from GDevelop commits" + +on: + schedule: + - cron: "0 8 * * *" # Every day at 08:00 UTC + workflow_dispatch: # Manual trigger + pull_request: # For testing in PRs + +permissions: + contents: write + pull-requests: write + +jobs: + update-docs: + runs-on: ubuntu-latest + if: github.repository == 'GDevelopApp/GDevelop-documentation' + timeout-minutes: 20 + + steps: + # ── Checkout this documentation repo ────────────────────────────── + - name: Checkout documentation repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # ── Node.js ─────────────────────────────────────────────────────── + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + + # ── Install AI CLI ──────────────────────────────────────────────── + - name: Install Claude Code + run: npm install -g @anthropic-ai/claude-code + + # Uncomment below (and comment above) to use Codex instead: + # - name: Install Codex + # run: npm install -g @openai/codex + + # ── Run the update script ───────────────────────────────────────── + - name: Update documentation from recent commits + id: update + env: + AI_PROVIDER: "claude" # Change to "codex" to use Codex + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + # OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + run: | + node scripts/update-docs-from-commits.js + + # Pass the AI summary and commit log to subsequent steps + if [ -f /tmp/ai_summary.txt ]; then + delimiter="EOF_$(openssl rand -hex 8)" + { + echo "summary<<${delimiter}" + cat /tmp/ai_summary.txt + echo "" + echo "${delimiter}" + } >> "$GITHUB_OUTPUT" + fi + if [ -f /tmp/commit_log.txt ]; then + delimiter="EOF_$(openssl rand -hex 8)" + { + echo "commits<<${delimiter}" + cat /tmp/commit_log.txt + echo "" + echo "${delimiter}" + } >> "$GITHUB_OUTPUT" + fi + + # ── Create Pull Request ─────────────────────────────────────────── + - name: Create Pull Request + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "docs: auto-update documentation from GDevelop commits" + title: "Auto: Update documentation based on recent GDevelop changes" + body: | + This PR was automatically generated by the **Update docs from GDevelop commits** workflow. + + An AI coding agent analysed recent commits in [4ian/GDevelop](https://github.com/4ian/GDevelop) + and updated the documentation to reflect user-facing changes. + + ### GDevelop commits covered + ``` + ${{ steps.update.outputs.commits }} + ``` + + ### The AI agent summary is: + ${{ steps.update.outputs.summary }} + + **Please review the changes carefully before merging.** + base: main + branch: auto/update-docs-from-commits + delete-branch: true + labels: automated,documentation diff --git a/.gitignore b/.gitignore index 6962a76bc7..61473a0642 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ dokuwiki2wikijs/__pycache__ .DS_Store -site \ No newline at end of file +site +scripts/node_modules/ \ No newline at end of file diff --git a/automated_updates_data.json b/automated_updates_data.json new file mode 100644 index 0000000000..1d4e40b778 --- /dev/null +++ b/automated_updates_data.json @@ -0,0 +1,6 @@ +{ + "last_automated_updates_commit": null, + "last_improved_things": [ + + ] +} diff --git a/scripts/improve-docs.js b/scripts/improve-docs.js new file mode 100644 index 0000000000..b2b65d4934 --- /dev/null +++ b/scripts/improve-docs.js @@ -0,0 +1,225 @@ +#!/usr/bin/env node + +/** + * improve-docs.js + * + * Clones https://github.com/4ian/GDevelop, inspects the codebase, reads + * automated_updates_data.json to see what was already improved in the past, + * and then invokes an AI coding agent (Claude Code or Codex) to improve a + * DIFFERENT aspect of the documentation (its own choice of scope — a folder, + * feature, topic, etc.). + * + * The AI agent directly adds a new entry to the "last_improved_things" array + * inside automated_updates_data.json, so the change is tracked in the same + * commit as the documentation edits. + */ + +const { execSync } = require("child_process"); +const fs = require("fs"); +const path = require("path"); + +// --------------------------------------------------------------------------- +// Configuration +// --------------------------------------------------------------------------- +const GDEVELOP_REPO = "https://github.com/4ian/GDevelop.git"; +const GDEVELOP_DIR = process.env.GDEVELOP_DIR || "/tmp/GDevelop"; +const REPO_ROOT = path.resolve(__dirname, ".."); +const DATA_FILE = path.join(REPO_ROOT, "automated_updates_data.json"); +const SUMMARY_OUT = "/tmp/ai_summary.txt"; + +// AI provider — switch by changing this value (or set AI_PROVIDER env var). +// Supported values: "claude" | "codex" +const AI_PROVIDER = process.env.AI_PROVIDER || "claude"; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- +function run(cmd, opts = {}) { + return execSync(cmd, { encoding: "utf8", ...opts }).trim(); +} + +function writeDataFile(data) { + let json = JSON.stringify(data, null, 2); + // Keep empty arrays on separate lines so future additions produce clean diffs + json = json.replace(/"last_improved_things": \[\s*\]/g, + '"last_improved_things": [\n\n ]'); + fs.writeFileSync(DATA_FILE, json + "\n"); +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- +function main() { + // 1. Read tracking data + let data = { last_automated_updates_commit: null, last_improved_things: [] }; + if (fs.existsSync(DATA_FILE)) { + data = { ...data, ...JSON.parse(fs.readFileSync(DATA_FILE, "utf8")) }; + } + + const alreadyImproved = data.last_improved_things || []; + const previousCount = alreadyImproved.length; + + // 2. Clone / update GDevelop + if (!fs.existsSync(path.join(GDEVELOP_DIR, ".git"))) { + console.log("Cloning GDevelop…"); + execSync( + `git clone --filter=blob:none --no-checkout ${GDEVELOP_REPO} ${GDEVELOP_DIR}`, + { stdio: "inherit" } + ); + execSync("git checkout", { cwd: GDEVELOP_DIR, stdio: "inherit" }); + } else { + console.log("Updating existing GDevelop clone…"); + execSync("git fetch origin && git reset --hard origin/master", { + cwd: GDEVELOP_DIR, + stdio: "inherit", + }); + } + + // 3. Build the list of docs pages (gives the AI a map of what exists) + const docTree = run( + `find docs/gdevelop5 -name '*.md' -not -path '*/extensions/*' -not -name 'reference.md' -not -name 'expressions-reference.md' | sort`, + { cwd: REPO_ROOT } + ); + + // 4. Build prompt + const prompt = buildPrompt(alreadyImproved, docTree); + + const promptFile = "/tmp/doc_improve_prompt.txt"; + fs.writeFileSync(promptFile, prompt); + console.log(`Prompt written to ${promptFile} (${prompt.length} chars)`); + + // 5. Invoke AI agent + console.log(`\nInvoking AI agent (${AI_PROVIDER})…\n`); + const aiOutput = invokeAI(promptFile, REPO_ROOT); + + // 6. Read back the data file to find what the AI added + let updatedData = data; + try { + updatedData = JSON.parse(fs.readFileSync(DATA_FILE, "utf8")); + } catch (err) { + console.error("Warning: could not re-read automated_updates_data.json:", err.message); + } + + const updatedList = updatedData.last_improved_things || []; + let summary; + + if (updatedList.length > previousCount) { + // The AI added one or more entries — take the last one + const newEntry = updatedList[updatedList.length - 1]; + summary = newEntry.summary || "(no summary in new entry)"; + console.log(`\nAI added improvement entry: [${newEntry.date}] ${summary}`); + } else { + // AI didn't update the file — add a fallback entry ourselves + summary = aiOutput + ? aiOutput.split("\n").filter(Boolean).slice(0, 3).join(" ").substring(0, 200) + : "(no summary provided)"; + updatedData.last_improved_things = updatedList; + updatedData.last_improved_things.push({ + date: new Date().toISOString().split("T")[0], + summary, + }); + writeDataFile(updatedData); + console.log(`\nFallback: script added improvement entry: ${summary}`); + } + + // 7. Write summary for the workflow to pick up + fs.writeFileSync(SUMMARY_OUT, summary); + console.log(`Summary written to ${SUMMARY_OUT}`); +} + +// --------------------------------------------------------------------------- +// Prompt builder +// --------------------------------------------------------------------------- +function buildPrompt(alreadyImproved, docTree) { + const previousList = + alreadyImproved.length > 0 + ? alreadyImproved.map((e) => ` • [${e.date}] ${e.summary}`).join("\n") + : " (none yet — this is the first run)"; + + const today = new Date().toISOString().split("T")[0]; + + return `You are an expert technical writer improving GDevelop's documentation. + +DOCUMENTATION LOCATION +---------------------- +The documentation you must improve is in: ${REPO_ROOT}/docs/gdevelop5/ +The GDevelop engine source code is at: ${GDEVELOP_DIR}/ (use it for reference). + +EXISTING DOCUMENTATION PAGES (excluding auto-generated files) +------------------------------------------------------------- +${docTree} + +PREVIOUSLY IMPROVED ASPECTS (do NOT repeat these) +-------------------------------------------------- +${previousList} + +YOUR TASK +--------- +1. Choose ONE specific aspect of the documentation to improve. Pick a scope + that has NOT been improved before (see the list above). Your scope can be: + – A specific folder (e.g., docs/gdevelop5/objects/) + – A specific feature (e.g., physics, publishing, variables) + – A specific topic (e.g., getting started tutorials, event system) +2. Read the relevant documentation pages AND the GDevelop source code to + understand the current state. +3. Make **concise** improvements: fix inaccuracies, add missing information + found in the source code, improve clarity, or fill gaps. +4. Do NOT rewrite entire pages — make targeted, valuable edits. +5. Preserve the existing writing style and Markdown formatting. + +IMPORTANT CONSTRAINTS +--------------------- +• NEVER edit auto-generated files. These include: + – Every file under docs/gdevelop5/extensions/ + – Every "reference.md" file under docs/gdevelop5/all-features/*/reference.md + – docs/gdevelop5/all-features/expressions-reference.md +• NEVER create new files unless absolutely necessary. +• NEVER edit files outside docs/gdevelop5/ (except automated_updates_data.json as described below). +• Do NOT touch images or binary files. + +WHEN YOU ARE DONE +----------------- +Edit the file "automated_updates_data.json" in ${REPO_ROOT}/ and add a new +entry at the END of the "last_improved_things" array with this format: + + { "date": "${today}", "summary": "" } + +For example, the file might look like: + { + "last_automated_updates_commit": null, + "last_improved_things": [ + { "date": "${today}", "summary": "Improved objects/sprite/index.md — clarified animation looping behaviour" } + ] + } + +Make your changes now.`; +} + +// --------------------------------------------------------------------------- +// AI invocation +// --------------------------------------------------------------------------- +function invokeAI(promptFile, cwd) { + let cmd; + const opts = { cwd, timeout: 10 * 60 * 1000 }; + + if (AI_PROVIDER === "claude") { + // ── Claude Code ────────────────────────────────────────────────────── + cmd = `cat "${promptFile}" | claude -p --dangerously-skip-permissions`; + // ── To use Codex instead, comment the block above and uncomment below. + } else if (AI_PROVIDER === "codex") { + // ── OpenAI Codex ───────────────────────────────────────────────────── + cmd = `codex --full-auto -q "$(cat '${promptFile}')"`; + opts.shell = "/bin/bash"; + } else { + throw new Error(`Unknown AI_PROVIDER: "${AI_PROVIDER}". Use "claude" or "codex".`); + } + + const output = execSync(cmd, { ...opts, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }); + console.log("── AI agent output ─────────────────────────────────────────"); + console.log(output); + console.log("── End of AI agent output ──────────────────────────────────"); + return output; +} + +// --------------------------------------------------------------------------- +main(); diff --git a/scripts/package.json b/scripts/package.json new file mode 100644 index 0000000000..b481cdc20a --- /dev/null +++ b/scripts/package.json @@ -0,0 +1,10 @@ +{ + "name": "gdevelop-docs-automation-scripts", + "version": "1.0.0", + "private": true, + "description": "Scripts for automated GDevelop documentation updates using AI coding agents", + "scripts": { + "update-docs": "node update-docs-from-commits.js", + "improve-docs": "node improve-docs.js" + } +} diff --git a/scripts/update-docs-from-commits.js b/scripts/update-docs-from-commits.js new file mode 100644 index 0000000000..8aad604768 --- /dev/null +++ b/scripts/update-docs-from-commits.js @@ -0,0 +1,250 @@ +#!/usr/bin/env node + +/** + * update-docs-from-commits.js + * + * Clones https://github.com/4ian/GDevelop, inspects recent commits + * (since the last tracked commit, or the last 5 by default), and + * invokes an AI coding agent (Claude Code or Codex) to update the + * GDevelop documentation in docs/gdevelop5/ accordingly. + * + * State is persisted in automated_updates_data.json at the repo root. + */ + +const { execSync } = require("child_process"); +const fs = require("fs"); +const path = require("path"); + +// --------------------------------------------------------------------------- +// Configuration +// --------------------------------------------------------------------------- +const GDEVELOP_REPO = "https://github.com/4ian/GDevelop.git"; +const GDEVELOP_DIR = process.env.GDEVELOP_DIR || "/tmp/GDevelop"; +const REPO_ROOT = path.resolve(__dirname, ".."); +const DATA_FILE = path.join(REPO_ROOT, "automated_updates_data.json"); +const DEFAULT_COMMIT_COUNT = 5; +const MAX_DIFF_BYTES = 80000; // Truncate diffs to keep prompt manageable +const SUMMARY_OUT = "/tmp/ai_summary.txt"; +const COMMITS_OUT = "/tmp/commit_log.txt"; + +// AI provider — switch by changing this value (or set AI_PROVIDER env var). +// Supported values: "claude" | "codex" +const AI_PROVIDER = process.env.AI_PROVIDER || "claude"; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- +function run(cmd, opts = {}) { + return execSync(cmd, { encoding: "utf8", ...opts }).trim(); +} + +function runSafe(cmd, opts = {}) { + try { + return run(cmd, opts); + } catch { + return null; + } +} + +function writeDataFile(data) { + let json = JSON.stringify(data, null, 2); + // Keep empty arrays on separate lines so future additions produce clean diffs + json = json.replace(/"last_improved_things": \[\s*\]/g, + '"last_improved_things": [\n\n ]'); + fs.writeFileSync(DATA_FILE, json + "\n"); +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- +function main() { + // 1. Read tracking data + let data = { last_automated_updates_commit: null, last_improved_things: [] }; + if (fs.existsSync(DATA_FILE)) { + data = { ...data, ...JSON.parse(fs.readFileSync(DATA_FILE, "utf8")) }; + } + + // 2. Clone / update GDevelop + if (!fs.existsSync(path.join(GDEVELOP_DIR, ".git"))) { + console.log("Cloning GDevelop…"); + execSync( + `git clone --filter=blob:none --no-checkout ${GDEVELOP_REPO} ${GDEVELOP_DIR}`, + { stdio: "inherit" } + ); + execSync("git checkout", { cwd: GDEVELOP_DIR, stdio: "inherit" }); + } else { + console.log("Updating existing GDevelop clone…"); + execSync("git fetch origin && git reset --hard origin/master", { + cwd: GDEVELOP_DIR, + stdio: "inherit", + }); + } + + // 3. Determine commit range + const lastCommit = data.last_automated_updates_commit; + let commitRange = null; + + if (lastCommit) { + const commitExists = (() => { + try { + execSync(`git cat-file -e ${lastCommit}`, { + cwd: GDEVELOP_DIR, + stdio: "pipe", + }); + return true; + } catch { + return false; + } + })(); + if (commitExists) { + commitRange = `${lastCommit}..HEAD`; + } + } + + // Fallback: last N commits + if (!commitRange) { + commitRange = `HEAD~${DEFAULT_COMMIT_COUNT}..HEAD`; + } + + // 4. Gather commit info + const commitLog = runSafe( + `git log ${commitRange} --oneline --no-merges`, + { cwd: GDEVELOP_DIR } + ); + + if (!commitLog) { + console.log("No new commits found. Nothing to do."); + data.last_automated_updates_commit = run("git rev-parse HEAD", { + cwd: GDEVELOP_DIR, + }); + writeDataFile(data); + fs.writeFileSync(SUMMARY_OUT, "(no new commits to process)"); + fs.writeFileSync(COMMITS_OUT, "(none)"); + return; + } + + const diffStat = runSafe(`git diff ${commitRange} --stat`, { + cwd: GDEVELOP_DIR, + }); + + const diffContent = runSafe( + `git diff ${commitRange} -- '*.js' '*.jsx' '*.ts' '*.tsx' '*.json' '*.md' ` + + `':!*/__tests__/*' ':!*/node_modules/*' ':!*/fixtures/*' ` + + `| head -c ${MAX_DIFF_BYTES}`, + { cwd: GDEVELOP_DIR } + ); + + const latestCommit = run("git rev-parse HEAD", { cwd: GDEVELOP_DIR }); + + console.log(`Commit range : ${commitRange}`); + console.log(`Commits found:\n${commitLog}\n`); + + // 5. Build the prompt + const prompt = buildPrompt(commitLog, diffStat || "", diffContent || ""); + + // 6. Write prompt to a temp file (avoids shell-escaping issues) + const promptFile = "/tmp/doc_update_prompt.txt"; + fs.writeFileSync(promptFile, prompt); + console.log(`Prompt written to ${promptFile} (${prompt.length} chars)`); + + // 7. Invoke AI agent + console.log(`\nInvoking AI agent (${AI_PROVIDER})…\n`); + const aiOutput = invokeAI(promptFile, REPO_ROOT); + + // 8. Update tracking data + data.last_automated_updates_commit = latestCommit; + writeDataFile(data); + console.log(`\nTracking data updated — latest commit: ${latestCommit}`); + + // 9. Write summary + commit log for the workflow to pick up + const summary = aiOutput + ? aiOutput.trim() + : "(AI agent produced no output)"; + fs.writeFileSync(SUMMARY_OUT, summary); + fs.writeFileSync(COMMITS_OUT, commitLog); + console.log(`Summary written to ${SUMMARY_OUT}`); + console.log(`Commit log written to ${COMMITS_OUT}`); +} + +// --------------------------------------------------------------------------- +// Prompt builder +// --------------------------------------------------------------------------- +function buildPrompt(commitLog, diffStat, diffContent) { + return `You are an expert technical writer updating GDevelop's documentation. + +CONTEXT +------- +The documentation lives in: ${REPO_ROOT}/docs/gdevelop5/ +The GDevelop engine source code is at: ${GDEVELOP_DIR}/ +(You can read files there for reference.) + +RECENT GDEVELOP COMMITS +------------------------ +${commitLog} + +FILES CHANGED (summary) +----------------------- +${diffStat} + +DETAILED CHANGES (may be truncated) +------------------------------------ +${diffContent} + +YOUR TASK +--------- +1. Analyse the commits above and identify **user-facing** changes + (new features, changed behaviors, renamed concepts, new parameters, etc.). +2. Find the relevant documentation pages under docs/gdevelop5/ and make + **concise** updates so the docs accurately reflect the changes. +3. If a commit only touches internals, tests, or CI, you can skip it. +4. Keep edits minimal — do NOT rewrite entire pages. +5. Preserve the existing writing style and Markdown formatting. + +IMPORTANT CONSTRAINTS +--------------------- +• NEVER edit auto-generated files. These include: + – Every file under docs/gdevelop5/extensions/ + – Every "reference.md" file under docs/gdevelop5/all-features/*/reference.md + – docs/gdevelop5/all-features/expressions-reference.md +• NEVER create new files unless a major new feature truly requires it. +• NEVER edit files outside docs/gdevelop5/. +• Do NOT touch images or binary files. +• If no documentation updates are needed, do nothing. + +WHEN YOU ARE DONE +----------------- +Output a brief summary (2-3 lines) of the documentation changes you made, +or state that no changes were needed. This summary will be included in the +pull request description. + +Make your changes now.`; +} + +// --------------------------------------------------------------------------- +// AI invocation +// --------------------------------------------------------------------------- +function invokeAI(promptFile, cwd) { + let cmd; + const opts = { cwd, timeout: 10 * 60 * 1000 }; + + if (AI_PROVIDER === "claude") { + // ── Claude Code ────────────────────────────────────────────────────── + cmd = `cat "${promptFile}" | claude -p --dangerously-skip-permissions`; + // ── To use Codex instead, comment the block above and uncomment below. + } else if (AI_PROVIDER === "codex") { + // ── OpenAI Codex ───────────────────────────────────────────────────── + cmd = `codex --full-auto -q "$(cat '${promptFile}')"`; + opts.shell = "/bin/bash"; + } else { + throw new Error(`Unknown AI_PROVIDER: "${AI_PROVIDER}". Use "claude" or "codex".`); + } + + const output = execSync(cmd, { ...opts, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }); + console.log("── AI agent output ─────────────────────────────────────────"); + console.log(output); + console.log("── End of AI agent output ──────────────────────────────────"); + return output; +} + +// --------------------------------------------------------------------------- +main();