@sctg/backport-agent 0.1.0-20260529153548 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.mjs +1 -11
- package/dist/main.mjs.map +1 -1
- package/package.json +1 -5
package/dist/main.mjs
CHANGED
|
@@ -1786,16 +1786,6 @@ function makeReportSubAgent(modelId, systemPrompt) {
|
|
|
1786
1786
|
});
|
|
1787
1787
|
}
|
|
1788
1788
|
/**
|
|
1789
|
-
* Returns a backtick fence string long enough to safely wrap `content`.
|
|
1790
|
-
* Finds the longest run of consecutive backticks in the content and uses
|
|
1791
|
-
* one more, with a minimum of 3. This prevents inner ``` from closing the
|
|
1792
|
-
* outer fence and breaking Markdown rendering.
|
|
1793
|
-
*/
|
|
1794
|
-
function safeFence(content) {
|
|
1795
|
-
const maxRun = Math.max(0, ...(content.match(/`+/g) ?? []).map((s) => s.length));
|
|
1796
|
-
return "`".repeat(Math.max(3, maxRun + 1));
|
|
1797
|
-
}
|
|
1798
|
-
/**
|
|
1799
1789
|
* Calls the fast model to produce a Mermaid flowchart summarising the agent run.
|
|
1800
1790
|
* Returns a fenced mermaid code block string, or a fallback placeholder on error.
|
|
1801
1791
|
*/
|
|
@@ -2010,7 +2000,7 @@ function makeReportTool(config, promptLogPath) {
|
|
|
2010
2000
|
for (let i = 0; i < promptEntries.length; i++) {
|
|
2011
2001
|
const e = promptEntries[i];
|
|
2012
2002
|
const statusBadge = e.error ? "❌ Error" : "✅ OK";
|
|
2013
|
-
detailedLines.push(`### Call ${i + 1} / ${promptEntries.length} — \`${e.tool}\` ${statusBadge}`, "", `| Field | Value |`, `|---|---|`, `| **Timestamp** | ${e.timestamp} |`, `| **Tool** | \`${e.tool}\` |`, `| **Model** | \`${e.model}\` |`, `| **Duration** | ${e.durationMs} ms |`, ...e.inputTokens != null ? [`| **Tokens in** | ${e.inputTokens} |`] : [], ...e.outputTokens != null ? [`| **Tokens out** | ${e.outputTokens} |`] : [], ...e.cacheReadTokens != null && e.cacheReadTokens > 0 ? [`| **Cache read** | ${e.cacheReadTokens} |`] : [], ...e.cacheWriteTokens != null && e.cacheWriteTokens > 0 ? [`| **Cache write** | ${e.cacheWriteTokens} |`] : [], ...e.totalCost != null ? [`| **Cost** | $${e.totalCost.toFixed(6)} |`] : [], ...e.error ? [`| **Error** | ${e.error} |`] : [], "", "**Prompt sent to sub-agent:**", "",
|
|
2003
|
+
detailedLines.push(`### Call ${i + 1} / ${promptEntries.length} — \`${e.tool}\` ${statusBadge}`, "", `| Field | Value |`, `|---|---|`, `| **Timestamp** | ${e.timestamp} |`, `| **Tool** | \`${e.tool}\` |`, `| **Model** | \`${e.model}\` |`, `| **Duration** | ${e.durationMs} ms |`, ...e.inputTokens != null ? [`| **Tokens in** | ${e.inputTokens} |`] : [], ...e.outputTokens != null ? [`| **Tokens out** | ${e.outputTokens} |`] : [], ...e.cacheReadTokens != null && e.cacheReadTokens > 0 ? [`| **Cache read** | ${e.cacheReadTokens} |`] : [], ...e.cacheWriteTokens != null && e.cacheWriteTokens > 0 ? [`| **Cache write** | ${e.cacheWriteTokens} |`] : [], ...e.totalCost != null ? [`| **Cost** | $${e.totalCost.toFixed(6)} |`] : [], ...e.error ? [`| **Error** | ${e.error} |`] : [], "", "**Prompt sent to sub-agent:**", "", "```", e.prompt, "```", "", "**Response received:**", "", "```", e.response || "(empty)", "```", "", "---", "");
|
|
2014
2004
|
}
|
|
2015
2005
|
if (promptEntries.length === 0) detailedLines.push("_No AI sub-agent calls were made during this run._", "", "---", "");
|
|
2016
2006
|
if (promptEntries.length > 0) {
|
package/dist/main.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.mjs","names":[],"sources":["../src/config/schema.ts","../src/config/loader.ts","../src/customizations/schema.ts","../src/customizations/loader.ts","../src/tool-helper.ts","../src/git/git-client.ts","../src/git/git-tools.ts","../src/git/git-init.ts","../src/risk/classify-risk.ts","../src/risk/risk-tools.ts","../src/validation/commands.ts","../src/validation/validation-tools.ts","../src/github/github-tools.ts","../src/reports/report-tools.ts","../src/ai/ai-tools.ts","../src/main.ts"],"sourcesContent":["/**\n * @file config/schema.ts\n *\n * Zod schema for the agent's main configuration file (config.json).\n * All fields are validated and typed at load time via `SyncConfigSchema.parse()`.\n *\n * The top-level object is divided into five sections:\n * - `upstream` – coordinates of the original repository being tracked\n * - `fork` – coordinates of the customised fork maintained by this agent\n * - `workingDir` – filesystem location of the local checkout\n * - `auth` – git authentication (SSH key or HTTP bearer token)\n * - `sync` – behavioural knobs (commit limits, dry-run mode, branch names…)\n * - `customizations` – inline or external customizations manifest (optional)\n * - `models` – LLM model identifiers used for cheap vs. powerful inference\n * - `validation` – shell commands executed after cherry-picking, grouped by risk level\n */\n\nimport { z } from \"zod\"\n\n/**\n * Full Zod validation schema for the backport-agent configuration.\n *\n * All nested objects have sensible defaults so that a minimal config.json only\n * needs to specify `upstream`, `fork`, and `workingDir`.\n *\n * **Important — Zod v4 `.default()` behaviour:**\n * When an entire sub-object is optional, we use `.default(() => ({} as any))`.\n * The factory form `() => value` is required by Zod v4 (unlike v3's plain value form).\n * The `as any` cast is intentional: each individual field already carries its own\n * `.default(…)`, so Zod will fill in all missing keys automatically; the outer\n * `{}` is just an empty trigger that lets the field-level defaults take effect.\n */\nexport const SyncConfigSchema = z.object({\n /**\n * Coordinates of the upstream (canonical) repository.\n * The agent fetches from this remote and picks commits out of it.\n */\n upstream: z.object({\n /** GitHub repository in `owner/repo` format, e.g. `\"cline/cline\"`. */\n repo: z.string().describe(\"owner/repo of the upstream repository\"),\n /**\n * Full git URL for the upstream remote, e.g. `\"git@github.com:org/repo.git\"` (SSH)\n * or `\"https://github.com/org/repo.git\"` (HTTPS).\n * Required when the working directory does not yet exist (for auto-clone setup).\n * Supports any git hosting provider, not just GitHub.\n */\n url: z.string().optional().describe(\"Full git URL (SSH or HTTPS) for the upstream remote\"),\n /** Branch on the upstream repo that the agent tracks, e.g. `\"main\"`. */\n branch: z.string().describe(\"Upstream branch to sync from\"),\n /** Local git remote name pointing to the upstream repo. Defaults to `\"upstream\"`. */\n remote: z.string().default(\"upstream\").describe(\"Git remote name for upstream\"),\n }),\n\n /**\n * Coordinates of the fork (customised) repository.\n * This is where new sync branches are pushed and PRs are opened.\n */\n fork: z.object({\n /** GitHub repository in `owner/repo` format, e.g. `\"TEA-ching/cline\"`. */\n repo: z.string().describe(\"owner/repo of the fork\"),\n /**\n * Full git URL for cloning the fork, e.g. `\"git@github.com:myuser/repo.git\"` (SSH)\n * or `\"https://github.com/myuser/repo.git\"` (HTTPS).\n * If the working directory does not exist the agent will clone this URL automatically.\n * Supports any git hosting provider, not just GitHub.\n */\n url: z.string().optional().describe(\"Full git URL (SSH or HTTPS) used to clone the fork\"),\n /** Target branch in the fork that sync commits are based on, e.g. `\"main\"`. */\n branch: z.string().describe(\"Fork branch to sync into\"),\n /** Local git remote name pointing to the fork. Defaults to `\"origin\"`. */\n remote: z.string().default(\"origin\").describe(\"Git remote name for the fork\"),\n }),\n\n /**\n * Absolute filesystem path to the local git clone of the fork.\n * All git operations are executed with this path as the working directory.\n * Example: `\"/home/ci/repos/my-fork\"`.\n * If the directory does not exist and `fork.url` is set, the agent will\n * clone the fork automatically on startup.\n */\n workingDir: z.string().describe(\"Absolute path to the local clone of the fork\"),\n\n /**\n * Git authentication credentials.\n *\n * Exactly one of `sshKeyPath` or `githubToken` should be set:\n * - `sshKeyPath` — path to an SSH private key; sets `GIT_SSH_COMMAND` for all git calls.\n * Supports `~` expansion. Example: `\"~/.ssh/id_ed25519\"`.\n * - `githubToken` — bearer token for HTTPS remotes (GitHub PAT, GitLab token, etc.);\n * injected via `http.extraHeader`. Works with any git hosting provider.\n * For security, prefer referencing an environment variable with the `$VAR` syntax\n * (e.g. `\"$GITHUB_TOKEN\"`) instead of embedding the raw token. If omitted, the\n * agent falls back to the `GITHUB_TOKEN` environment variable automatically.\n *\n * Both fields are optional — omit this section if git is already authenticated\n * through the system SSH agent or a credential helper.\n */\n auth: z\n .object({\n /**\n * Absolute (or `~`-prefixed) path to the SSH private key.\n * Example: `\"~/.ssh/id_ed25519\"` or `\"/home/ci/.ssh/deploy_key\"`.\n */\n sshKeyPath: z.string().optional().describe(\"Path to the SSH private key (supports ~ expansion)\"),\n /**\n * Bearer token for HTTPS authentication.\n * Prefix with `$` to read from an environment variable at runtime\n * (e.g. `\"$GITHUB_TOKEN\"`), which avoids storing the secret in config.json.\n */\n githubToken: z.string().optional().describe(\n \"HTTP bearer token; use \\\"$ENV_VAR\\\" syntax to read from an environment variable\"\n ),\n })\n .default(() => ({} as any)),\n\n /**\n * Runtime behaviour settings for the sync loop.\n * All fields have defaults, so this entire section is optional in config.json.\n */\n sync: z\n .object({\n /**\n * Maximum number of agent loop iterations per run.\n * Each iteration is one model turn (potentially invoking several tools in parallel).\n * Increase this value for large repos or runs with many conflict resolutions.\n * Defaults to 200.\n */\n maxIterations: z.number().int().positive().default(200),\n /** Maximum number of upstream commits to process in a single agent run. Defaults to 20. */\n maxCommitsPerRun: z.number().int().positive().default(20),\n /**\n * Depth used when first fetching remote refs.\n * Shallow enough to be fast; `ensureMergeBase` will deepen if necessary. Defaults to 200.\n */\n initialFetchDepth: z.number().int().positive().default(200),\n /**\n * Absolute upper bound for history depth when searching for a merge-base.\n * If the merge-base is not found within this depth, a full `--unshallow` fetch is attempted.\n * Defaults to 4000.\n */\n maxFetchDepth: z.number().int().positive().default(4000),\n /**\n * Number of commits to cherry-pick before pausing for human review.\n * Smaller batches reduce blast radius if something goes wrong. Defaults to 5.\n */\n batchSize: z.number().int().positive().default(5),\n /**\n * When true, the agent runs all analysis steps but skips all write operations\n * (no cherry-picks, no branch pushes, no PR creation). Defaults to false.\n * Can also be enabled at runtime via the `DRY_RUN=true` environment variable.\n */\n dryRun: z.boolean().default(false),\n /** When true, the agent opens a draft PR after pushing the sync branch. Defaults to true. */\n createPullRequest: z.boolean().default(true),\n /**\n * Prefix used when naming the auto-generated sync branch.\n * The final branch name is `<branchPrefix><upstreamBranch>-<YYYY-MM-DD>`.\n * Defaults to `\"sync/upstream-\"`.\n */\n branchPrefix: z.string().default(\"sync/upstream-\"),\n })\n // Allow omitting the entire sync block in config.json; each field has its own default.\n .default(() => ({} as any)),\n\n /**\n * LLM model identifiers for the keypoollive provider.\n * Use a cheap/fast model for high-volume triage and a more powerful one for\n * conflict resolution where reasoning quality matters most.\n */\n models: z\n .object({\n /**\n * Model used for fast, inexpensive tasks such as summarising diffs and\n * classifying risk alongside the deterministic rule engine.\n * Defaults to `\"mistral/devstral-latest\"`.\n */\n fast: z.string().default(\"mistral/devstral-latest\").describe(\"Low-cost model for summaries and risk triage\"),\n /**\n * Model used as first attempt for conflict resolution — optimised for code tasks.\n * Falls back to `models.powerful` if this call fails.\n * Defaults to `\"mistral/devstral-latest\"`.\n */\n specialist: z\n .string()\n .default(\"mistral/devstral-latest\")\n .describe(\"Code-specialist model for conflict resolution (first attempt)\"),\n /**\n * Model used for complex conflict resolution that demands deeper reasoning.\n * Invoked as a fallback when `models.specialist` fails.\n * Defaults to `\"mistral/magistral-medium-latest\"`.\n */\n powerful: z\n .string()\n .default(\"mistral/magistral-medium-latest\")\n .describe(\"High-capability model for conflict resolution (fallback)\"),\n })\n // Allow omitting the entire models block; individual fields carry defaults.\n .default(() => ({} as any)),\n\n /**\n * Deterministic merge-strategy overrides by file path.\n *\n * Each entry is either a glob pattern (matched via `minimatch`) or a regex\n * literal in the form `/pattern/flags` (e.g. `\"/^sdk\\\\/.*\\.lock$/i\"`).\n * Patterns are tested against the repo-relative file path.\n *\n * When a conflicted file matches:\n * - `ours` → the fork version (HEAD) is used as-is; AI resolution is skipped.\n * - `theirs` → the upstream version (CHERRY_PICK_HEAD) is used as-is; AI resolution is skipped.\n *\n * `theirs` is checked first; if a file matches both, `theirs` wins.\n */\n resolve: z\n .object({\n /**\n * Patterns for files where the fork version must always be kept.\n * Useful for lock files, generated assets, or files maintained exclusively in the fork.\n */\n ours: z.array(z.string()).default([]).describe(\"Glob/regex patterns — always keep fork version on conflict\"),\n /**\n * Patterns for files where the upstream version must always be taken.\n * Useful for changelogs, upstream-owned config files, or generated files\n * that must not carry fork modifications.\n */\n theirs: z.array(z.string()).default([]).describe(\"Glob/regex patterns — always take upstream version on conflict\"),\n })\n .default(() => ({} as any)),\n\n /**\n * Customizations manifest source.\n *\n * Accepts three forms:\n * - `string` starting with `http://` or `https://` → fetched at runtime.\n * - `string` (any other value) → treated as a local filesystem path.\n * - `object` → the manifest is embedded directly in config.json (JSON equivalent\n * of the YAML structure expected by `CustomizationsSchema`).\n *\n * When omitted the loader falls back to the `BACKPORT_CUSTOMIZATIONS` env var,\n * then to `./customizations.yaml` in the current working directory.\n */\n customizations: z\n .union([z.string(), z.record(z.string(), z.unknown())])\n .optional()\n .describe(\"Path, URL, or inline object for the customizations manifest\"),\n\n /**\n * Report output settings.\n */\n report: z\n .object({\n /**\n * Filesystem directory where the detailed Markdown run report is written.\n * The file name is `report.<timestamp>.md`.\n * Defaults to the current working directory (`.`).\n */\n destination: z.string().default(\".\").describe(\"Directory where detailed run reports are written\"),\n })\n .default(() => ({} as any)),\n\n /**\n * Shell command suites executed after cherry-picking, indexed by risk level.\n * Commands must match the allowlist in `validation/commands.ts` or they will\n * be blocked at execution time.\n */\n validation: z\n .object({\n /**\n * Commands run for low-risk commits (no customisation or build-critical files touched).\n * Defaults to `[\"npm run typecheck\"]`.\n */\n low: z.array(z.string()).default([\"npm run typecheck\"]),\n /**\n * Commands run for medium-risk commits (shared/services code changed).\n * Defaults to typecheck + unit tests.\n */\n medium: z.array(z.string()).default([\"npm run typecheck\", \"npm run test:unit\"]),\n /**\n * Commands run for high-risk commits (customisation zones, build files, lock files…).\n * Defaults to typecheck + unit tests + full build.\n */\n high: z.array(z.string()).default([\"npm run typecheck\", \"npm run test:unit\", \"npm run build\"]),\n })\n // Allow omitting the entire validation block; individual fields carry defaults.\n .default(() => ({} as any)),\n})\n\n/**\n * TypeScript type derived directly from `SyncConfigSchema`.\n * Use this type throughout the codebase instead of repeating the inline shape.\n */\nexport type SyncConfig = z.infer<typeof SyncConfigSchema>\n","/**\n * @file config/loader.ts\n *\n * Loads and validates the agent's main configuration from a JSON file.\n *\n * Resolution order for the config path (first match wins):\n * 1. Explicit `configPath` argument passed by the caller.\n * 2. The `BACKPORT_CONFIG` environment variable.\n * 3. `config.json` in the current working directory.\n *\n * Environment variable overrides applied after parsing:\n * - `DRY_RUN=true` → forces `sync.dryRun = true` regardless of the JSON value.\n */\n\nimport { readFileSync } from \"node:fs\"\nimport { resolve } from \"node:path\"\nimport { SyncConfigSchema, type SyncConfig } from \"./schema.js\"\n\n/**\n * Read, parse, and validate the agent configuration file.\n *\n * The raw JSON is parsed first, then any environment-variable overrides are\n * merged in before the result is validated through `SyncConfigSchema.parse()`.\n * Zod will throw a descriptive `ZodError` if required fields are missing or\n * have the wrong type.\n *\n * @param configPath - Optional explicit path to a `config.json` file.\n * Falls back to `BACKPORT_CONFIG` env var, then `./config.json`.\n * @returns A fully validated `SyncConfig` object with all defaults applied.\n * @throws {Error} If the file cannot be read or cannot be parsed as JSON.\n * @throws {ZodError} If the JSON structure does not satisfy `SyncConfigSchema`.\n */\nexport function loadConfig(configPath?: string): SyncConfig {\n // Determine which config file to read, in priority order.\n const path = configPath ?? process.env.BACKPORT_CONFIG ?? resolve(process.cwd(), \"config.json\")\n\n // Read synchronously — startup is blocking by design; no need for async here.\n const raw = JSON.parse(readFileSync(path, \"utf-8\"))\n\n // Ensure optional top-level sections exist so nested defaults are always applied.\n raw.sync ??= {}\n raw.models ??= {}\n raw.validation ??= {}\n\n // Environment variable overrides — applied before Zod validation so that\n // field-level constraints (e.g. type checks) still apply to the final values.\n if (process.env.KEYPOOL_VAULT_URL) {\n // KEYPOOL_VAULT_URL is consumed by the keypoollive provider, not by this schema.\n // We validate its presence separately in main.ts at agent startup.\n }\n\n if (process.env.DRY_RUN === \"true\") {\n // Allow CI pipelines to safely test the agent without pushing anything.\n raw.sync = { ...(raw.sync ?? {}), dryRun: true }\n }\n\n // Validate and apply defaults. SyncConfigSchema.parse() throws on invalid input.\n return SyncConfigSchema.parse(raw)\n}\n","/**\n * @file customizations/schema.ts\n *\n * Zod schema for the agent's customizations manifest (customizations.yaml).\n *\n * Each \"customization entry\" describes a deliberate deviation from upstream:\n * which file paths it covers, what invariants must remain intact after a sync,\n * and optional shell commands that can verify the customization is still working.\n *\n * The agent uses this manifest to:\n * 1. Detect when an upstream commit touches a customization zone (risk classification).\n * 2. Guide conflict resolution — the LLM knows which files carry fork-specific logic.\n * 3. Produce human-readable PR comments that explain why certain files need review.\n */\n\nimport { z } from \"zod\"\n\n/**\n * Schema for a single customization entry in the manifest.\n *\n * Example YAML entry:\n * ```yaml\n * - id: keypoollive-provider-vscode\n * description: \"Registers the keypoollive LLM provider inside the VS Code extension\"\n * paths:\n * - src/api/providers/keypoollive.ts\n * - src/shared/providers/providers.json\n * invariants:\n * - \"keypoollive must remain listed in providers.json\"\n * testCommands:\n * - \"npm run typecheck\"\n * ```\n */\nexport const CustomizationEntrySchema = z.object({\n /**\n * Short machine-readable identifier for this customization, e.g. `\"keypoollive-provider-vscode\"`.\n * Used in risk reports and decision logs to unambiguously reference the entry.\n */\n id: z.string(),\n\n /**\n * Human-readable description of what this customization does and why it exists.\n * Surfaced in PR comments and agent decision logs.\n */\n description: z.string(),\n\n /**\n * Glob patterns (relative to the repository root) that cover the files owned\n * by this customization. Any upstream commit touching one of these paths will\n * be classified as high risk.\n *\n * Standard minimatch syntax is supported, e.g. `\"src/api/providers/keypoollive/**\"`.\n */\n paths: z.array(z.string()).describe(\"Glob patterns relative to repo root\"),\n\n /**\n * Ordered list of invariants that must remain true after every sync.\n * The agent checks these conceptually during conflict resolution and includes\n * them in the PR body so human reviewers know what to verify.\n *\n * Example: `\"The SCTG_KEY_VAULT_URL constant must not be removed.\"`\n */\n invariants: z.array(z.string()).describe(\"Human-readable invariants that must remain true after sync\"),\n\n /**\n * Optional shell commands to run in order to verify this specific customization\n * is still intact after a sync. These are appended to the validation suite when\n * the commit risk level is \"high\" and this customization is affected.\n *\n * Commands must still match the global allowlist in `validation/commands.ts`.\n */\n testCommands: z.array(z.string()).optional().describe(\"Commands to verify this customization still works\"),\n})\n\n/**\n * Schema for the entire customizations manifest file.\n * The top-level key `customizations` holds the array of entries.\n */\nexport const CustomizationsSchema = z.object({\n /** Array of all known fork customizations. May be empty if the fork has no deviations. */\n customizations: z.array(CustomizationEntrySchema),\n})\n\n/**\n * TypeScript type for a single customization entry, inferred from `CustomizationEntrySchema`.\n */\nexport type CustomizationEntry = z.infer<typeof CustomizationEntrySchema>\n\n/**\n * TypeScript type for the full customizations manifest, inferred from `CustomizationsSchema`.\n */\nexport type Customizations = z.infer<typeof CustomizationsSchema>\n","/**\n * @file customizations/loader.ts\n *\n * Loads and validates the customizations manifest from multiple sources.\n *\n * Resolution order for the manifest (first match wins):\n * 1. Explicit `source` argument passed by the caller.\n * - `string` starting with `http://` or `https://` → fetched via HTTP GET.\n * - `string` (other) → read from the local filesystem.\n * - `object` → used directly as the parsed manifest (JSON/inline form).\n * 2. The `BACKPORT_CUSTOMIZATIONS` environment variable (file path).\n * 3. `customizations.yaml` in the current working directory.\n *\n * The resolved value is parsed with `js-yaml` when it comes from a string/URL,\n * then validated against `CustomizationsSchema` via Zod.\n */\n\nimport { readFileSync } from \"node:fs\"\nimport { resolve } from \"node:path\"\nimport yaml from \"js-yaml\"\nimport { CustomizationsSchema, type Customizations } from \"./schema.js\"\n\n/**\n * Read, parse, and validate the customizations manifest.\n *\n * @param source - Optional source: a file path, an HTTP(S) URL, or an inline object.\n * Falls back to `BACKPORT_CUSTOMIZATIONS` env var, then `./customizations.yaml`.\n * @returns A fully validated `Customizations` object.\n * @throws {Error} If the file/URL cannot be read.\n * @throws {ZodError} If the structure does not satisfy `CustomizationsSchema`.\n */\nexport async function loadCustomizations(source?: string | Record<string, unknown>): Promise<Customizations> {\n // --- Inline object: already parsed, validate directly ---\n if (source !== undefined && typeof source === \"object\") {\n return CustomizationsSchema.parse(source)\n }\n\n // --- Resolve string source ---\n const strSource =\n source ?? process.env.BACKPORT_CUSTOMIZATIONS ?? resolve(process.cwd(), \"customizations.yaml\")\n\n let raw: unknown\n\n if (typeof strSource === \"string\" && (strSource.startsWith(\"http://\") || strSource.startsWith(\"https://\"))) {\n // URL: fetch via HTTP GET\n const response = await fetch(strSource)\n if (!response.ok) {\n throw new Error(`Failed to fetch customizations from ${strSource}: HTTP ${response.status} ${response.statusText}`)\n }\n const text = await response.text()\n raw = yaml.load(text)\n } else {\n // Local file path\n raw = yaml.load(readFileSync(strSource as string, \"utf-8\"))\n }\n\n return CustomizationsSchema.parse(raw)\n}\n\n/**\n * Flatten all glob patterns from every customization entry into a single array.\n *\n * Useful as a quick pre-filter: if a changed file matches any of these patterns\n * the caller knows it needs deeper per-entry inspection.\n *\n * @param customizations - Validated customizations manifest.\n * @returns Deduplicated flat array of all glob patterns across all entries.\n */\nexport function getCustomizationPaths(customizations: Customizations): string[] {\n // flatMap collapses the nested arrays from each entry's `paths` field.\n return customizations.customizations.flatMap((c) => c.paths)\n}\n","/**\n * @file tool-helper.ts\n *\n * Typed wrapper around `createTool` from `@sctg/cline-sdk`.\n *\n * **Problem — overload resolution ambiguity:**\n * `@sctg/cline-shared/dist/tools/create.d.ts` declares two overloads of\n * `createTool`:\n * 1. `createTool(config: { inputSchema: Record<string, unknown>, ... })`\n * 2. `createTool<TSchema extends ZodTypeAny, TOutput>(config: { inputSchema: TSchema, ... })`\n *\n * TypeScript evaluates overloads in declaration order. Because `ZodObject`\n * is structurally assignable to `Record<string, unknown>`, overload 1 always\n * wins, and the inferred input type in `execute` becomes `unknown` instead of\n * the typed schema inference from overload 2.\n *\n * **Solution:**\n * `defineTool` has the correct generic signature (overload 2's types) and casts\n * the config to `any` before forwarding to `createTool`. TypeScript then\n * infers the Zod-typed `execute` parameter correctly at every call site.\n *\n * This is the only place where `as any` is used in the codebase.\n */\n\nimport { createTool } from \"@sctg/cline-agents\"\nimport type { AgentTool, AgentToolContext } from \"@sctg/cline-sdk\"\nimport { z } from \"zod\"\n\n/**\n * Creates a fully-typed agent tool from the provided configuration.\n *\n * This is a thin wrapper around `createTool` that exists solely to fix TypeScript\n * overload resolution. All arguments are forwarded unchanged; the only difference\n * from calling `createTool` directly is that `TSchema` is correctly inferred from\n * `inputSchema`.\n *\n * @typeParam TSchema - Zod schema type for the tool's input object.\n * @typeParam TOutput - Return type of the `execute` function.\n *\n * @param config - Tool configuration object.\n * @param config.name - Machine-readable tool name (snake_case by convention).\n * @param config.description - Natural-language description shown to the LLM.\n * @param config.inputSchema - Zod schema that validates and types the tool's input.\n * @param config.execute - Async function called by the agent runtime. Receives\n * a fully typed `input` (inferred from `TSchema`) and\n * an `AgentToolContext` for runtime metadata.\n * @param config.lifecycle - Optional lifecycle hooks (e.g. `completesRun: true`).\n * @param config.timeoutMs - Optional per-invocation timeout in milliseconds.\n * @param config.retryable - Whether the runtime should retry on transient failure.\n * @param config.maxRetries - Maximum retry attempts (used when `retryable` is `true`).\n * @returns A fully constructed `AgentTool` ready to be passed to the `Agent` constructor.\n */\nexport function defineTool<TSchema extends z.ZodTypeAny, TOutput>(config: {\n name: string\n description: string\n inputSchema: TSchema\n execute: (input: z.infer<TSchema>, context: AgentToolContext) => Promise<TOutput>\n lifecycle?: AgentTool<z.infer<TSchema>, TOutput>[\"lifecycle\"]\n timeoutMs?: number\n retryable?: boolean\n maxRetries?: number\n}): AgentTool<z.infer<TSchema>, TOutput> {\n // Cast to `any` to bypass the overload ambiguity described above.\n // The return type annotation ensures callers still get full type safety.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return createTool(config as any)\n}\n","/**\n * @file git/git-client.ts\n *\n * Low-level git operations used by the backport agent.\n *\n * Design principles:\n * - **No shell interpolation** — all git invocations use `execFileSync` with an\n * explicit argument array. User-supplied strings (SHAs, branch names, file\n * paths) are always passed as separate array items, never concatenated into a\n * shell command string. This prevents command-injection vulnerabilities.\n * - **Synchronous I/O** — the agent runs a single-threaded, sequential workflow;\n * async/await overhead would add complexity without benefit.\n * - **Minimal surface** — each function does exactly one git operation. Higher-\n * level orchestration lives in `git-tools.ts` (agent tool wrappers).\n */\n\nimport { execFileSync } from \"node:child_process\"\nimport { readFileSync, writeFileSync } from \"node:fs\"\n\n/**\n * Executes a git command in the given working directory using `execFileSync`.\n *\n * All standard streams are piped so that stdout and stderr are captured rather\n * than printed to the terminal. The return value is the trimmed stdout string.\n *\n * **Security note:** arguments must always be provided as an array — never as a\n * pre-joined string — to prevent shell injection.\n *\n * @param args - Git sub-command and its arguments, e.g. `[\"cherry\", \"-v\", \"HEAD\"]`.\n * @param cwd - Absolute path to the repository working directory.\n * @returns Trimmed stdout output of the git command.\n * @throws If the git process exits with a non-zero status (e.g. merge conflict).\n */\nexport function git(args: string[], cwd: string): string {\n return execFileSync(\"git\", args, { cwd, encoding: \"utf-8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] }).trim()\n}\n\n/**\n * Represents a single upstream commit that the agent is considering for cherry-picking.\n */\nexport type CandidateCommit = {\n /** Full 40-character SHA of the upstream commit. */\n sha: string\n /** First line of the commit message (subject). */\n subject: string\n /**\n * `true` when `git cherry` determined that an equivalent patch is already\n * present in the fork branch (prefix `-` in cherry output).\n * Such commits are reported but skipped without cherry-picking.\n */\n alreadyApplied: boolean\n}\n\n/**\n * Ensures the local git history is deep enough to compute the merge-base between\n * the upstream branch and the fork branch.\n *\n * Many CI environments start with a shallow clone (`--depth=1`). This function\n * progressively deepens the clone in steps rather than fetching the entire\n * history at once, which keeps network usage low for the common case.\n *\n * Algorithm:\n * 1. Try `git merge-base` at the current depth.\n * 2. If it fails, deepen by the next step value and retry.\n * 3. If even `maxDepth` is insufficient, fall back to `--unshallow`.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @param upstreamRef - Full ref for the upstream branch, e.g. `\"upstream/main\"`.\n * @param forkRef - Full ref for the fork branch, e.g. `\"origin/main\"`.\n * @param maxDepth - Depth ceiling before attempting a full unshallow fetch.\n * Defaults to 4000 (matches `sync.maxFetchDepth` default).\n * @returns The SHA of the common ancestor commit (the merge-base).\n * @throws If the merge-base cannot be determined even after a full fetch.\n */\nexport function ensureMergeBase(\n cwd: string,\n upstreamRef: string,\n forkRef: string,\n maxDepth = 4000,\n): string {\n // Progressive depth ladder: try cheaper options first to minimise fetch size.\n const depths = [200, 500, 1000, 2000, maxDepth]\n\n for (const depth of depths) {\n try {\n // Attempt to find the common ancestor at the current history depth.\n return git([\"merge-base\", upstreamRef, forkRef], cwd)\n } catch {\n if (depth === maxDepth) {\n // Last resort: fetch the entire history and try once more.\n git([\"fetch\", \"--unshallow\"], cwd)\n return git([\"merge-base\", upstreamRef, forkRef], cwd)\n }\n // Deepen the shallow clone by the next step and loop.\n git([\"fetch\", `--deepen=${depth}`], cwd)\n }\n }\n\n throw new Error(\"Could not find merge-base even after full fetch\")\n}\n\n/**\n * Lists all upstream commits that are not yet present in the fork branch.\n *\n * Uses `git cherry` which compares patch content (not just SHA) so that commits\n * that were already cherry-picked (and may have a different SHA in the fork) are\n * correctly identified as already applied.\n *\n * Output format of `git cherry -v <upstream> <fork>`:\n * - Lines prefixed with `+` are **not** in the fork → candidates for cherry-pick.\n * - Lines prefixed with `-` **are** equivalent in the fork → already applied.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @param upstreamRef - Full ref for the upstream branch, e.g. `\"upstream/main\"`.\n * @param forkRef - Full ref for the fork branch, e.g. `\"origin/main\"`.\n * @returns Array of `CandidateCommit` objects, oldest-first.\n */\nexport function listCandidateCommits(\n cwd: string,\n upstreamRef: string,\n forkRef: string,\n): CandidateCommit[] {\n // `git cherry -v <upstream> <fork>` lists commits reachable from <upstream>\n // but not equivalent in <fork>. The `-v` flag adds the subject line.\n const cherryOutput = git([\"cherry\", \"-v\", forkRef, upstreamRef], cwd)\n\n // Empty output means upstream and fork are already in sync.\n if (!cherryOutput) return []\n\n return cherryOutput.split(\"\\n\").map((line) => {\n // Each line: `<marker> <sha> <subject>` where marker is `+` or `-`.\n const marker = line[0]\n const rest = line.slice(2) // skip marker and space\n const spaceIdx = rest.indexOf(\" \")\n const sha = rest.slice(0, spaceIdx)\n const subject = rest.slice(spaceIdx + 1)\n return {\n sha,\n subject,\n // `-` means an equivalent patch already exists in the fork.\n alreadyApplied: marker === \"-\",\n }\n })\n}\n\n/**\n * Returns the list of file paths changed by a single commit.\n *\n * Internally calls `git diff-tree --no-commit-id -r --name-only <sha>` which\n * lists only changed paths without any diff content, keeping the output small.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @param sha - Full or abbreviated commit SHA.\n * @returns Array of repository-relative file paths changed by the commit.\n * Empty array if the commit has no file changes (e.g. an empty commit).\n */\nexport function getCommitChangedFiles(cwd: string, sha: string): string[] {\n const output = git([\"diff-tree\", \"--no-commit-id\", \"-r\", \"--name-only\", sha], cwd)\n // Filter out empty strings that result from splitting a trailing newline.\n return output ? output.split(\"\\n\").filter(Boolean) : []\n}\n\n/**\n * Returns the full diff of a single commit, capped to `maxBytes` characters.\n *\n * The diff includes the commit stat summary (`--stat`) followed by the patch\n * (`--patch`). Large diffs are truncated with a notice so that the LLM context\n * window is not exhausted by a single commit.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @param sha - Full or abbreviated commit SHA.\n * @param maxBytes - Maximum number of characters to return. Defaults to 32 000.\n * @returns Diff string, possibly truncated.\n */\nexport function getCommitDiff(cwd: string, sha: string, maxBytes = 32_000): string {\n const full = git([\"show\", \"--stat\", \"--patch\", sha], cwd)\n // Truncate and append a notice so the LLM knows the diff is incomplete.\n return full.length > maxBytes ? full.slice(0, maxBytes) + \"\\n... [truncated]\" : full\n}\n\n/**\n * Creates a new sync branch from the tip of the fork branch.\n *\n * The branch is created locally; `pushBranch` must be called separately to\n * publish it to the remote.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @param branchName - Name for the new sync branch.\n * @param forkRef - Full ref of the fork branch to branch off, e.g. `\"origin/main\"`.\n */\nexport function createSyncBranch(cwd: string, branchName: string, forkRef: string): void {\n // First check out the fork branch tip to set HEAD correctly.\n git([\"checkout\", forkRef], cwd)\n // Then create and switch to the new sync branch.\n git([\"checkout\", \"-b\", branchName], cwd)\n}\n\n/**\n * Attempts to cherry-pick a single upstream commit onto the current branch.\n *\n * The `-x` flag appends `(cherry picked from commit …)` to the commit message,\n * providing an audit trail in the fork's history.\n *\n * On conflict, the cherry-pick is intentionally left **in progress** rather than\n * aborted. This allows the agent to inspect each conflicted file via\n * `getConflictContext`, resolve them, then call `continueCherryPick`. If the\n * agent cannot resolve the conflicts, it should call `abortCherryPick` instead.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @param sha - Full or abbreviated SHA of the commit to cherry-pick.\n * @returns An object with `success: true` if the cherry-pick applied cleanly,\n * or `success: false` plus the list of conflicted file paths.\n */\nexport function cherryPick(cwd: string, sha: string): { success: boolean; conflictedFiles: string[] } {\n try {\n // -x appends a \"cherry picked from\" note to the commit message.\n git([\"cherry-pick\", \"-x\", sha], cwd)\n return { success: true, conflictedFiles: [] }\n } catch {\n // Git exits non-zero on conflict. Collect the conflicting file paths.\n const status = git([\"diff\", \"--name-only\", \"--diff-filter=U\"], cwd)\n // U = unmerged (conflicted) files.\n const conflictedFiles = status ? status.split(\"\\n\").filter(Boolean) : []\n return { success: false, conflictedFiles }\n }\n}\n\n/**\n * Aborts a cherry-pick that is currently in progress.\n *\n * This resets the index and working tree to the state before `git cherry-pick`\n * was called. It is safe to call even if no cherry-pick is in progress (the\n * error is swallowed silently).\n *\n * @param cwd - Absolute path to the repository working directory.\n */\nexport function abortCherryPick(cwd: string): void {\n try {\n git([\"cherry-pick\", \"--abort\"], cwd)\n } catch {\n // Ignore errors — git returns non-zero if there is no cherry-pick in progress,\n // which is a harmless edge case (e.g. called twice by mistake).\n }\n}\n\n/**\n * Returns the content of a file at a specific git ref.\n *\n * Common use cases:\n * - `ref = \"HEAD\"` → the fork's current version of the file.\n * - `ref = \"CHERRY_PICK_HEAD\"` → the upstream version being cherry-picked.\n * - `ref = \"<sha>\"` → the version at any specific commit.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @param ref - Git ref, symbolic name, or SHA.\n * @param filePath - Repository-relative path of the file, e.g. `\"src/foo.ts\"`.\n * @returns The file content as a UTF-8 string, or `null` if the file does not\n * exist at the given ref (e.g. the file was added by the cherry-picked commit).\n */\nexport function getFileAtRef(cwd: string, ref: string, filePath: string): string | null {\n try {\n // `git show <ref>:<path>` streams the blob content to stdout.\n return git([\"show\", `${ref}:${filePath}`], cwd)\n } catch {\n // Non-zero exit means the path does not exist at that ref.\n return null\n }\n}\n\n/**\n * Reads the current working-tree version of a file, including any conflict markers.\n *\n * After a failed cherry-pick, git leaves conflict markers (`<<<<<<<`, `=======`,\n * `>>>>>>>`) in the file. This function reads that raw content so the agent can\n * analyse it before attempting a resolution.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @param filePath - Repository-relative path of the file, e.g. `\"src/foo.ts\"`.\n * @returns The raw file content as a UTF-8 string (may contain conflict markers).\n * @throws If the file does not exist on disk.\n */\nexport function readWorkingFile(cwd: string, filePath: string): string {\n // Absolute path is constructed by joining cwd and the repo-relative path.\n return readFileSync(`${cwd}/${filePath}`, \"utf-8\")\n}\n\n/**\n * Writes resolved content to a file on disk and stages it with `git add`.\n *\n * Called by the agent after resolving each conflicted file. The file must\n * contain no conflict markers before calling this function.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @param filePath - Repository-relative path of the file, e.g. `\"src/foo.ts\"`.\n * @param content - Fully resolved file content, free of conflict markers.\n */\nexport function writeAndStageFile(cwd: string, filePath: string, content: string): void {\n // Write the resolved content to disk, replacing the conflict-marker version.\n writeFileSync(`${cwd}/${filePath}`, content, \"utf-8\")\n // Stage the file so it is included in the cherry-pick commit.\n git([\"add\", filePath], cwd)\n}\n\n/**\n * Completes an in-progress cherry-pick after all conflicts have been resolved and staged.\n *\n * Uses `GIT_EDITOR=true` to suppress the interactive editor that git would\n * otherwise open for the commit message, making this safe to call in a\n * non-interactive CI environment.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @throws If there are still unstaged conflicted files when this is called.\n */\nexport function continueCherryPick(cwd: string): void {\n // GIT_EDITOR=true accepts the default commit message without opening an editor.\n // --no-edit is also passed as a belt-and-suspenders precaution.\n execFileSync(\"git\", [\"cherry-pick\", \"--continue\", \"--no-edit\"], {\n cwd,\n encoding: \"utf-8\",\n env: { ...process.env, GIT_EDITOR: \"true\" },\n })\n}\n\n/**\n * Pushes the sync branch to the fork remote.\n *\n * A simple non-force push. If the branch already exists on the remote with\n * different history the push will fail — the agent should never force-push to\n * avoid overwriting human commits.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @param remote - Name of the git remote to push to, e.g. `\"origin\"`.\n * @param branchName - Name of the local branch to push.\n */\nexport function pushBranch(cwd: string, remote: string, branchName: string): void {\n git([\"push\", remote, branchName], cwd)\n}\n\n/**\n * Fetches both the upstream and fork remotes to bring local refs up to date.\n *\n * Uses a shallow fetch (`--depth=N`) to keep network usage proportional. The\n * depth here corresponds to `sync.initialFetchDepth`; `ensureMergeBase` will\n * deepen further if needed.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @param upstreamRemote - Name of the upstream git remote, e.g. `\"upstream\"`.\n * @param forkRemote - Name of the fork git remote, e.g. `\"origin\"`.\n * @param depth - Shallow fetch depth.\n */\nexport function fetchRemotes(cwd: string, upstreamRemote: string, forkRemote: string, depth: number): void {\n git([\"fetch\", `--depth=${depth}`, upstreamRemote], cwd)\n git([\"fetch\", `--depth=${depth}`, forkRemote], cwd)\n}\n\n\n","/**\n * @file git/git-tools.ts\n *\n * Factory that creates the agent tools wrapping all low-level git operations.\n *\n * Each tool returned by `makeGitTools` corresponds to a single capability that\n * the LLM can invoke during the sync workflow:\n *\n * 1. `fetch_remotes` — update local refs from upstream and fork.\n * 2. `list_candidate_commits` — discover which upstream commits to sync.\n * 3. `get_commit_details` — inspect changed files and full diff.\n * 4. `create_sync_branch` — branch off the fork tip for this sync run.\n * 5. `cherry_pick_commit` — apply a single commit; reports conflicts.\n * 6. `abort_cherry_pick` — abandon a conflicting cherry-pick.\n * 7. `get_conflict_context` — fetch fork, upstream, and marker-annotated versions.\n * 8. `apply_resolved_file` — write the LLM's resolution and stage it.\n * 9. `continue_cherry_pick` — complete the cherry-pick after all files resolved.\n * 10. `push_sync_branch` — publish the sync branch to the fork remote.\n *\n * All tools respect the `sync.dryRun` flag by returning early with a `dryRun:true`\n * marker instead of performing any mutating operation.\n */\n\nimport { z } from \"zod\"\nimport { readFileSync } from \"node:fs\"\nimport { minimatch } from \"minimatch\"\nimport { defineTool } from \"../tool-helper.js\"\nimport {\n ensureMergeBase,\n listCandidateCommits,\n getCommitChangedFiles,\n getCommitDiff,\n createSyncBranch,\n cherryPick,\n abortCherryPick,\n getFileAtRef,\n writeAndStageFile,\n continueCherryPick,\n pushBranch,\n fetchRemotes,\n} from \"./git-client.js\"\nimport type { SyncConfig } from \"../config/schema.js\"\n\n/**\n * Tests whether a repo-relative file path matches any of the given patterns.\n *\n * Patterns are either:\n * - A glob string (matched via `minimatch` with `matchBase: true`).\n * - A regex literal in the form `/source/flags` (e.g. `\"/^sdk\\\\/.*\\.ts$/i\"`).\n *\n * @param filePath - Repo-relative path of the file to test.\n * @param patterns - Array of glob or regex patterns from the config.\n * @returns `true` if the path matches at least one pattern.\n */\nfunction matchesResolvePattern(filePath: string, patterns: string[]): boolean {\n for (const pattern of patterns) {\n // Regex literal: /source/flags\n if (pattern.startsWith(\"/\") && pattern.lastIndexOf(\"/\") > 0) {\n const lastSlash = pattern.lastIndexOf(\"/\")\n const source = pattern.slice(1, lastSlash)\n const flags = pattern.slice(lastSlash + 1)\n try {\n if (new RegExp(source, flags).test(filePath)) return true\n } catch {\n // Silently skip malformed regex patterns.\n }\n } else {\n // Glob pattern via minimatch.\n if (minimatch(filePath, pattern, { matchBase: true })) return true\n }\n }\n return false\n}\n\n/**\n * Builds and returns all git-related agent tools pre-bound to the provided config.\n *\n * The returned array is spread directly into the `Agent` constructor's `tools`\n * array. Each tool captures `workingDir`, `upstream`, `fork`, and `sync` from\n * the config via closure, so callers never need to pass them per-invocation.\n *\n * @param config - Validated `SyncConfig` loaded from `config.json`.\n * @returns Array of ten agent tools covering the full git workflow.\n */\nexport function makeGitTools(config: SyncConfig) {\n // Destructure frequently-used config sections for brevity inside each tool.\n const { workingDir, upstream, fork, sync } = config\n\n /**\n * Tool: fetch_remotes\n *\n * Fetches the upstream and fork remotes at `sync.initialFetchDepth`, then\n * calls `ensureMergeBase` to deepen the clone if needed. This must be called\n * once at the start of every run before any other git tool.\n */\n const fetchRemotesTool = defineTool({\n name: \"fetch_remotes\",\n description: \"Fetch both upstream and fork remotes to ensure local refs are up to date.\",\n inputSchema: z.object({}),\n execute: async () => {\n // Fetch both remotes at the configured initial depth.\n fetchRemotes(workingDir, upstream.remote, fork.remote, sync.initialFetchDepth)\n // Deepen the clone as needed so that merge-base computation succeeds.\n ensureMergeBase(\n workingDir,\n `${upstream.remote}/${upstream.branch}`,\n `${fork.remote}/${fork.branch}`,\n sync.maxFetchDepth,\n )\n return { success: true }\n },\n })\n\n /**\n * Tool: list_candidate_commits\n *\n * Uses `git cherry` to compare upstream and fork by patch content. Commits\n * that have already been applied (even with a different SHA) are excluded.\n * The result is limited to `sync.maxCommitsPerRun` to prevent the agent from\n * processing an unbounded queue in a single session.\n */\n const listCandidatesTool = defineTool({\n name: \"list_candidate_commits\",\n description:\n \"List upstream commits that are not yet applied to the fork branch. \" +\n \"Uses git cherry to detect already-applied patches by content, not just SHA. \" +\n \"Returns an array of candidate commits with their SHA, subject, and alreadyApplied flag.\",\n inputSchema: z.object({}),\n execute: async () => {\n const candidates = listCandidateCommits(\n workingDir,\n `${upstream.remote}/${upstream.branch}`,\n `${fork.remote}/${fork.branch}`,\n )\n // Filter out already-applied commits and cap to the configured run limit.\n const pending = candidates.filter((c) => !c.alreadyApplied).slice(0, sync.maxCommitsPerRun)\n return { candidates: pending, total: pending.length }\n },\n })\n\n /**\n * Tool: get_commit_details\n *\n * Returns the file list and (optionally) the full diff for a given commit SHA.\n * The diff is truncated at 32 000 characters by `getCommitDiff` to protect\n * the LLM context window. The agent should call this before risk classification\n * and before attempting a cherry-pick.\n */\n const getCommitDetailsTool = defineTool({\n name: \"get_commit_details\",\n description:\n \"Get the changed files and diff for a specific upstream commit. \" +\n \"Use this before classifying risk or attempting a cherry-pick.\",\n inputSchema: z.object({\n sha: z.string().describe(\"The commit SHA to inspect\"),\n /** Set to false to skip the diff and only retrieve the file list. */\n includeDiff: z.boolean().default(true),\n }),\n execute: async ({ sha, includeDiff }) => {\n const changedFiles = getCommitChangedFiles(workingDir, sha)\n // Fetch the diff only when explicitly requested to save context tokens.\n const diff = includeDiff ? getCommitDiff(workingDir, sha) : null\n return { sha, changedFiles, diff }\n },\n })\n\n /**\n * Tool: create_sync_branch\n *\n * Creates a new local branch named `<branchPrefix><upstreamBranch>-<date>`\n * branching off `<forkRemote>/<forkBranch>`. No-ops in dry-run mode.\n * The branch name is returned so subsequent tools can reference it.\n */\n const createSyncBranchTool = defineTool({\n name: \"create_sync_branch\",\n description:\n \"Create a new sync branch from the fork branch tip. \" +\n \"The branch name is auto-generated with today's date. Returns the branch name.\",\n inputSchema: z.object({}),\n execute: async () => {\n // Skip actual branch creation in dry-run mode.\n if (sync.dryRun) return { branchName: null, dryRun: true }\n // Build the branch name from the configured prefix, upstream branch, and today's date.\n const date = new Date().toISOString().slice(0, 10)\n const branchName = `${sync.branchPrefix}${upstream.branch}-${date}`\n createSyncBranch(workingDir, branchName, `${fork.remote}/${fork.branch}`)\n return { branchName }\n },\n })\n\n /**\n * Tool: cherry_pick_commit\n *\n * Attempts to cherry-pick the given SHA. On success, the commit is already\n * committed to the local branch. On conflict, git leaves the cherry-pick in\n * progress; the agent should call `get_conflict_context` / `apply_resolved_file`\n * / `continue_cherry_pick` in sequence, or `abort_cherry_pick` to give up.\n */\n const cherryPickCommitTool = defineTool({\n name: \"cherry_pick_commit\",\n description:\n \"Attempt to cherry-pick a single upstream commit onto the current sync branch. \" +\n \"Returns success:true if clean, or success:false with conflictedFiles if conflicts arose. \" +\n \"On conflict, the cherry-pick is left in progress for the resolve_conflict tool.\",\n inputSchema: z.object({\n sha: z.string().describe(\"Upstream commit SHA to cherry-pick\"),\n }),\n execute: async ({ sha }) => {\n // Dry-run: report success without touching the repository.\n if (sync.dryRun) return { success: true, dryRun: true, conflictedFiles: [] }\n return cherryPick(workingDir, sha)\n },\n })\n\n /**\n * Tool: abort_cherry_pick\n *\n * Calls `git cherry-pick --abort` to discard any partially applied changes and\n * restore the working tree to the state before the cherry-pick started. Should\n * be called when the agent decides a conflict is too complex to resolve safely.\n */\n const abortCherryPickTool = defineTool({\n name: \"abort_cherry_pick\",\n description: \"Abort the current cherry-pick in progress. Call this when a conflict cannot be resolved automatically.\",\n inputSchema: z.object({}),\n execute: async () => {\n abortCherryPick(workingDir)\n return { aborted: true }\n },\n })\n\n /**\n * Tool: get_conflict_context\n *\n * Returns three views of a conflicted file so the LLM has all the information\n * it needs for a principled resolution:\n * - `forkVersion` — the file as it existed in HEAD before the cherry-pick.\n * - `upstreamVersion` — the file as it exists in CHERRY_PICK_HEAD (incoming).\n * - `withMarkers` — the current working-tree content with `<<<<<<<` markers.\n *\n * `forkVersion` or `upstreamVersion` may be `null` if the file is new on one side.\n */\n const getConflictContextTool = defineTool({\n name: \"get_conflict_context\",\n description:\n \"For a conflicted file, return the fork version (HEAD), the upstream version (CHERRY_PICK_HEAD), \" +\n \"and the current file content with conflict markers. Use this to gather context before resolving.\",\n inputSchema: z.object({\n filePath: z.string().describe(\"Repo-relative path of the conflicted file\"),\n }),\n execute: async ({ filePath }) => {\n // Fetch the fork's current committed version (may be null for new files).\n const forkVersion = getFileAtRef(workingDir, \"HEAD\", filePath)\n // Fetch the incoming upstream version (may be null for deleted files).\n const upstreamVersion = getFileAtRef(workingDir, \"CHERRY_PICK_HEAD\", filePath)\n // Read the working-tree file which contains conflict markers.\n let withMarkers: string | null = null\n try {\n withMarkers = readFileSync(`${workingDir}/${filePath}`, \"utf-8\")\n } catch {\n // The file may have been deleted by the upstream commit.\n withMarkers = null\n }\n\n // Deterministic strategy override from config.resolve.\n // `theirs` is checked first; if a file matches both, theirs wins.\n let forcedStrategy: \"ours\" | \"theirs\" | null = null\n const resolveConfig = config.resolve\n if (resolveConfig) {\n if (matchesResolvePattern(filePath, resolveConfig.theirs ?? [])) {\n forcedStrategy = \"theirs\"\n } else if (matchesResolvePattern(filePath, resolveConfig.ours ?? [])) {\n forcedStrategy = \"ours\"\n }\n }\n\n return { filePath, forkVersion, upstreamVersion, withMarkers, forcedStrategy }\n },\n })\n\n /**\n * Tool: apply_resolved_file\n *\n * Writes the LLM-provided resolution for a single conflicted file to disk and\n * runs `git add` to stage it. Must be called for every conflicted file before\n * `continue_cherry_pick`. The `resolvedContent` must be free of conflict markers.\n */\n const applyResolvedFileTool = defineTool({\n name: \"apply_resolved_file\",\n description:\n \"Write the resolved content for a conflicted file and stage it. \" +\n \"Call this for each conflicted file before calling continue_cherry_pick.\",\n inputSchema: z.object({\n filePath: z.string().describe(\"Repo-relative path of the file\"),\n resolvedContent: z.string().describe(\"The fully resolved file content, with no conflict markers\"),\n }),\n execute: async ({ filePath, resolvedContent }) => {\n // Skip file write in dry-run mode.\n if (sync.dryRun) return { staged: false, dryRun: true }\n writeAndStageFile(workingDir, filePath, resolvedContent)\n return { staged: true, filePath }\n },\n })\n\n /**\n * Tool: continue_cherry_pick\n *\n * Finalises the cherry-pick after all conflicted files have been resolved and\n * staged. Internally calls `git cherry-pick --continue --no-edit` with\n * `GIT_EDITOR=true` so that no interactive editor is opened.\n */\n const continueCherryPickTool = defineTool({\n name: \"continue_cherry_pick\",\n description:\n \"Complete the cherry-pick after all conflicted files have been resolved and staged via apply_resolved_file.\",\n inputSchema: z.object({}),\n execute: async () => {\n // Skip in dry-run mode.\n if (sync.dryRun) return { committed: false, dryRun: true }\n continueCherryPick(workingDir)\n return { committed: true }\n },\n })\n\n /**\n * Tool: push_sync_branch\n *\n * Pushes the named sync branch to `fork.remote`. Called once after all commits\n * have been processed. Only a non-force push is performed to avoid overwriting\n * human commits on the remote.\n */\n const pushSyncBranchTool = defineTool({\n name: \"push_sync_branch\",\n description: \"Push the current sync branch to the fork remote.\",\n inputSchema: z.object({\n /** Name of the local sync branch to push, as returned by `create_sync_branch`. */\n branchName: z.string(),\n }),\n execute: async ({ branchName }) => {\n // Skip push in dry-run mode.\n if (sync.dryRun) return { pushed: false, dryRun: true }\n pushBranch(workingDir, fork.remote, branchName)\n return { pushed: true, branchName }\n },\n })\n\n return [\n fetchRemotesTool,\n listCandidatesTool,\n getCommitDetailsTool,\n createSyncBranchTool,\n cherryPickCommitTool,\n abortCherryPickTool,\n getConflictContextTool,\n applyResolvedFileTool,\n continueCherryPickTool,\n pushSyncBranchTool,\n ]\n}\n","/**\n * @file git/git-init.ts\n *\n * Repository initialisation and authentication helpers.\n *\n * Two exported functions:\n *\n * - `applyGitAuth(config)` — configures the process environment so that all\n * subsequent git calls (via `git-client.ts`) use the right credentials.\n * Supports SSH private keys (via `GIT_SSH_COMMAND`) and HTTP bearer tokens\n * (via git's `http.extraHeader` environment config).\n *\n * - `ensureWorkingDir(config)` — makes the `workingDir` ready for the agent:\n * · If the directory does not exist (or is not a git repo): clones `fork.url`.\n * · If it already exists: fetches all remotes to bring it up to date.\n * · Ensures the upstream remote is properly configured when its URL is provided\n * and it differs from the fork remote.\n *\n * Design notes:\n * - All git I/O is synchronous (matches the rest of git-client.ts).\n * - No secrets are stored in child-process arguments — tokens are injected via\n * environment variables only.\n * - `fork.url` supports any git hosting provider (GitHub, GitLab, Gitea, …).\n */\n\nimport { existsSync, mkdirSync } from \"node:fs\"\nimport { dirname } from \"node:path\"\nimport { execFileSync } from \"node:child_process\"\nimport { git } from \"./git-client.js\"\nimport type { SyncConfig } from \"../config/schema.js\"\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolves a config value that may reference an environment variable.\n *\n * If `value` starts with `$` the remainder is treated as an environment variable\n * name. This lets operators write `\"$GITHUB_TOKEN\"` in config.json instead of\n * embedding the raw secret, keeping credentials out of version control.\n *\n * @param value - Raw config string, e.g. `\"ghp_abc123\"` or `\"$MY_TOKEN\"`.\n * @returns The resolved string, or `undefined` if the env var is not set.\n */\nfunction resolveConfigValue(value: string): string | undefined {\n if (value.startsWith(\"$\")) {\n return process.env[value.slice(1)]\n }\n return value\n}\n\n// ---------------------------------------------------------------------------\n// Exports\n// ---------------------------------------------------------------------------\n\n/**\n * Configures process-level environment variables so that all subsequent git\n * operations (in this process and its child processes) use the right credentials.\n *\n * Priority order:\n * 1. SSH key (`config.auth.sshKeyPath`) → sets `GIT_SSH_COMMAND`\n * 2. Token (`config.auth.githubToken`) → sets `GIT_CONFIG_*` http.extraHeader\n * 3. Fallback to `GITHUB_TOKEN` env var → same as (2)\n *\n * If none of the above are set the function is a no-op; git will use whatever\n * credentials are already available in the environment (SSH agent, credential helper…).\n *\n * @param config - Validated `SyncConfig` loaded from `config.json`.\n */\nexport function applyGitAuth(config: SyncConfig): void {\n const { sshKeyPath, githubToken } = config.auth\n\n // --- SSH key ---\n if (sshKeyPath) {\n // Expand leading ~ to the user's home directory.\n const keyPath = sshKeyPath.replace(/^~(?=\\/|$)/, process.env.HOME ?? \"\")\n process.env.GIT_SSH_COMMAND = `ssh -i \"${keyPath}\" -o StrictHostKeyChecking=no -o BatchMode=yes`\n process.stderr.write(`[GitAuth] SSH key configured: ${keyPath}\\n`)\n return\n }\n\n // --- HTTP bearer token ---\n const rawToken = githubToken ?? \"$GITHUB_TOKEN\"\n const token = resolveConfigValue(rawToken)\n if (token) {\n // git supports injecting config via numbered GIT_CONFIG_KEY_N / GIT_CONFIG_VALUE_N env vars.\n // This avoids writing to any config file and works without root access.\n const count = parseInt(process.env.GIT_CONFIG_COUNT ?? \"0\", 10)\n process.env.GIT_CONFIG_COUNT = String(count + 1)\n process.env[`GIT_CONFIG_KEY_${count}`] = \"http.extraHeader\"\n process.env[`GIT_CONFIG_VALUE_${count}`] = `Authorization: Bearer ${token}`\n process.stderr.write(\"[GitAuth] HTTP bearer token auth configured.\\n\")\n }\n}\n\n/**\n * Ensures that `config.workingDir` contains a ready-to-use git repository.\n *\n * **Clone** (directory absent or not a git repo):\n * Clones `config.fork.url` into `config.workingDir`. The parent directory is\n * created if necessary. Throws if `fork.url` is not set.\n *\n * **Sync** (directory is already a git repo):\n * Runs `git fetch --all --prune` to bring all tracked remotes up to date.\n *\n * **Upstream remote**:\n * When `config.upstream.url` is set and `upstream.remote` differs from\n * `fork.remote`, the upstream remote is added (or its URL updated) automatically.\n *\n * @param config - Validated `SyncConfig` loaded from `config.json`.\n * @throws If cloning is required but `fork.url` is not configured.\n */\nexport function ensureWorkingDir(config: SyncConfig): void {\n const { workingDir, upstream, fork } = config\n const isGitRepo = existsSync(`${workingDir}/.git`)\n\n // --- Clone if the working directory does not yet exist ---\n if (!isGitRepo) {\n if (!fork.url) {\n throw new Error(\n `[GitInit] '${workingDir}' is not a git repository and fork.url is not configured. ` +\n `Set fork.url to a valid git URL (SSH or HTTPS) to enable automatic cloning.`,\n )\n }\n // Ensure the parent directory exists (git clone does not create it).\n mkdirSync(dirname(workingDir), { recursive: true })\n process.stderr.write(`[GitInit] Cloning ${fork.url} → ${workingDir} ...\\n`)\n execFileSync(\"git\", [\"clone\", fork.url, workingDir], {\n // Pipe stdin, inherit stdout/stderr so clone progress is visible.\n stdio: [\"pipe\", \"inherit\", \"inherit\"],\n })\n process.stderr.write(\"[GitInit] Clone complete.\\n\")\n } else {\n // --- Fetch all remotes to sync the existing checkout ---\n process.stderr.write(`[GitInit] ${workingDir} found — fetching all remotes...\\n`)\n try {\n git([\"fetch\", \"--all\", \"--prune\"], workingDir)\n process.stderr.write(\"[GitInit] Fetch complete.\\n\")\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n process.stderr.write(`[GitInit] Warning: fetch failed: ${msg}\\n`)\n }\n }\n\n // --- Ensure upstream remote is configured ---\n // Only act when an upstream URL is provided and the upstream remote has a\n // different name than the fork remote (avoids overwriting the fork \"origin\").\n if (upstream.url && upstream.remote !== fork.remote) {\n try {\n const remotes = git([\"remote\"], workingDir).split(\"\\n\").filter(Boolean)\n if (!remotes.includes(upstream.remote)) {\n git([\"remote\", \"add\", upstream.remote, upstream.url], workingDir)\n process.stderr.write(`[GitInit] Added remote '${upstream.remote}' → ${upstream.url}\\n`)\n } else {\n const currentUrl = git([\"remote\", \"get-url\", upstream.remote], workingDir)\n if (currentUrl !== upstream.url) {\n git([\"remote\", \"set-url\", upstream.remote, upstream.url], workingDir)\n process.stderr.write(`[GitInit] Updated remote '${upstream.remote}' → ${upstream.url}\\n`)\n }\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n process.stderr.write(`[GitInit] Warning: could not configure upstream remote: ${msg}\\n`)\n }\n }\n}\n","/**\n * @file risk/classify-risk.ts\n *\n * Purely deterministic commit risk classifier — no LLM is involved.\n *\n * The classifier assigns one of three risk levels to an upstream commit:\n * - **low** — Only touches files that are safe to auto-apply.\n * - **medium** — Touches shared infrastructure (API layer, shared types, services)\n * that may interact with fork customizations.\n * - **high** — Touches build configuration, CI pipelines, lockfiles, protobuf\n * definitions, or a file explicitly listed in the fork's\n * `customizations.yaml`.\n *\n * The LLM agent uses this output as high-level context before deciding whether\n * to attempt a cherry-pick, skip, or request human review.\n *\n * Pattern matching uses `minimatch` (the same glob library used by `.gitignore`\n * and the VS Code extension tree). All patterns are relative to the repository\n * root, as returned by `git diff-tree --name-only`.\n */\n\nimport { minimatch } from \"minimatch\"\nimport type { Customizations } from \"../customizations/schema.js\"\n\n/**\n * The three risk levels assigned to every upstream commit.\n *\n * - `\"low\"` Safe to cherry-pick with minimal validation.\n * - `\"medium\"` Requires standard validation suite before merging.\n * - `\"high\"` Requires full validation + likely human review.\n */\nexport type RiskLevel = \"low\" | \"medium\" | \"high\"\n\n/**\n * Full risk assessment result for a single upstream commit.\n */\nexport type CommitRisk = {\n /** The commit SHA that was classified. */\n sha: string\n /** Computed risk level: \"low\", \"medium\", or \"high\". */\n level: RiskLevel\n /** Human-readable explanations for why each risk factor was triggered. */\n reasons: string[]\n /** `true` if any file in the commit matches a customization zone in the fork. */\n touchesCustomization: boolean\n /** IDs of `CustomizationEntry` objects whose paths were matched by this commit. */\n customizationIds: string[]\n}\n\n/**\n * Glob patterns whose matches unconditionally elevate risk to `\"high\"`.\n *\n * These files are considered build-critical or structurally sensitive:\n * - Dependency manifests and lockfiles (`package.json`, `*-lock.*`, `*.lock`)\n * - CI / GitHub Actions workflows (`.github/workflows/**`)\n * - Build scripts directory (`scripts/**`)\n * - ESBuild config files (`esbuild.*`)\n * - TypeScript project references (`tsconfig*.json`)\n * - Protobuf definitions (`proto/**`) — changes here require proto regeneration\n * and affect the entire RPC layer\n */\nconst HIGH_RISK_PATTERNS = [\n \"package.json\",\n \"package-lock.json\",\n \"pnpm-lock.yaml\",\n \"yarn.lock\",\n \".github/workflows/**\",\n \"scripts/**\",\n \"esbuild.*\",\n \"tsconfig*.json\",\n \"proto/**\",\n]\n\n/**\n * Glob patterns whose matches elevate risk to `\"medium\"` when no high-risk\n * pattern has already been matched.\n *\n * These paths contain shared infrastructure that is more likely to conflict\n * with fork customizations than generic feature code:\n * - `src/core/api/**` — API provider implementations\n * - `src/shared/**` — Shared types and utilities used everywhere\n * - `src/services/**` — Backend services (MCP hub, etc.)\n * - `webview-ui/src/services/**`— Webview service layer\n */\nconst MEDIUM_RISK_PATTERNS = [\n \"src/core/api/**\",\n \"src/shared/**\",\n \"src/services/**\",\n \"webview-ui/src/services/**\",\n]\n\n/**\n * Classifies the risk level of an upstream commit based on which files it changes.\n *\n * Evaluation order (highest-priority first):\n * 1. Fork customization zones (`customizations.yaml`) → always `high`.\n * 2. `HIGH_RISK_PATTERNS` glob matches → always `high`.\n * 3. `MEDIUM_RISK_PATTERNS` glob matches → `medium` (if not already high).\n * 4. Detected file deletions or renames → elevate to at least `medium`.\n * 5. No matches → remains `low`.\n *\n * This function is **pure and deterministic** — given the same inputs it always\n * returns the same output. It has no side effects and performs no I/O.\n *\n * @param sha - Full or abbreviated commit SHA (used to label the result).\n * @param changedFiles - Repository-relative file paths changed by the commit,\n * as returned by `getCommitChangedFiles`.\n * @param customizations - Validated customizations manifest from `loadCustomizations`.\n * @returns A `CommitRisk` record with the computed level, reasons, and customization matches.\n */\nexport function classifyRisk(sha: string, changedFiles: string[], customizations: Customizations): CommitRisk {\n const reasons: string[] = []\n const matchedCustomizationIds: string[] = []\n let level: RiskLevel = \"low\"\n\n // --- Step 1: Check fork customization zones ---\n // Any file that matches a customization's glob pattern triggers high risk,\n // because it means an upstream change directly conflicts with our fork-specific code.\n for (const entry of customizations.customizations) {\n const hits = changedFiles.filter((f) => entry.paths.some((p) => minimatch(f, p)))\n if (hits.length > 0) {\n matchedCustomizationIds.push(entry.id)\n reasons.push(`Touches customization \"${entry.id}\": ${hits.join(\", \")}`)\n level = \"high\"\n }\n }\n\n // --- Step 2: Check high-risk file patterns ---\n // Build infrastructure changes (lockfiles, CI, tsconfig, proto) are always high risk\n // regardless of whether they touch a named customization zone.\n for (const pattern of HIGH_RISK_PATTERNS) {\n const hits = changedFiles.filter((f) => minimatch(f, pattern))\n if (hits.length > 0) {\n if (level !== \"high\") level = \"high\"\n reasons.push(`High-risk file pattern \"${pattern}\": ${hits.join(\", \")}`)\n }\n }\n\n // --- Step 3: Check medium-risk patterns (only if still low) ---\n // These patterns are checked last so that a high-risk determination from steps 1-2\n // is not overwritten. A medium classification means the agent should run the\n // standard validation suite but may still auto-apply.\n if (level === \"low\") {\n for (const pattern of MEDIUM_RISK_PATTERNS) {\n const hits = changedFiles.filter((f) => minimatch(f, pattern))\n if (hits.length > 0) {\n level = \"medium\"\n reasons.push(`Medium-risk pattern \"${pattern}\": ${hits.join(\", \")}`)\n }\n }\n }\n\n // --- Step 4: Deletions and renames ---\n // Removing or renaming files is inherently risky because dependent code may break.\n // Elevate to at least medium if not already high.\n const deletions = changedFiles.filter((f) => f.startsWith(\"DELETE:\") || f.startsWith(\"RENAME:\"))\n if (deletions.length > 0) {\n if (level === \"low\") level = \"medium\"\n reasons.push(`File deletions or renames detected`)\n }\n\n // --- Step 5: Fallback reason ---\n // Always include at least one reason so callers don't have to handle an empty array.\n if (reasons.length === 0) {\n reasons.push(\"No risk patterns matched — appears to be a low-risk change\")\n }\n\n return {\n sha,\n level,\n reasons,\n touchesCustomization: matchedCustomizationIds.length > 0,\n customizationIds: matchedCustomizationIds,\n }\n}\n","/**\n * @file risk/risk-tools.ts\n *\n * Factory that creates the `classify_commit_risk` agent tool.\n *\n * Risk classification is a deterministic gate that runs **before** any LLM\n * reasoning: the agent calls this tool first, learns the risk level and\n * affected customizations, then decides how to proceed (auto-apply, apply with\n * validation, or escalate to human review).\n *\n * The tool is kept in a separate factory function so that the validated\n * `customizations` object (loaded once at startup) can be captured by closure\n * and reused across every invocation without re-parsing the YAML file.\n */\n\nimport { z } from \"zod\"\nimport { defineTool } from \"../tool-helper.js\"\nimport { classifyRisk } from \"./classify-risk.js\"\nimport { getCommitChangedFiles } from \"../git/git-client.js\"\nimport type { SyncConfig } from \"../config/schema.js\"\nimport type { Customizations } from \"../customizations/schema.js\"\n\n/**\n * Builds and returns the `classify_commit_risk` agent tool.\n *\n * The tool is pre-bound to `config` and `customizations` so that callers only\n * need to provide the commit SHA at invocation time.\n *\n * @param config - Validated `SyncConfig` (provides `workingDir`).\n * @param customizations - Validated customizations manifest (provides zone definitions).\n * @returns A single agent tool: `classify_commit_risk`.\n */\nexport function makeRiskTool(config: SyncConfig, customizations: Customizations) {\n return defineTool({\n name: \"classify_commit_risk\",\n description:\n \"Classify the risk level of an upstream commit by analysing which files it changes. \" +\n \"Returns 'low', 'medium', or 'high' with human-readable reasons. \" +\n \"High risk means the commit touches fork customization zones or build-critical files. \" +\n \"This is a deterministic check — no LLM is used here.\",\n inputSchema: z.object({\n /** Full or abbreviated SHA of the upstream commit to classify. */\n sha: z.string().describe(\"Upstream commit SHA to classify\"),\n }),\n execute: async ({ sha }) => {\n // 1. Retrieve the list of paths changed by this commit from git.\n const changedFiles = getCommitChangedFiles(config.workingDir, sha)\n // 2. Run the deterministic pattern matcher against those paths.\n const risk = classifyRisk(sha, changedFiles, customizations)\n // The full CommitRisk object is returned to the agent as tool output.\n return risk\n },\n })\n}\n","/**\n * @file validation/commands.ts\n *\n * Allowlist-based command runner for the validation suite.\n *\n * **Security model:**\n * The LLM agent may suggest commands to run during validation. To prevent\n * arbitrary code execution, every command is checked against a fixed prefix\n * allowlist before being executed. Only standard package-manager test scripts\n * and well-known CLI tools are permitted. The allowlist is hard-coded in this\n * file and cannot be extended by the agent at runtime.\n *\n * **No shell interpolation:**\n * Commands are split on whitespace and executed via `execFileSync(bin, args[])`\n * (not through a shell). This prevents shell metacharacter injection even for\n * allowlisted commands.\n *\n * **Failure fast:**\n * `runValidationSuite` stops at the first failing command so that the agent\n * receives a clear, actionable error rather than a wall of cascading output.\n */\n\nimport { execFileSync } from \"node:child_process\"\n\n/**\n * Result of running a single validation command.\n */\nexport type CommandResult = {\n /** The original command string that was (attempted to be) executed. */\n command: string\n /** `true` if the command exited with code 0. */\n success: boolean\n /** Process exit code. `1` is used when the command was blocked by the allowlist. */\n exitCode: number\n /** Combined stdout + stderr output from the process (or the block reason). */\n output: string\n}\n\n/**\n * Hard-coded allowlist of command prefixes that the agent is permitted to execute.\n *\n * A command is allowed if and only if it starts with one of these strings.\n * The list is intentionally conservative:\n * - `\"npm run \"` — npm scripts defined in package.json\n * - `\"pnpm run \"` — pnpm equivalent\n * - `\"yarn run \"` — yarn equivalent\n * - `\"npx tsc\"` — TypeScript type-checker\n * - `\"npx eslint\"` — ESLint linter\n * - `\"npx vitest\"` — Vitest unit test runner\n * - `\"node --version\"` — Non-destructive version probe (useful for diagnostics)\n */\nexport const ALLOWED_COMMAND_PREFIXES = [\n \"npm run \",\n \"pnpm run \",\n \"yarn run \",\n \"npx tsc\",\n \"npx eslint\",\n \"npx vitest\",\n \"node --version\",\n]\n\n/**\n * Returns `true` if the command starts with an allowlisted prefix.\n *\n * The check is intentionally a simple `startsWith` — it does not parse the\n * full command. This makes the allowlist easy to audit.\n *\n * @param command - The full command string to check.\n * @returns `true` if the command is allowed, `false` if it should be blocked.\n */\nexport function isAllowedCommand(command: string): boolean {\n return ALLOWED_COMMAND_PREFIXES.some((prefix) => command.startsWith(prefix))\n}\n\n/**\n * Runs a single validation command and returns its result.\n *\n * If the command is not on the allowlist, it is immediately blocked and a\n * descriptive error is returned without executing anything.\n *\n * Execution details:\n * - `stdio: [\"pipe\",\"pipe\",\"pipe\"]` — all streams are captured, not printed.\n * - `timeout: 120_000` ms — commands are killed if they take more than 2 minutes.\n * - No `shell: true` — the command is parsed by whitespace split and executed directly.\n *\n * @param command - Full command string, e.g. `\"npm run typecheck\"`.\n * @param cwd - Absolute working directory in which to run the command.\n * @returns A `CommandResult` with the success flag, exit code, and captured output.\n */\nexport function runValidationCommand(command: string, cwd: string): CommandResult {\n // Security gate: reject any command not matching the allowlist.\n if (!isAllowedCommand(command)) {\n return {\n command,\n success: false,\n exitCode: 1,\n output: `Blocked: command \"${command}\" is not in the allowed list`,\n }\n }\n\n // Split on whitespace to build the [binary, ...args] array for execFileSync.\n // This avoids passing a shell-interpolated string.\n const parts = command.split(\" \")\n const bin = parts[0]\n const args = parts.slice(1)\n\n try {\n const output = execFileSync(bin, args, {\n cwd,\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n // 2-minute safety timeout — prevents hung test runners from blocking the agent.\n timeout: 120_000,\n })\n return { command, success: true, exitCode: 0, output }\n } catch (err: unknown) {\n // execFileSync throws when the process exits non-zero. Extract diagnostic info.\n const e = err as { status?: number; stdout?: string; stderr?: string; message?: string }\n // Combine all output streams for maximum debuggability.\n const output = [e.stdout, e.stderr, e.message].filter(Boolean).join(\"\\n\")\n return { command, success: false, exitCode: e.status ?? 1, output }\n }\n}\n\n/**\n * Runs an ordered list of validation commands, stopping on the first failure.\n *\n * \"Fail fast\" semantics are intentional: the agent should see the first broken\n * command and address it rather than drowning in cascading failures.\n *\n * @param commands - Ordered array of command strings to execute.\n * @param cwd - Absolute working directory for all commands.\n * @returns Array of `CommandResult` objects, one per executed command.\n * If a command fails, no subsequent commands are executed.\n */\nexport function runValidationSuite(commands: string[], cwd: string): CommandResult[] {\n const results: CommandResult[] = []\n for (const command of commands) {\n const result = runValidationCommand(command, cwd)\n results.push(result)\n // Stop on first failure — no point running further checks.\n if (!result.success) break\n }\n return results\n}\n","/**\n * @file validation/validation-tools.ts\n *\n * Factory that creates the `run_validation` agent tool.\n *\n * Validation is the agent's safety net: before accepting a cherry-picked commit\n * as \"done\", the agent runs the suite appropriate for the commit's risk level to\n * confirm that the fork still builds and passes tests.\n *\n * Risk → suite mapping (configured in `config.validation`):\n * - `\"low\"` → `config.validation.low` (e.g. only typecheck)\n * - `\"medium\"` → `config.validation.medium` (e.g. typecheck + unit tests)\n * - `\"high\"` → `config.validation.high` (e.g. full build + integration tests)\n *\n * The agent may append extra customization-specific commands via the\n * `extraCommands` input field. All commands (base suite + extras) are passed\n * through the same `ALLOWED_COMMAND_PREFIXES` allowlist in `commands.ts`.\n */\n\nimport { z } from \"zod\"\nimport { defineTool } from \"../tool-helper.js\"\nimport { runValidationSuite } from \"./commands.js\"\nimport type { SyncConfig } from \"../config/schema.js\"\nimport type { RiskLevel } from \"../risk/classify-risk.js\"\n\n/**\n * Builds and returns the `run_validation` agent tool.\n *\n * The tool is pre-bound to `config` so that the caller only needs to supply the\n * risk level and any optional extra commands at invocation time.\n *\n * @param config - Validated `SyncConfig` (provides `workingDir` and `validation` suites).\n * @returns A single agent tool: `run_validation`.\n */\nexport function makeValidationTool(config: SyncConfig) {\n return defineTool({\n name: \"run_validation\",\n description:\n \"Run the validation suite appropriate for a given risk level. \" +\n \"'low' runs only typecheck. 'medium' adds unit tests. 'high' adds build and integration tests. \" +\n \"All commands are allowlisted — arbitrary commands are rejected. \" +\n \"Returns success status and per-command output.\",\n inputSchema: z.object({\n /** Risk level computed by `classify_commit_risk`. Determines which suite to run. */\n riskLevel: z.enum([\"low\", \"medium\", \"high\"]).describe(\"Risk level determines which suite to run\"),\n /**\n * Optional additional commands to append to the standard suite.\n * Useful for customization-specific verification commands listed in\n * `customizations.yaml` under `testCommands`.\n * Each command must still match the `ALLOWED_COMMAND_PREFIXES` allowlist.\n */\n extraCommands: z\n .array(z.string())\n .optional()\n .describe(\"Additional commands to append, must match the allowed prefix list\"),\n }),\n execute: async ({ riskLevel, extraCommands = [] }) => {\n // Dry-run: skip all command execution and report success.\n if (config.sync.dryRun) {\n return { dryRun: true, results: [], allPassed: true }\n }\n\n // Map each risk level to its configured command list from config.validation.\n const suites: Record<RiskLevel, string[]> = {\n low: config.validation.low,\n medium: config.validation.medium,\n high: config.validation.high,\n }\n\n // Combine the standard suite with any caller-provided extra commands.\n const commands = [...suites[riskLevel], ...extraCommands]\n // Execute each command in order, stopping on the first failure.\n const results = runValidationSuite(commands, config.workingDir)\n const allPassed = results.every((r) => r.success)\n\n return { riskLevel, results, allPassed }\n },\n // 5-minute overall timeout for the entire suite (individual commands have 2-min timeouts).\n timeoutMs: 300_000,\n })\n}\n","/**\n * @file github/github-tools.ts\n *\n * GitHub API tools that allow the agent to manage pull requests on the fork\n * repository. All operations go through the official `@octokit/rest` client\n * which enforces HTTPS and authenticated requests.\n *\n * The three tools returned by `makeGitHubTools` cover the PR lifecycle:\n * 1. `find_existing_sync_pr` — check whether a previous run already opened a PR,\n * and if so, recover its machine-readable state so the current run can resume.\n * 2. `create_sync_pr` — open a new draft PR with the sync branch, embedding\n * both human-readable Markdown and a hidden machine-readable state block.\n * 3. `add_human_review_comment` — flag specific files or decisions for a human\n * reviewer by posting a comment on the PR.\n *\n * **Idempotency** is achieved via `STATE_MARKER_START/END` HTML comment markers\n * embedded inside the PR body. On each run the agent first calls\n * `find_existing_sync_pr`, which extracts and parses the JSON state block if\n * present, allowing the run to skip already-processed commits.\n *\n * **Authentication** is read from the `GITHUB_TOKEN` environment variable at\n * tool invocation time (not at module load time) so that the token is never\n * stored in process memory longer than necessary.\n */\n\nimport { z } from \"zod\"\nimport { defineTool } from \"../tool-helper.js\"\nimport { Octokit } from \"@octokit/rest\"\nimport type { SyncConfig } from \"../config/schema.js\"\n\n/**\n * Creates and returns an authenticated Octokit instance using the `GITHUB_TOKEN`\n * environment variable.\n *\n * Called inside each tool's `execute` function rather than once at module level\n * to keep the token out of long-lived closures.\n *\n * @returns Authenticated `Octokit` REST client.\n * @throws If `GITHUB_TOKEN` is not set in the environment.\n */\nfunction makeOctokit(): Octokit {\n const token = process.env.GITHUB_TOKEN\n if (!token) throw new Error(\"GITHUB_TOKEN environment variable is required\")\n return new Octokit({ auth: token })\n}\n\n/**\n * Parses an `\"owner/repo\"` string into its component parts.\n *\n * @param repoStr - Repository string in `\"owner/repo\"` format.\n * @returns An object with `owner` and `repo` string fields.\n * @throws If the string does not contain exactly one `/` separator.\n */\nfunction parseRepo(repoStr: string): { owner: string; repo: string } {\n const [owner, repo] = repoStr.split(\"/\")\n if (!owner || !repo) throw new Error(`Invalid repo format: \"${repoStr}\", expected \"owner/repo\"`)\n return { owner, repo }\n}\n\n/**\n * Opening delimiter of the hidden JSON state block embedded in the PR body.\n *\n * The state block is wrapped in an HTML comment so it is invisible in the\n * rendered PR view but can be extracted programmatically on re-runs.\n * Example embedded block:\n * ```\n * <!-- backport-agent-state\n * { \"processedShas\": [\"abc123\", \"def456\"] }\n * -->\n * ```\n */\nconst STATE_MARKER_START = \"<!-- backport-agent-state\\n\"\n\n/**\n * Closing delimiter of the hidden JSON state block embedded in the PR body.\n * @see STATE_MARKER_START\n */\nconst STATE_MARKER_END = \"\\n-->\"\n\n/**\n * Builds and returns the three GitHub API agent tools.\n *\n * All tools capture `fork`, `upstream`, and `sync` from the config via closure.\n * In dry-run mode, every tool returns early with `{ dryRun: true }` and performs\n * no network requests.\n *\n * @param config - Validated `SyncConfig` loaded from `config.json`.\n * @returns Array of three agent tools: `[findExistingPrTool, createSyncPrTool, addHumanReviewCommentTool]`.\n */\nexport function makeGitHubTools(config: SyncConfig) {\n // Destructure the config sections needed by the tools.\n const { fork, upstream, sync } = config\n\n /**\n * Tool: find_existing_sync_pr\n *\n * Queries the fork repository for open PRs whose title starts with\n * `\"Sync upstream\"` and whose body contains the `STATE_MARKER_START` sentinel.\n *\n * If a matching PR is found, the hidden state JSON is extracted and returned\n * so the calling agent can resume from where a previous run left off.\n */\n const findExistingPrTool = defineTool({\n name: \"find_existing_sync_pr\",\n description:\n \"Search for an existing open sync PR created by the backport agent. \" +\n \"Returns the PR number and current state JSON if found, null otherwise.\",\n inputSchema: z.object({}),\n execute: async () => {\n // Skip network call in dry-run mode.\n if (sync.dryRun) return { pr: null, dryRun: true }\n\n const octokit = makeOctokit()\n const { owner, repo } = parseRepo(fork.repo)\n\n // List open PRs whose head branch starts with the configured prefix.\n const { data: prs } = await octokit.pulls.list({\n owner,\n repo,\n state: \"open\",\n head: `${owner}:${sync.branchPrefix}`,\n per_page: 10,\n })\n\n // Find the most recent backport-agent PR by checking title and body marker.\n const agentPr = prs.find(\n (pr) => pr.title.startsWith(\"Sync upstream\") && pr.body?.includes(STATE_MARKER_START),\n )\n if (!agentPr) return { pr: null }\n\n // Extract the embedded JSON state from the PR body.\n let agentState: Record<string, unknown> | null = null\n if (agentPr.body) {\n const start = agentPr.body.indexOf(STATE_MARKER_START)\n const end = agentPr.body.indexOf(STATE_MARKER_END, start)\n if (start !== -1 && end !== -1) {\n try {\n // Slice out just the JSON content between the two markers.\n agentState = JSON.parse(agentPr.body.slice(start + STATE_MARKER_START.length, end))\n } catch {\n // Malformed JSON is treated as missing state — the run starts fresh.\n agentState = null\n }\n }\n }\n return { pr: { number: agentPr.number, url: agentPr.html_url, state: agentState } }\n },\n })\n\n /**\n * Tool: create_sync_pr\n *\n * Opens a draft pull request from the sync branch into `fork.branch`. The PR\n * body consists of the agent-generated Markdown summary followed by the hidden\n * state block. Labels are applied as a best-effort operation (non-fatal if\n * they don't exist on the repository).\n */\n const createSyncPrTool = defineTool({\n name: \"create_sync_pr\",\n description:\n \"Create a draft pull request on the fork repository with the sync branch. \" +\n \"Embeds a hidden state block for idempotent re-runs. Returns the PR URL.\",\n inputSchema: z.object({\n /** Name of the local/remote sync branch created by `create_sync_branch`. */\n branchName: z.string(),\n /** Human-readable Markdown body shown in the GitHub PR UI. */\n markdownBody: z.string().describe(\"Human-readable PR body in Markdown\"),\n /** Machine-readable JSON state to embed as a hidden comment for re-run idempotency. */\n agentState: z.record(z.string(), z.unknown()).describe(\"Machine-readable state to embed in the PR body\"),\n /** Labels to apply to the PR. Defaults to `[\"sync\", \"agent-generated\"]`. */\n labels: z.array(z.string()).default([\"sync\", \"agent-generated\"]),\n }),\n execute: async ({ branchName, markdownBody, agentState, labels }) => {\n // Skip PR creation in dry-run mode.\n if (sync.dryRun) return { url: null, dryRun: true }\n\n const octokit = makeOctokit()\n const { owner, repo } = parseRepo(fork.repo)\n const date = new Date().toISOString().slice(0, 10)\n\n // Build the hidden state block: JSON surrounded by the HTML comment markers.\n const hiddenState = `${STATE_MARKER_START}${JSON.stringify(agentState, null, 2)}${STATE_MARKER_END}`\n // Concatenate the human-readable body with the hidden state block.\n const body = `${markdownBody}\\n\\n${hiddenState}`\n\n // Create the draft PR via the GitHub REST API.\n const { data: pr } = await octokit.pulls.create({\n owner,\n repo,\n title: `Sync upstream ${upstream.branch} into ${fork.branch} (${date})`,\n body,\n head: branchName,\n base: fork.branch,\n draft: true,\n })\n\n // Apply labels — best-effort; labels may not exist on the repository.\n try {\n await octokit.issues.addLabels({ owner, repo, issue_number: pr.number, labels })\n } catch {\n // Non-fatal: labels are cosmetic and their absence does not affect workflow.\n }\n\n return { url: pr.html_url, number: pr.number }\n },\n })\n\n /**\n * Tool: add_human_review_comment\n *\n * Posts a Markdown comment on an existing sync PR. Used when the agent\n * encounters a conflict or edge case it cannot safely resolve automatically\n * and needs to escalate to a human reviewer.\n */\n const addHumanReviewCommentTool = defineTool({\n name: \"add_human_review_comment\",\n description:\n \"Add a comment to the sync PR flagging a specific file or decision for human review. \" +\n \"Use when the agent cannot safely resolve a conflict automatically.\",\n inputSchema: z.object({\n /** PR number on the fork repository to comment on. */\n prNumber: z.number().int(),\n /** Markdown-formatted comment body explaining what needs human attention. */\n comment: z.string().describe(\"Markdown comment explaining what needs human attention\"),\n }),\n execute: async ({ prNumber, comment }) => {\n // Skip comment posting in dry-run mode.\n if (sync.dryRun) return { commented: false, dryRun: true }\n\n const octokit = makeOctokit()\n const { owner, repo } = parseRepo(fork.repo)\n // Post the comment as a regular issue comment (PRs are issues in GitHub API).\n await octokit.issues.createComment({ owner, repo, issue_number: prNumber, body: comment })\n return { commented: true }\n },\n })\n\n return [findExistingPrTool, createSyncPrTool, addHumanReviewCommentTool]\n}\n","/**\n * @file reports/report-tools.ts\n *\n * Factory that creates the `generate_report` agent tool.\n *\n * The report tool is the **terminal step** of every sync run: the agent calls it\n * after processing all candidate commits. Setting `lifecycle: { completesRun: true }`\n * signals to the `@sctg/cline-sdk` runtime that the agent should stop after this\n * tool returns, so the tool doubles as both report generator and run terminator.\n *\n * The tool produces:\n * - A human-readable **Markdown string** suitable for use as a GitHub PR body.\n * - A compact **agentState** object that can be embedded in the PR body (hidden\n * HTML comment) for idempotent re-runs (see `github-tools.ts`).\n * - Boolean flags `allPassed` and `needsHumanReview` for caller logic.\n * - A detailed **Markdown file** written to `config.report.destination` combining\n * the PR-body summary, a Mermaid workflow diagram generated by the fast model,\n * and a full transcript of every AI sub-agent call from the prompts JSONL log.\n *\n * Report sections (PR body):\n * 1. Header — date, upstream ref, fork ref, sync branch name.\n * 2. Summary — counts of applied / needs-review / blocked commits.\n * 3. Applied commits — listed with risk badges and conflict-resolution notes.\n * 4. Human review required — conflicted or validation-failed commits with reasons.\n * 5. Blocked commits — SHAs that were not attempted at all.\n * 6. Agent decision log — ordered audit trail of key agent decisions.\n *\n * Additional sections (detailed file only):\n * 7. Mermaid workflow diagram — generated by the fast LLM from the run summary.\n * 8. AI sub-agent call log — full prompt/response transcript from the JSONL log.\n */\n\nimport { z } from \"zod\"\nimport { readFileSync, writeFileSync, mkdirSync, existsSync } from \"node:fs\"\nimport { resolve as resolvePath, join as joinPath, relative as relativePath } from \"node:path\"\nimport { git } from \"../git/git-client.js\"\nimport { Agent } from \"@sctg/cline-sdk\"\nimport { defineTool } from \"../tool-helper.js\"\nimport type { SyncConfig } from \"../config/schema.js\"\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Shape of a single entry in the `.prompts.jsonl` log file.\n */\ninterface PromptLogEntry {\n timestamp: string\n tool: string\n model: string\n durationMs: number\n prompt: string\n response: string\n error?: string\n inputTokens?: number\n outputTokens?: number\n cacheReadTokens?: number\n cacheWriteTokens?: number\n totalCost?: number\n}\n\n/**\n * Reads the JSONL prompt log, returning all valid entries.\n * Silently skips malformed lines so a corrupt log cannot block the report.\n */\nfunction readPromptLog(logPath: string): PromptLogEntry[] {\n if (!existsSync(logPath)) return []\n try {\n return readFileSync(logPath, \"utf8\")\n .split(\"\\n\")\n .filter(Boolean)\n .flatMap((line) => {\n try {\n return [JSON.parse(line) as PromptLogEntry]\n } catch {\n return []\n }\n })\n } catch {\n return []\n }\n}\n\n/**\n * Instantiates a minimal sub-Agent with no tools for a single reasoning turn.\n * Identical in purpose to the helper in `ai-tools.ts` but local to avoid a\n * cross-module import cycle.\n */\nfunction makeReportSubAgent(modelId: string, systemPrompt: string): Agent {\n return new Agent({\n providerId: \"keypoollive\",\n modelId,\n apiKey: \"auto\",\n systemPrompt,\n tools: [],\n })\n}\n\n/**\n * Returns a backtick fence string long enough to safely wrap `content`.\n * Finds the longest run of consecutive backticks in the content and uses\n * one more, with a minimum of 3. This prevents inner ``` from closing the\n * outer fence and breaking Markdown rendering.\n */\nfunction safeFence(content: string): string {\n const maxRun = Math.max(0, ...(content.match(/`+/g) ?? []).map((s) => s.length))\n return \"`\".repeat(Math.max(3, maxRun + 1))\n}\n\n/**\n * Calls the fast model to produce a Mermaid flowchart summarising the agent run.\n * Returns a fenced mermaid code block string, or a fallback placeholder on error.\n */\nasync function generateMermaidDiagram(\n config: SyncConfig,\n runSummary: string,\n): Promise<string> {\n const systemPrompt = `You are a technical diagram generator. When given a summary of a Git backport agent run, \\\nproduce a single Mermaid flowchart (flowchart TD) that visually represents: \\\n(1) each candidate commit as a node labelled with its short SHA and subject, \\\n(2) the risk level applied to each commit (low / medium / high), \\\n(3) which AI tools were invoked (analyze_commit_for_backport, check_customization_compatibility, resolve_conflict_with_ai), \\\n(4) the final disposition of each commit (applied ✅, blocked ⛔, needs-review ⚠️, skipped ⟳). \\\nUse colour-coded styles: applied=green, blocked=red, needs-review=orange, skipped=grey. \\\nOutput ONLY the raw Mermaid code, no prose, no code fence.`\n\n const userPrompt = `Agent run summary:\\n\\n${runSummary}\\n\\nOutput the Mermaid flowchart now.`\n\n try {\n const subAgent = makeReportSubAgent(config.models.fast, systemPrompt)\n const result = await subAgent.run(userPrompt)\n const raw = (result.outputText ?? \"\").trim()\n // Strip any accidental code fence if the model added one.\n const inner = raw.replace(/^```(?:mermaid)?\\n?/i, \"\").replace(/\\n?```$/, \"\").trim()\n return \"```mermaid\\n\" + inner + \"\\n```\"\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n return `<!-- Mermaid generation failed: ${msg} -->`\n }\n}\n\n// ---------------------------------------------------------------------------\n// Zod schemas\n// ---------------------------------------------------------------------------\n\n/**\n * Zod schema for the result of processing a single upstream commit.\n *\n * The agent populates one `CommitResult` per candidate commit processed during\n * the run. These are aggregated by the report tool to produce the final summary.\n */\nconst CommitResultSchema = z.object({\n /** Full SHA of the upstream commit. */\n sha: z.string(),\n /** Commit subject line (first line of the message). */\n subject: z.string(),\n /** Risk level assigned by `classify_commit_risk`. */\n riskLevel: z.enum([\"low\", \"medium\", \"high\"]),\n /**\n * Final disposition of this commit:\n * - `\"applied\"` — cherry-picked cleanly with no conflicts.\n * - `\"skipped\"` — already applied in the fork (git cherry found equivalent patch).\n * - `\"conflict-resolved\"` — had conflicts that the agent resolved automatically.\n * - `\"conflict-blocked\"` — had conflicts the agent could not safely resolve; needs human review.\n * - `\"validation-failed\"` — cherry-picked cleanly but the validation suite failed.\n */\n status: z.enum([\"applied\", \"skipped\", \"conflict-resolved\", \"conflict-blocked\", \"validation-failed\"]),\n /** Paths of files that had merge conflicts (populated for conflict-* statuses). */\n conflictedFiles: z.array(z.string()).optional(),\n /** Human-readable reasons why this commit needs manual review. */\n humanReviewReasons: z.array(z.string()).optional(),\n /** Per-command validation results, populated when `status === \"validation-failed\"`. */\n validationResults: z.array(z.object({ command: z.string(), success: z.boolean(), output: z.string() })).optional(),\n})\n\n/**\n * TypeScript type for a single commit result, inferred from `CommitResultSchema`.\n */\nexport type CommitResult = z.infer<typeof CommitResultSchema>\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Builds and returns the `generate_report` agent tool.\n *\n * @param config - Validated `SyncConfig` — used for model IDs and `report.destination`.\n * @param promptLogPath - Absolute path to the JSONL file written by `logPrompt()` in ai-tools.ts.\n * @returns A single agent tool: `generate_report` (with `completesRun: true`).\n */\nexport function makeReportTool(config: SyncConfig, promptLogPath: string) {\n return defineTool({\n name: \"generate_report\",\n description:\n \"Generate the final sync report as a Markdown string suitable for a PR body. \" +\n \"Call this as the LAST step after all commits have been processed. \" +\n \"Returns the report text AND signals that the agent run is complete.\",\n inputSchema: z.object({\n /** Name of the sync branch, or `null` in dry-run mode. */\n syncBranch: z.string().nullable(),\n /** Full ref of the upstream branch, e.g. `\"upstream/main\"`. */\n upstreamRef: z.string(),\n /** Full ref of the fork branch, e.g. `\"origin/main\"`. */\n forkRef: z.string(),\n /** Array of per-commit results — one entry per processed candidate. */\n commitResults: z.array(CommitResultSchema),\n /** Commits that were not attempted at all, with mandatory reasons. */\n blockedCommits: z.array(z.object({\n /** Full or abbreviated SHA of the blocked commit. */\n sha: z.string(),\n /** Human-readable reason why this commit was not attempted. */\n reason: z.string().describe(\"Why this commit was not attempted (AI analysis result, policy, etc.)\"),\n /** Risk level from classify_commit_risk, if known. */\n riskLevel: z.enum([\"low\", \"medium\", \"high\"]).optional(),\n })).describe(\"Commits not attempted, each with a specific reason\"),\n /**\n * All SHAs returned by list_candidate_commits — used to detect silently dropped commits.\n * Pass the complete list so the report can cross-check accountability.\n */\n allCandidateShas: z.array(z.string()).optional().describe(\n \"Complete list of SHAs from list_candidate_commits, used to detect unaccounted commits\"\n ),\n /** Ordered list of key decisions the agent made during this run, for audit purposes. */\n agentDecisions: z.array(z.string()).describe(\"Audit trail of key decisions made during this run\"),\n }),\n // completesRun:true tells the SDK to stop the agent loop after this tool returns.\n lifecycle: { completesRun: true },\n execute: async ({ syncBranch, upstreamRef, forkRef, commitResults, blockedCommits, agentDecisions, allCandidateShas }) => {\n const date = new Date().toISOString()\n const timestampSlug = date.replace(/[:.]/g, \"-\").replace(\"T\", \"_\").slice(0, 19)\n\n // --- Partition commits by final status for the summary section ---\n const applied = commitResults.filter((r) => [\"applied\", \"conflict-resolved\"].includes(r.status))\n const needsReview = commitResults.filter((r) => [\"conflict-blocked\", \"validation-failed\"].includes(r.status))\n const allPassed = needsReview.length === 0\n\n // --- Accountability check: detect silently dropped commits ---\n const processedShas = new Set([\n ...commitResults.map((r) => r.sha),\n ...blockedCommits.map((c) => c.sha),\n ])\n const unaccounted = (allCandidateShas ?? []).filter(\n (sha) => !processedShas.has(sha) && !processedShas.has(sha.slice(0, 8))\n )\n\n // --- Build the PR-body Markdown line by line ---\n const prBodyLines: string[] = [\n \"## Backport Agent — Sync Report\",\n \"\",\n `**Date**: ${date}`,\n `**Upstream ref**: \\`${upstreamRef}\\``,\n `**Fork ref**: \\`${forkRef}\\``,\n `**Sync branch**: ${syncBranch ? `\\`${syncBranch}\\`` : \"_dry-run (no branch created)_\"}`,\n \"\",\n \"### Summary\",\n \"\",\n `- ✅ Applied: ${applied.length}`,\n `- ⚠️ Needs human review: ${needsReview.length}`,\n `- ⛔ Blocked (not attempted): ${blockedCommits.length}`,\n ...(unaccounted.length > 0 ? [`- 🔴 Unaccounted (agent bug): ${unaccounted.length}`] : []),\n \"\",\n ]\n\n if (applied.length > 0) {\n prBodyLines.push(\"### Applied commits\", \"\")\n for (const r of applied) {\n const badge = r.status === \"conflict-resolved\" ? \" _(conflict resolved by agent)_\" : \"\"\n prBodyLines.push(`- \\`${r.sha.slice(0, 8)}\\` [${r.riskLevel}] ${r.subject}${badge}`)\n }\n prBodyLines.push(\"\")\n }\n\n if (needsReview.length > 0) {\n prBodyLines.push(\"### ⚠️ Human review required\", \"\")\n for (const r of needsReview) {\n prBodyLines.push(`- \\`${r.sha.slice(0, 8)}\\` ${r.subject}`)\n if (r.conflictedFiles?.length) prBodyLines.push(` - Conflicted files: ${r.conflictedFiles.join(\", \")}`)\n if (r.humanReviewReasons?.length) {\n for (const reason of r.humanReviewReasons) prBodyLines.push(` - ${reason}`)\n }\n }\n prBodyLines.push(\"\")\n }\n\n if (blockedCommits.length > 0) {\n prBodyLines.push(\"### Blocked commits (not attempted)\", \"\")\n for (const { sha, reason, riskLevel } of blockedCommits) {\n const badge = riskLevel ? ` [${riskLevel}]` : \"\"\n prBodyLines.push(`- \\`${sha.slice(0, 8)}\\`${badge} — ${reason}`)\n }\n prBodyLines.push(\"\")\n }\n\n if (unaccounted.length > 0) {\n prBodyLines.push(\"### 🔴 Unaccounted commits (agent processing gap)\", \"\")\n for (const sha of unaccounted) prBodyLines.push(`- \\`${sha.slice(0, 8)}\\` — not processed or reported by agent`)\n prBodyLines.push(\"\")\n }\n\n if (agentDecisions.length > 0) {\n prBodyLines.push(\"### Agent decision log\", \"\")\n for (const decision of agentDecisions) prBodyLines.push(`- ${decision}`)\n prBodyLines.push(\"\")\n }\n\n const report = prBodyLines.join(\"\\n\")\n\n // --- Build the machine-readable state object for idempotent re-runs ---\n const agentState = {\n generatedAt: date,\n appliedShas: applied.map((r) => r.sha),\n blockedShas: blockedCommits.map((c) => c.sha),\n needsReviewShas: needsReview.map((r) => r.sha),\n unaccountedShas: unaccounted,\n }\n\n // -----------------------------------------------------------------------\n // Detailed report file — combines PR body, Mermaid diagram, and AI call log\n // -----------------------------------------------------------------------\n\n // Build a compact plain-text run summary for the Mermaid prompt.\n const runSummaryForDiagram = [\n `Upstream: ${upstreamRef} Fork: ${forkRef}`,\n `Applied (${applied.length}): ${applied.map((r) => `${r.sha.slice(0, 8)} [${r.riskLevel}] ${r.subject}`).join(\" | \") || \"none\"}`,\n `Blocked (${blockedCommits.length}): ${blockedCommits.map((c) => `${c.sha.slice(0, 8)} — ${c.reason}`).join(\" | \") || \"none\"}`,\n `Needs review (${needsReview.length}): ${needsReview.map((r) => r.sha.slice(0, 8)).join(\", \") || \"none\"}`,\n `AI calls: ${readPromptLog(promptLogPath).map((e) => `${e.tool}(${e.model}, ${e.durationMs}ms)`).join(\", \") || \"none\"}`,\n ].join(\"\\n\")\n\n const mermaidBlock = await generateMermaidDiagram(config, runSummaryForDiagram)\n\n // Read and format the prompt log entries.\n const promptEntries = readPromptLog(promptLogPath)\n\n const detailedLines: string[] = [\n \"# Backport Agent — Detailed Run Report\",\n \"\",\n \"> This file is generated automatically. It is intended for human analysts and\",\n \"> AI reasoning models to assess agent performance, decision quality, and LLM behavior.\",\n \"\",\n `**Run timestamp**: ${date}`,\n `**Models used**: fast=\\`${config.models.fast}\\` powerful=\\`${config.models.powerful}\\``,\n `**Prompt log**: \\`${promptLogPath}\\``,\n \"\",\n \"---\",\n \"\",\n \"## Summary (PR body)\",\n \"\",\n report,\n \"\",\n \"---\",\n \"\",\n \"## Agent Workflow Diagram\",\n \"\",\n \"> Generated by the fast model from the run summary. Evaluate the agent's decision path.\",\n \"\",\n mermaidBlock,\n \"\",\n \"---\",\n \"\",\n `## AI Sub-Agent Call Log (${promptEntries.length} call${promptEntries.length === 1 ? \"\" : \"s\"})`,\n \"\",\n \"> Each entry is one sub-agent invocation. Review prompt/response pairs to assess\",\n \"> reasoning quality, hallucinations, and decision accuracy.\",\n \"\",\n ]\n\n for (let i = 0; i < promptEntries.length; i++) {\n const e = promptEntries[i]\n const statusBadge = e.error ? \"❌ Error\" : \"✅ OK\"\n detailedLines.push(\n `### Call ${i + 1} / ${promptEntries.length} — \\`${e.tool}\\` ${statusBadge}`,\n \"\",\n `| Field | Value |`,\n `|---|---|`,\n `| **Timestamp** | ${e.timestamp} |`,\n `| **Tool** | \\`${e.tool}\\` |`,\n `| **Model** | \\`${e.model}\\` |`,\n `| **Duration** | ${e.durationMs} ms |`,\n ...(e.inputTokens != null ? [`| **Tokens in** | ${e.inputTokens} |`] : []),\n ...(e.outputTokens != null ? [`| **Tokens out** | ${e.outputTokens} |`] : []),\n ...(e.cacheReadTokens != null && e.cacheReadTokens > 0 ? [`| **Cache read** | ${e.cacheReadTokens} |`] : []),\n ...(e.cacheWriteTokens != null && e.cacheWriteTokens > 0 ? [`| **Cache write** | ${e.cacheWriteTokens} |`] : []),\n ...(e.totalCost != null ? [`| **Cost** | $${e.totalCost.toFixed(6)} |`] : []),\n ...(e.error ? [`| **Error** | ${e.error} |`] : []),\n \"\",\n \"**Prompt sent to sub-agent:**\",\n \"\",\n safeFence(e.prompt),\n e.prompt,\n safeFence(e.prompt),\n \"\",\n \"**Response received:**\",\n \"\",\n safeFence(e.response || \"(empty)\"),\n e.response || \"(empty)\",\n safeFence(e.response || \"(empty)\"),\n \"\",\n \"---\",\n \"\",\n )\n }\n\n if (promptEntries.length === 0) {\n detailedLines.push(\"_No AI sub-agent calls were made during this run._\", \"\", \"---\", \"\")\n }\n\n // Performance summary table.\n if (promptEntries.length > 0) {\n const totalMs = promptEntries.reduce((sum, e) => sum + e.durationMs, 0)\n const totalIn = promptEntries.reduce((sum, e) => sum + (e.inputTokens ?? 0), 0)\n const totalOut = promptEntries.reduce((sum, e) => sum + (e.outputTokens ?? 0), 0)\n const totalCost = promptEntries.reduce((sum, e) => sum + (e.totalCost ?? 0), 0)\n const byTool = new Map<string, { count: number; totalMs: number; totalIn: number; totalOut: number }>()\n for (const e of promptEntries) {\n const cur = byTool.get(e.tool) ?? { count: 0, totalMs: 0, totalIn: 0, totalOut: 0 }\n byTool.set(e.tool, {\n count: cur.count + 1,\n totalMs: cur.totalMs + e.durationMs,\n totalIn: cur.totalIn + (e.inputTokens ?? 0),\n totalOut: cur.totalOut + (e.outputTokens ?? 0),\n })\n }\n const hasTokenData = totalIn > 0 || totalOut > 0\n detailedLines.push(\n \"## Performance Summary\",\n \"\",\n `**Total AI time**: ${totalMs} ms across ${promptEntries.length} call(s)`,\n ...(hasTokenData ? [\n `**Total input tokens**: ${totalIn}`,\n `**Total output tokens**: ${totalOut}`,\n ...(totalCost > 0 ? [`**Total estimated cost**: $${totalCost.toFixed(6)}`] : []),\n ] : []),\n \"\",\n hasTokenData\n ? \"| Tool | Calls | Total ms | Avg ms | Input tok | Output tok |\"\n : \"| Tool | Calls | Total ms | Avg ms |\",\n hasTokenData ? \"|---|---|---|---|---|---|\" : \"|---|---|---|---|\",\n ...[...byTool.entries()].map(\n ([tool, s]) => hasTokenData\n ? `| \\`${tool}\\` | ${s.count} | ${s.totalMs} | ${Math.round(s.totalMs / s.count)} | ${s.totalIn} | ${s.totalOut} |`\n : `| \\`${tool}\\` | ${s.count} | ${s.totalMs} | ${Math.round(s.totalMs / s.count)} |`,\n ),\n \"\",\n )\n }\n\n // Write the detailed report to disk (skipped in dry-run mode).\n if (config.sync.dryRun) {\n process.stderr.write(\"[Report] Dry-run mode — detailed report not written to disk.\\n\")\n } else {\n try {\n const destDir = resolvePath(config.workingDir, config.report.destination)\n mkdirSync(destDir, { recursive: true })\n const reportFilename = `report.${timestampSlug}.md`\n const reportFilePath = joinPath(destDir, reportFilename)\n writeFileSync(reportFilePath, detailedLines.join(\"\\n\"), \"utf8\")\n process.stderr.write(`[Report] Detailed report written to: ${reportFilePath}\\n`)\n\n // Commit and push the report file to the sync branch if inside the repo.\n const relPath = relativePath(config.workingDir, reportFilePath)\n if (syncBranch && !relPath.startsWith(\"..\")) {\n git([\"add\", relPath], config.workingDir)\n git([\"commit\", \"-m\", `chore(backport): add run report ${reportFilename}`], config.workingDir)\n git([\"push\", config.fork.remote, syncBranch], config.workingDir)\n process.stderr.write(`[Report] Report committed and pushed to ${config.fork.remote}/${syncBranch}\\n`)\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n process.stderr.write(`[Report] Warning: could not write/commit detailed report: ${msg}\\n`)\n }\n }\n\n return { report, agentState, allPassed, needsHumanReview: needsReview.length > 0 }\n },\n })\n}\n\n","/**\n * @file ai/ai-tools.ts\n *\n * AI-powered agent tools that spawn focused sub-agents for tasks requiring\n * deep language model reasoning beyond what the deterministic rule engine\n * can provide.\n *\n * **Why sub-agents?**\n * The main backport agent drives the orchestration loop and calls deterministic\n * git/GitHub/validation tools. Some decisions — conflict resolution, semantic\n * understanding of diffs, customisation compatibility — benefit from a dedicated\n * LLM call with a narrowly scoped system prompt and no distracting tool context.\n * Spawning a sub-`Agent` with `tools: []` gives exactly that: a single-turn\n * reasoning call that returns structured JSON.\n *\n * **Tools exported by `makeAiTools`:**\n *\n * 1. `resolve_conflict_with_ai` — Given a three-way conflict (base / ours /\n * theirs), produce a resolved file body with no conflict markers.\n * Uses `config.models.powerful` for maximum reasoning quality.\n *\n * 2. `analyze_commit_for_backport` — Given a commit SHA, message, diff, and\n * changed-file list, produce a structured semantic assessment of what the\n * commit does and how risky it is to backport.\n * Uses `config.models.fast` (analytical but not critical-path).\n *\n * 3. `check_customization_compatibility` — Given a diff and a list of\n * customisation records (pattern + description), reason about whether the\n * upstream changes could semantically break fork-specific behaviour even\n * if no textual conflict exists.\n * Uses `config.models.fast`.\n *\n * **JSON extraction:**\n * Sub-agents are instructed to emit only a JSON object. In practice, some\n * models wrap the object in a markdown code fence. `extractJson` strips the\n * fence when present so `JSON.parse` always receives clean text.\n *\n * **Error handling:**\n * If the sub-agent call fails (network error, model error, parse error), each\n * tool returns a structured fallback object with `error` set rather than\n * throwing, so the main agent can log the failure and continue.\n */\n\nimport { z } from \"zod\"\nimport { Agent } from \"@sctg/cline-sdk\"\nimport { defineTool } from \"../tool-helper.js\"\nimport type { SyncConfig } from \"../config/schema.js\"\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Attempts to extract a JSON value from raw LLM output.\n *\n * The LLM may wrap its JSON in a markdown code fence (```` ```json … ``` ````\n * or ```` ``` … ``` ````). This helper strips the fences when present so\n * `JSON.parse` receives valid JSON text.\n *\n * @typeParam T - Expected shape of the parsed value.\n * @param text - Raw string output from the sub-agent.\n * @returns The parsed value cast to `T`.\n * @throws `SyntaxError` if no valid JSON can be found in the text.\n */\nfunction extractJson<T>(text: string): T {\n // 1. Try ```json … ``` code fence (most common for structured output)\n const jsonFenceMatch = text.match(/```json\\s*([\\s\\S]*?)```/)\n if (jsonFenceMatch) {\n return JSON.parse(jsonFenceMatch[1].trim()) as T\n }\n\n // 2. Try generic ``` … ``` code fence\n const genericFenceMatch = text.match(/```\\s*([\\s\\S]*?)```/)\n if (genericFenceMatch) {\n return JSON.parse(genericFenceMatch[1].trim()) as T\n }\n\n // 3. Try to find an inline JSON object `{…}` in the text\n const inlineObjectMatch = text.match(/\\{[\\s\\S]*\\}/)\n if (inlineObjectMatch) {\n return JSON.parse(inlineObjectMatch[0]) as T\n }\n\n // 4. Last resort: parse the full trimmed text\n return JSON.parse(text.trim()) as T\n}\n\n// ---------------------------------------------------------------------------\n// PromptLogger — records all sub-agent LLM interactions to a JSONL file\n// ---------------------------------------------------------------------------\n\nimport { appendFileSync } from \"node:fs\"\n\n/**\n * Token usage breakdown from a single sub-agent LLM call.\n */\ninterface TokenUsage {\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheWriteTokens: number\n totalCost?: number\n}\n\n/**\n * Appends a single prompt/response record to the run's JSONL log file.\n *\n * The log file path is `./run-<timestamp>.prompts.jsonl` (relative to cwd).\n * It is created on first write and appended on subsequent calls.\n * Each line is a self-contained JSON object — suitable for streaming analysis\n * and incremental loading without parsing the entire file.\n *\n * @param logPath - Absolute path to the JSONL log file for this run.\n * @param toolName - The backport agent tool that invoked the sub-agent.\n * @param modelId - LLM model identifier used for this call.\n * @param prompt - Full user prompt sent to the sub-agent.\n * @param response - Raw text response received from the sub-agent.\n * @param durationMs - Wall-clock time for the sub-agent call in milliseconds.\n * @param error - Optional error message if the call failed.\n * @param usage - Optional token usage breakdown from the LLM response.\n */\nfunction logPrompt(\n logPath: string,\n toolName: string,\n modelId: string,\n prompt: string,\n response: string,\n durationMs: number,\n error?: string | null,\n usage?: TokenUsage | null,\n): void {\n const record = {\n timestamp: new Date().toISOString(),\n tool: toolName,\n model: modelId,\n durationMs,\n prompt,\n response,\n ...(error ? { error } : {}),\n ...(usage ? {\n inputTokens: usage.inputTokens,\n outputTokens: usage.outputTokens,\n cacheReadTokens: usage.cacheReadTokens,\n cacheWriteTokens: usage.cacheWriteTokens,\n ...(usage.totalCost != null ? { totalCost: usage.totalCost } : {}),\n } : {}),\n }\n try {\n appendFileSync(logPath, JSON.stringify(record) + \"\\n\", \"utf8\")\n } catch {\n // Log write failures are non-fatal — the agent run must not be blocked.\n process.stderr.write(`[PromptLogger] Warning: could not write to ${logPath}\\n`)\n }\n}\n\n/**\n * Creates a minimal sub-`Agent` configured with the keypoollive provider.\n *\n * The sub-agent has an empty tools array — it performs a single reasoning turn\n * and returns its text output via `result.outputText`.\n *\n * @param modelId - Model identifier to use (fast or powerful).\n * @param systemPrompt - System prompt that scopes the sub-agent's behaviour.\n * @returns A configured `Agent` instance ready to call `.run(userPrompt)`.\n */\nfunction makeSubAgent(modelId: string, systemPrompt: string): Agent {\n return new Agent({\n providerId: \"keypoollive\",\n modelId,\n apiKey: \"auto\",\n systemPrompt,\n tools: [],\n })\n}\n\n// ---------------------------------------------------------------------------\n// Exported factory\n// ---------------------------------------------------------------------------\n\n/**\n * Builds and returns all AI-powered agent tools pre-bound to the provided config.\n *\n * @param config - Validated `SyncConfig` loaded from `config.json`.\n * @param logPath - Absolute path to the JSONL prompt log file for this run.\n * Created by `main.ts` as `run-<timestamp>.prompts.jsonl`.\n * @returns Array of three agent tools for AI-assisted analysis.\n */\nexport function makeAiTools(config: SyncConfig, logPath: string) {\n // -------------------------------------------------------------------------\n // Tool 1: resolve_conflict_with_ai\n // -------------------------------------------------------------------------\n\n /**\n * Tool: resolve_conflict_with_ai\n *\n * Spawns a focused sub-agent to produce a conflict-free merged version of a\n * file that has a three-way merge conflict.\n *\n * The sub-agent receives the base (common ancestor), our (fork) version, and\n * their (upstream) version of the file, together with the upstream commit\n * message for context. It reasons about the intent of each side and returns\n * a merged body with no conflict markers.\n *\n * Uses `config.models.powerful` because incorrect conflict resolution can\n * silently break fork-specific behaviour.\n */\n const resolveConflictTool = defineTool({\n name: \"resolve_conflict_with_ai\",\n description:\n \"Resolves a three-way merge conflict in a single file using AI reasoning. \" +\n \"Provide the base (common ancestor), our (fork) version, and their (upstream) version \" +\n \"of the file, plus the upstream commit message for context. \" +\n \"Returns the resolved file content with no conflict markers, a confidence level, \" +\n \"and a brief reasoning summary. \" +\n \"Use this when cherry_pick_commit reports a conflict and you need to resolve a specific file.\",\n inputSchema: z.object({\n /**\n * Repository-relative path to the conflicted file (e.g. `src/core/api/index.ts`).\n * Used only for display/reasoning context — no filesystem access is performed.\n */\n filePath: z.string().describe(\"Repo-relative path of the conflicted file\"),\n\n /**\n * Content of the file at the common ancestor commit (merge base).\n * May be an empty string when the file was created on both branches independently.\n */\n baseContent: z.string().describe(\"File content at the common ancestor (merge base); empty string if none\"),\n\n /**\n * Content of the file in the fork branch (our version, with our customisations).\n */\n ourContent: z.string().describe(\"File content in the fork branch (our customised version)\"),\n\n /**\n * Content of the file in the upstream commit (their version).\n */\n theirContent: z.string().describe(\"File content from the upstream commit (their version)\"),\n\n /**\n * The full commit message of the upstream change being cherry-picked.\n * Helps the model understand the intent of the upstream change.\n */\n commitMessage: z.string().describe(\"Upstream commit message, used to understand the intent of the change\"),\n\n /**\n * Optional: a human-readable note describing relevant fork customisations\n * in this file (e.g. \"This file contains the keypoollive provider registration\").\n * When provided, the model uses it to decide which parts must not be overwritten.\n */\n customizationNote: z\n .string()\n .optional()\n .describe(\"Optional description of fork customisations in this file to help preserve them\"),\n }),\n execute: async ({ filePath, baseContent, ourContent, theirContent, commitMessage, customizationNote }) => {\n /**\n * System prompt focuses the sub-agent exclusively on conflict resolution.\n * The model must output only a single JSON object.\n */\n const systemPrompt = [\n \"You are an expert software engineer specialising in Git merge conflict resolution.\",\n \"Your sole task is to produce a clean merged version of a conflicted file.\",\n \"\",\n \"Rules:\",\n \"- Output ONLY a valid JSON object. No prose, no explanations outside the JSON.\",\n '- The JSON must have exactly three fields: \"resolvedContent\", \"confidence\", \"reasoning\".',\n '- \"resolvedContent\": the complete resolved file content as a string. MUST contain zero conflict markers (<<<<<<<, =======, >>>>>>>).',\n '- \"confidence\": one of \"high\", \"medium\", or \"low\".',\n ' - \"high\": you are certain the resolution is correct and preserves all intent.',\n ' - \"medium\": the resolution is plausible but you had to make a judgment call.',\n ' - \"low\": the resolution is uncertain; a human should review it.',\n '- \"reasoning\": a single sentence explaining the key decision you made.',\n \"\",\n \"Resolution strategy:\",\n \"1. Understand what the UPSTREAM change was trying to achieve (read the commit message).\",\n \"2. Understand what the FORK version preserves (customisations, local patches).\",\n \"3. Integrate both intents: keep fork customisations AND apply the upstream change where safe.\",\n \"4. When in doubt, prefer preserving fork customisations and mark confidence as 'low'.\",\n ].join(\"\\n\")\n\n const customizationSection = customizationNote\n ? `\\nFork customisation context:\\n${customizationNote}\\n`\n : \"\"\n\n const userPrompt =\n `Resolve the merge conflict in file: ${filePath}\\n` +\n `Upstream commit message: ${commitMessage}\\n` +\n customizationSection +\n `\\n--- BASE (common ancestor) ---\\n${baseContent || \"(file did not exist at merge base)\"}\\n` +\n `\\n--- OURS (fork version) ---\\n${ourContent}\\n` +\n `\\n--- THEIRS (upstream version) ---\\n${theirContent}\\n` +\n `\\nOutput the JSON object now.`\n\n // Try specialist model first; fall back to powerful on any failure.\n const modelsToTry = [\n { modelId: config.models.specialist, label: \"specialist\" },\n { modelId: config.models.powerful, label: \"powerful\" },\n ]\n\n let lastError: string | null = null\n for (const { modelId, label } of modelsToTry) {\n try {\n const subAgent = makeSubAgent(modelId, systemPrompt)\n const t0 = Date.now()\n const result = await subAgent.run(userPrompt)\n const durationMs = Date.now() - t0\n logPrompt(logPath, \"resolve_conflict_with_ai\", modelId, userPrompt, result.outputText ?? \"\", durationMs, null, result.usage)\n const output = extractJson<{\n resolvedContent: string\n confidence: \"high\" | \"medium\" | \"low\"\n reasoning: string\n }>(result.outputText ?? \"\")\n\n return {\n resolvedContent: output.resolvedContent,\n confidence: output.confidence,\n reasoning: output.reasoning,\n error: null,\n }\n } catch (err) {\n lastError = err instanceof Error ? err.message : String(err)\n process.stderr.write(\n `[resolve_conflict_with_ai] ${label} model (${modelId}) failed: ${lastError.slice(0, 120)} — ${\n label === \"specialist\" ? \"retrying with powerful model\" : \"giving up\"\n }\\n`,\n )\n logPrompt(logPath, \"resolve_conflict_with_ai\", modelId, userPrompt, \"\", 0, lastError)\n }\n }\n\n return {\n resolvedContent: \"\",\n confidence: \"low\" as const,\n reasoning: `AI resolution failed on all models: ${lastError}`,\n error: lastError,\n }\n },\n })\n\n // -------------------------------------------------------------------------\n // Tool 2: analyze_commit_for_backport\n // -------------------------------------------------------------------------\n\n /**\n * Tool: analyze_commit_for_backport\n *\n * Spawns a sub-agent to produce a semantic assessment of an upstream commit\n * and its implications for the fork.\n *\n * Unlike the deterministic `classify_commit_risk` tool, this tool reasons\n * about the *intent* of the commit: what architectural or behavioural change\n * it introduces, whether it touches concepts relevant to the fork's\n * customisations, and what the recommended backport action is.\n *\n * Uses `config.models.fast` (analytical, not on the critical path).\n */\n const analyzeCommitTool = defineTool({\n name: \"analyze_commit_for_backport\",\n description:\n \"Performs a semantic analysis of an upstream commit to understand its intent and \" +\n \"assess how complex it is to backport into the fork. \" +\n \"Returns a structured assessment with a human-readable summary, key changes, \" +\n \"complexity rating, semantic risk factors, and a backport recommendation. \" +\n \"Use this before cherry-picking a high-risk commit to better understand what it does.\",\n inputSchema: z.object({\n /**\n * Full commit SHA (40-character hex string).\n */\n sha: z.string().describe(\"Full commit SHA\"),\n\n /**\n * The commit's subject + body as returned by `git log --format=%B`.\n */\n commitMessage: z.string().describe(\"Full commit message (subject + body)\"),\n\n /**\n * Full unified diff of the commit as returned by `git show --format=` or\n * `git diff <parent> <sha>`.\n */\n diff: z.string().describe(\"Full unified diff of the commit\"),\n\n /**\n * List of file paths changed by this commit (relative to repo root).\n */\n changedFiles: z.array(z.string()).describe(\"List of file paths changed by this commit\"),\n }),\n execute: async ({ sha, commitMessage, diff, changedFiles }) => {\n const systemPrompt = [\n \"You are an expert software engineer specialising in Git history analysis.\",\n \"Your task is to analyse a single upstream commit and assess how complex it is to\",\n \"backport it into a heavily customised fork.\",\n \"\",\n \"Output ONLY a valid JSON object with exactly these fields:\",\n ' \"summary\": string — 2-3 sentence description of what this commit does.',\n ' \"keyChanges\": string[] — bullet-point list of the most important code changes.',\n ' \"backportComplexity\": \"trivial\" | \"moderate\" | \"complex\"',\n ' - \"trivial\": small, isolated change with no side effects.',\n ' - \"moderate\": meaningful change but scope is clear and contained.',\n ' - \"complex\": refactor, API change, or broad change that may interact with customisations.',\n ' \"semanticRiskFactors\": string[] — list of reasons why this commit could break a fork',\n ' (e.g. \"renames exported interface\", \"changes provider registration pattern\").',\n ' Empty array if no risks detected.',\n ' \"recommendation\": \"apply\" | \"apply-with-care\" | \"review-required\" | \"skip\"',\n ' - \"apply\": safe to cherry-pick automatically.',\n ' - \"apply-with-care\": cherry-pick but verify validation passes.',\n ' - \"review-required\": human should review before merging.',\n ' - \"skip\": commit should not be backported.',\n ].join(\"\\n\")\n\n const userPrompt =\n `Commit SHA: ${sha}\\n` +\n `Commit message:\\n${commitMessage}\\n\\n` +\n `Changed files (${changedFiles.length}):\\n${changedFiles.join(\"\\n\")}\\n\\n` +\n `Diff:\\n${diff}\\n\\n` +\n `Output the JSON object now.`\n\n try {\n const subAgent = makeSubAgent(config.models.fast, systemPrompt)\n const t0 = Date.now()\n const result = await subAgent.run(userPrompt)\n const durationMs = Date.now() - t0\n logPrompt(logPath, \"analyze_commit_for_backport\", config.models.fast, userPrompt, result.outputText ?? \"\", durationMs, null, result.usage)\n const output = extractJson<{\n summary: string\n keyChanges: string[]\n backportComplexity: \"trivial\" | \"moderate\" | \"complex\"\n semanticRiskFactors: string[]\n recommendation: \"apply\" | \"apply-with-care\" | \"review-required\" | \"skip\"\n }>(result.outputText ?? \"\")\n\n return {\n summary: output.summary,\n keyChanges: output.keyChanges,\n backportComplexity: output.backportComplexity,\n semanticRiskFactors: output.semanticRiskFactors,\n recommendation: output.recommendation,\n error: null,\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logPrompt(logPath, \"analyze_commit_for_backport\", config.models.fast, userPrompt, \"\", 0, message)\n return {\n summary: `Analysis failed: ${message}`,\n keyChanges: [],\n backportComplexity: \"complex\" as const,\n semanticRiskFactors: [`AI analysis unavailable: ${message}`],\n recommendation: \"review-required\" as const,\n error: message,\n }\n }\n },\n })\n\n // -------------------------------------------------------------------------\n // Tool 3: check_customization_compatibility\n // -------------------------------------------------------------------------\n\n /**\n * Tool: check_customization_compatibility\n *\n * Spawns a sub-agent to reason about whether an upstream diff is semantically\n * compatible with the fork's declared customisations.\n *\n * The deterministic `classify_commit_risk` tool matches file globs to flag\n * risky commits. This tool goes further: it reads the *descriptions* of\n * customisations and asks the model whether the upstream change could break\n * the described behaviour, even if the changed files don't match the glob.\n *\n * Uses `config.models.fast` (fast reasoning, results used to augment the\n * main agent's risk assessment rather than as a hard gate).\n */\n const checkCompatibilityTool = defineTool({\n name: \"check_customization_compatibility\",\n description:\n \"Checks whether an upstream diff is semantically compatible with the fork's customisations. \" +\n \"Goes beyond file-path glob matching: the AI reads the customisation descriptions and \" +\n \"reasons about whether the upstream change could break declared fork-specific behaviour. \" +\n \"Returns a compatibility verdict, a list of affected customisations, semantic conflicts, \" +\n \"warnings, and a recommendation. \" +\n \"Use this when classify_commit_risk returns 'medium' or 'high' and you want deeper insight.\",\n inputSchema: z.object({\n /**\n * Full unified diff of the upstream commit being evaluated.\n */\n diff: z.string().describe(\"Full unified diff of the upstream commit\"),\n\n /**\n * List of customisation entries loaded from `customizations.yaml`.\n * Each entry has a glob pattern (identifying which files are customised)\n * and a human-readable description of what the customisation does.\n */\n customizations: z\n .array(\n z.object({\n /**\n * Glob pattern (e.g. `src/core/api/providers/**`) identifying files\n * that belong to this customisation zone.\n */\n pattern: z.string().describe(\"Glob pattern for customised file paths\"),\n /**\n * Human-readable description of what this customisation does and why\n * it must be preserved.\n */\n description: z.string().describe(\"Description of the fork customisation\"),\n }),\n )\n .describe(\"Customisation entries from customizations.yaml\"),\n }),\n execute: async ({ diff, customizations }) => {\n /**\n * If there are no customisations defined, there is nothing to check.\n * Return a trivially compatible result without calling the LLM.\n */\n if (customizations.length === 0) {\n return {\n compatible: true,\n affectedCustomizations: [],\n semanticConflicts: [],\n warnings: [],\n recommendation: \"No customisations defined; upstream change is safe to apply.\",\n error: null,\n }\n }\n\n const customizationList = customizations\n .map((c, i) => ` ${i + 1}. Pattern: ${c.pattern}\\n Description: ${c.description}`)\n .join(\"\\n\")\n\n const systemPrompt = [\n \"You are an expert software engineer specialising in fork maintenance and semantic conflict detection.\",\n \"Your task is to assess whether an upstream Git diff could break the customised behaviour\",\n \"of a fork, given a list of declared customisations.\",\n \"\",\n \"Output ONLY a valid JSON object with exactly these fields:\",\n ' \"compatible\": boolean — true if the upstream change is unlikely to break any customisation.',\n ' \"affectedCustomizations\": string[] — names/patterns of customisations potentially affected.',\n ' \"semanticConflicts\": string[] — specific ways the upstream change could break fork behaviour.',\n ' Each entry is a concrete description (e.g. \"renames ApiProvider enum',\n ' value used by keypoollive provider registration\").',\n ' Empty array if no conflicts detected.',\n ' \"warnings\": string[] — non-blocking concerns worth noting (e.g. \"touches shared types',\n ' used by customised components\").',\n ' Empty array if none.',\n ' \"recommendation\": string — one sentence advising the agent what to do.',\n \"\",\n \"Important: focus on SEMANTIC compatibility, not textual conflicts.\",\n \"A change can be textually clean but still break the fork (e.g. renaming an interface\",\n \"that the fork's custom provider implements).\",\n ].join(\"\\n\")\n\n const userPrompt =\n `Declared fork customisations:\\n${customizationList}\\n\\n` +\n `Upstream diff to evaluate:\\n${diff}\\n\\n` +\n `Output the JSON object now.`\n\n try {\n const subAgent = makeSubAgent(config.models.fast, systemPrompt)\n const t0 = Date.now()\n const result = await subAgent.run(userPrompt)\n const durationMs = Date.now() - t0\n logPrompt(logPath, \"check_customization_compatibility\", config.models.fast, userPrompt, result.outputText ?? \"\", durationMs, null, result.usage)\n const output = extractJson<{\n compatible: boolean\n affectedCustomizations: string[]\n semanticConflicts: string[]\n warnings: string[]\n recommendation: string\n }>(result.outputText ?? \"\")\n\n return {\n compatible: output.compatible,\n affectedCustomizations: output.affectedCustomizations,\n semanticConflicts: output.semanticConflicts,\n warnings: output.warnings,\n recommendation: output.recommendation,\n error: null,\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logPrompt(logPath, \"check_customization_compatibility\", config.models.fast, userPrompt, \"\", 0, message)\n return {\n compatible: false,\n affectedCustomizations: [],\n semanticConflicts: [],\n warnings: [`AI compatibility check failed: ${message}`],\n recommendation: \"Treat as potentially incompatible; request human review.\",\n error: message,\n }\n }\n },\n })\n\n // Return all three tools in a single array for spreading into the main Agent.\n return [resolveConflictTool, analyzeCommitTool, checkCompatibilityTool]\n}\n","/**\n * @file main.ts\n *\n * Entry point for the Backport Agent CLI.\n *\n * **Initialization sequence:**\n * 1. Parse CLI arguments (`--verbose`, `--config`, `--backport-customizations`,\n * `--keypool-vault-url`, `--keypool-live-secret`, `--keypool-state-file`, `--dry-run`).\n * 2. Validate required environment variables (`KEYPOOL_VAULT_URL` or `KEYPOOL_LIVE_SECRET`).\n * 3. Load and validate `config.json` via `loadConfig()`.\n * 4. Load and validate `customizations.yaml` via `loadCustomizations()`.\n * 5. Assemble all agent tools from the individual factory functions.\n * 6. Instantiate the `Agent` with the keypoollive provider, system prompt, and tools.\n * 7. Subscribe to runtime events to stream assistant output to stdout.\n * 8. Call `agent.run(task)` with the sync task description.\n * 9. Print the final report (or exit with code 1 on any fatal error).\n *\n * **Provider:**\n * `keypoollive` with `apiKey: \"auto\"` uses the `KEYPOOL_VAULT_URL` environment\n * variable to resolve API keys at runtime via an encrypted vault, enabling\n * automatic key rotation without storing secrets in the codebase.\n */\n/// <reference types=\"node\" />\n// ---------------------------------------------------------------------------\n// CLI argument parsing — runs before .env loading so flags can override env.\n// ---------------------------------------------------------------------------\n{\n const argv = process.argv.slice(2)\n function getArgValue(name: string): string | undefined {\n const idx = argv.indexOf(name)\n return idx !== -1 && idx + 1 < argv.length ? argv[idx + 1] : undefined\n }\n function hasFlag(name: string): boolean {\n return argv.includes(name)\n }\n\n if (hasFlag(\"--verbose\")) process.env.VERBOSE = \"true\"\n if (hasFlag(\"--dry-run\")) process.env.DRY_RUN = \"true\"\n const cliConfig = getArgValue(\"--config\")\n if (cliConfig) process.env._CLI_CONFIG_PATH = cliConfig\n const cliCustomizations = getArgValue(\"--backport-customizations\")\n if (cliCustomizations) process.env.BACKPORT_CUSTOMIZATIONS = cliCustomizations\n const cliVaultUrl = getArgValue(\"--keypool-vault-url\")\n if (cliVaultUrl) process.env.KEYPOOL_VAULT_URL = cliVaultUrl\n const cliLiveSecret = getArgValue(\"--keypool-live-secret\")\n if (cliLiveSecret) process.env.KEYPOOL_LIVE_SECRET = cliLiveSecret\n const cliStateFile = getArgValue(\"--keypool-state-file\")\n if (cliStateFile) process.env.KEYPOOL_STATE_FILE = cliStateFile\n}\n// Load .env file if present — allows setting KEYPOOL_VAULT_URL, KEYPOOL_LIVE_SECRET,\n// BACKPORT_CUSTOMIZATIONS, etc. without modifying the shell environment.\n// Uses Node.js 20.6+ built-in --env-file support via the `dotenv` fallback.\nimport { existsSync } from \"node:fs\"\nimport { resolve as resolvePath } from \"node:path\"\n{\n const envPath = resolvePath(process.cwd(), \".env\")\n if (existsSync(envPath)) {\n const { config } = await import(\"dotenv\")\n config({ path: envPath })\n }\n}\nimport { Agent, createBuiltinTools, createUserInstructionConfigService } from \"@sctg/cline-sdk\"\nimport type { UserInstructionConfigRecord } from \"@sctg/cline-sdk\"\nimport { loadConfig } from \"./config/loader.js\"\nimport { loadCustomizations } from \"./customizations/loader.js\"\nimport { makeGitTools } from \"./git/git-tools.js\"\nimport { applyGitAuth, ensureWorkingDir } from \"./git/git-init.js\"\nimport { makeRiskTool } from \"./risk/risk-tools.js\"\nimport { makeValidationTool } from \"./validation/validation-tools.js\"\nimport { makeGitHubTools } from \"./github/github-tools.js\"\nimport { makeReportTool } from \"./reports/report-tools.js\"\nimport { makeAiTools } from \"./ai/ai-tools.js\"\n\n// ---------------------------------------------------------------------------\n// System prompt — defines the agent's responsibilities and constraints.\n// This is a multi-line template string embedded directly in main.ts so that\n// the full agent workflow is visible in a single place for auditability.\n// ---------------------------------------------------------------------------\nconst SYSTEM_PROMPT = `You are the Backport Agent, a specialist in safely synchronizing a customized Git fork with its upstream repository.\n\n## Your mission\nIntegrate upstream commits into the fork branch while preserving all fork-specific customizations.\nProduce a draft pull request with a clear report. Never push directly to the main branch.\n\n## Core workflow (follow this exactly)\n\n1. Call fetch_remotes to ensure refs are up to date.\n2. Call list_candidate_commits to get pending upstream commits (already filtered, newest-last).\n - Record all returned SHAs immediately. You are accountable for every single one.\n3. For each candidate commit (process ALL of them — no silent skips):\n a. Call get_commit_details to inspect changed files and diff.\n b. Call classify_commit_risk to determine risk level deterministically.\n c. Risk-based decision:\n - LOW risk: proceed directly to step 5 (cherry-pick). No AI analysis needed.\n - MEDIUM risk: call analyze_commit_for_backport for context, then proceed to cherry-pick.\n - HIGH risk (touches a customization zone):\n * MANDATORY: Call check_customization_compatibility — pass the diff and all affected customization IDs.\n * MANDATORY: Call analyze_commit_for_backport — pass sha, message, diff, and changed files.\n * Read both responses carefully:\n - If both tools confirm the change is SAFE or ORTHOGONAL to the customization (e.g., it modifies a\n different provider, unrelated docs section, or infrastructure that doesn't overlap with fork code):\n → proceed to cherry-pick (step 5). Do NOT block on risk level alone.\n - If the tools identify a genuine semantic conflict (same code paths, incompatible invariants):\n → add to blockedCommits with a precise reason from the AI analysis.\n - If uncertain: still attempt the cherry-pick; conflicts will surface in step 5c.\n d. Commits with alreadyApplied: true → record as \"skipped\" in commitResults.\n4. Create the sync branch via create_sync_branch (once, before first cherry-pick).\n5. For each non-skipped commit (process lowest risk first):\n a. Call cherry_pick_commit.\n b. If success: record as \"applied\" in commitResults and proceed to next.\n c. If conflicts: for each conflicted file, call get_conflict_context, then attempt resolution.\n - Check the \\`forcedStrategy\\` field returned by get_conflict_context:\n * \\`forcedStrategy: \"ours\"\\` → use \\`forkVersion\\` directly as resolvedContent; call apply_resolved_file immediately. No AI call needed.\n * \\`forcedStrategy: \"theirs\"\\` → use \\`upstreamVersion\\` directly as resolvedContent; call apply_resolved_file immediately. No AI call needed.\n * \\`forcedStrategy: null\\` → proceed with AI resolution below.\n - (When forcedStrategy is null) Call resolve_conflict_with_ai with the base/ours/theirs content to get an AI-proposed resolution.\n - If confidence is \"high\" or \"medium\": verify no conflict markers remain, then call apply_resolved_file, then continue_cherry_pick.\n - If confidence is \"low\" or the tool returned an error: call abort_cherry_pick, mark commit as conflict-blocked.\n6. Call run_validation with the highest risk level encountered in this run.\n7. If validation fails: note it in the report, mark relevant commits as validation-failed.\n8. Call push_sync_branch (unless dry-run).\n9. Call find_existing_sync_pr to check for an existing PR.\n10. Call generate_report with the full summary of all decisions.\n11. Call create_sync_pr with the report as body (unless an existing PR was found and up to date).\n\n## Accountability (enforced — never skip)\n- You received a finite list of SHAs from list_candidate_commits.\n- EVERY SHA must appear in generate_report: either in commitResults (as applied/skipped/conflict-blocked/validation-failed) OR in blockedCommits.\n- No commit may be silently dropped. If you are unsure what to do with a commit, add it to blockedCommits with reason \"deferred: needs human triage\".\n- blockedCommits entries MUST include a specific human-readable reason (not just the SHA).\n- Pass allCandidateShas to generate_report — it cross-checks accountability automatically.\n\n## Hard constraints (never violate)\n- NEVER block a commit solely because classify_commit_risk returns \"high\" — always run the mandatory AI tools first.\n- NEVER apply a resolved file with conflict markers (<<<, ===, >>>) still present.\n- NEVER call continue_cherry_pick before all conflicted files are staged.\n- NEVER fabricate file content — only use content from get_conflict_context.\n- NEVER run commands that are not available as tools.\n- NEVER skip generate_report — it ends the run and produces the output.\n- If KEYPOOL_VAULT_URL is not set and apiKey is \"auto\", the run will fail before reaching this point.\n`\n\n// ---------------------------------------------------------------------------\n// Entry point — async main() is wrapped in .catch() for clean error exit.\n// ---------------------------------------------------------------------------\n\n/**\n * Main async entry point.\n *\n * Orchestrates the full agent lifecycle from environment validation through\n * report output. On any unhandled error the process exits with code 1.\n *\n * @throws On missing environment variables, invalid config, or agent failure.\n */\nasync function main() {\n // --- Environment validation ---\n // Fail fast if the provider cannot authenticate: avoids a confusing runtime\n // error deep inside the agent run.\n if (!process.env.KEYPOOL_VAULT_URL && !process.env.KEYPOOL_LIVE_SECRET) {\n console.error(\n \"Error: KEYPOOL_VAULT_URL environment variable is required for the keypoollive provider.\\n\" +\n \"Set it to your encrypted vault URL (e.g. https://raw.githubusercontent.com/.../ai.json.XXXX.enc)\\n\" +\n \"along with KEYPOOL_LIVE_SECRET as the decryption key.\",\n )\n process.exit(1)\n }\n\n // --- Config & customization loading ---\n // Both loaders throw descriptive errors if the files are missing or invalid.\n const config = loadConfig(process.env._CLI_CONFIG_PATH)\n // loadCustomizations supports: string path, URL, or inline object from config.\n const customizations = await loadCustomizations(\n config.customizations ?? process.env.BACKPORT_CUSTOMIZATIONS,\n )\n\n // --- Authentication + working directory setup ---\n // applyGitAuth sets process-level env vars (GIT_SSH_COMMAND or GIT_CONFIG_*)\n // before any git call is made, so all subsequent operations use the right creds.\n // ensureWorkingDir clones the fork repo if it doesn't exist, or fetches all\n // remotes if it does, bringing the checkout up to date before the agent starts.\n applyGitAuth(config)\n ensureWorkingDir(config)\n\n const userInstructionService = createUserInstructionConfigService({\n skills: { workspacePath: config.workingDir },\n })\n\n await userInstructionService.start()\n\n // --- Tool assembly ---\n // Each factory returns one or more AgentTool instances bound to the config.\n const gitTools = makeGitTools(config) // 10 tools for git operations\n const riskTool = makeRiskTool(config, customizations) // 1 tool for risk classification\n const validationTool = makeValidationTool(config) // 1 tool for validation suite\n const githubTools = makeGitHubTools(config) // 3 tools for GitHub PR management\n // Prompt log file for this run — every sub-agent LLM call is appended here.\n const promptLogPath = resolvePath(`run-${Date.now()}.prompts.jsonl`)\n process.stderr.write(`[PromptLogger] Writing sub-agent logs to: ${promptLogPath}\\n`)\n\n const reportTool = makeReportTool(config, promptLogPath) // 1 terminal tool (completesRun: true)\n const aiTools = makeAiTools(config, promptLogPath) // 3 AI-powered analysis tools\n\n // --- SDK built-in tools ---\n // Add Cline integrated tools so the agent can also perform generic workspace\n // operations through the standard runtime surface.\n const builtinTools = createBuiltinTools({\n cwd: config.workingDir,\n enableReadFiles: true,\n enableSearch: true,\n enableBash: true,\n enableWebFetch: true,\n enableApplyPatch: true,\n enableEditor: true,\n enableSkills: true,\n enableAskQuestion: true,\n enableSubmitAndExit: true,\n executors: {\n // Resolve skills from the workspace through the SDK's user-instruction service.\n skills: async (skill: string, args: string | undefined) => {\n const configuredSkills = userInstructionService.listRecords(\"skill\")\n const match = configuredSkills.find(\n (record: UserInstructionConfigRecord) => record.id === skill || record.item.name === skill || record.filePath === skill,\n )\n\n if (!match || match.item.disabled) {\n const availableSkills = configuredSkills\n .filter((record: UserInstructionConfigRecord) => !record.item.disabled)\n .map((record: UserInstructionConfigRecord) => record.item.name)\n\n return availableSkills.length > 0\n ? `Skill \"${skill}\" is not available. Known skills: ${availableSkills.join(\", \")}`\n : `No configured skills are available in this backport-agent runtime.`\n }\n\n const parts = [\n `Skill: ${match.item.name}`,\n match.item.description ? `Description: ${match.item.description}` : null,\n args ? `Arguments: ${args}` : null,\n \"Instructions:\",\n match.item.instructions,\n ].filter(Boolean)\n\n return parts.join(\"\\n\")\n },\n // Headless CI mode: ask_question is surfaced but should not block runs.\n askQuestion: async (question: string, options: string[]) => {\n const normalizedOptions = options.length > 0 ? options.join(\" | \") : \"(no options)\"\n return `Question recorded (headless mode): ${question} [${normalizedOptions}]`\n },\n // Keep submit_and_exit functional for compatibility with integrated flows.\n submit: async (summary: string, verified: boolean) =>\n `submit_and_exit acknowledged (verified=${verified ? \"true\" : \"false\"}): ${summary}`,\n },\n })\n\n // Flatten all tools into a single array for the Agent constructor.\n const allTools = [...builtinTools, ...gitTools, riskTool, validationTool, ...githubTools, reportTool, ...aiTools]\n\n // --- Agent instantiation ---\n // Using the keypoollive provider with \"auto\" apiKey — the SDK resolves the\n // actual API key at runtime via KEYPOOL_VAULT_URL.\n // config.models.fast selects the model configured for speed (see config.json).\n const agent = new Agent({\n providerId: \"keypoollive\",\n modelId: config.models.fast,\n apiKey: \"auto\", // SDK resolves via KEYPOOL_VAULT_URL at invocation time\n systemPrompt: SYSTEM_PROMPT,\n tools: allTools,\n maxIterations: config.sync.maxIterations,\n // Prevent the run from ending until generate_report (completesRun: true) is called.\n completionPolicy: { requireCompletionTool: true },\n })\n\n // --- Event subscription ---\n // Stream assistant text deltas to stdout so the operator can watch progress.\n // Tool-level progress (iterations, tool calls) is gated behind VERBOSE=true to\n // keep non-verbose runs clean. Set VERBOSE=true in .env or the shell to enable.\n const verbose = process.env.VERBOSE === \"true\"\n let lastEventWasText = false\n // Track the highest iteration seen so far across all attempts, so that when\n // the agent is restarted after a provider error the displayed counter is\n // continuous rather than resetting to 1.\n let iterationOffset = 0\n let lastSeenIteration = 0\n let currentAttempt = 1\n agent.subscribe((event: Parameters<Parameters<typeof agent.subscribe>[0]>[0]) => {\n const rawIter = (event as unknown as { iteration?: number }).iteration\n if (typeof rawIter === \"number\" && rawIter > lastSeenIteration) {\n lastSeenIteration = rawIter\n }\n const displayIter = typeof rawIter === \"number\" ? iterationOffset + rawIter : \"?\"\n if (event.type === \"assistant-text-delta\") {\n lastEventWasText = true\n process.stdout.write(event.text)\n } else if (event.type === \"tool-started\" && verbose) {\n // Ensure tool log starts on a fresh line after any streamed text.\n if (lastEventWasText) process.stderr.write(\"\\n\")\n lastEventWasText = false\n const inp = event.toolCall.input as Record<string, unknown>\n const preview =\n inp && typeof inp === \"object\" && Object.keys(inp).length > 0\n ? Object.keys(inp)\n .slice(0, 2)\n .map((k) => `${k}=${JSON.stringify(inp[k]).slice(0, 60)}`)\n .join(\", \")\n : \"(no input)\"\n process.stderr.write(`[→ iter ${displayIter}] ${event.toolCall.toolName}(${preview})\\n`)\n } else if (event.type === \"tool-finished\" && verbose) {\n lastEventWasText = false\n const result = event.toolCall as unknown as { toolName: string }\n process.stderr.write(`[← iter ${displayIter}] ${result.toolName ?? event.toolCall.toolName} done\\n`)\n } else if ((event.type === \"iteration_start\" || event.type === \"turn-started\") && verbose) {\n const retrySuffix = currentAttempt > 1 ? ` - Retry ${currentAttempt - 1}` : \"\"\n process.stderr.write(`\\n--- iteration ${displayIter}${retrySuffix} ---\\n`)\n }\n })\n\n // --- Task construction ---\n const dryRunNote = config.sync.dryRun ? \" [DRY RUN — no changes will be pushed]\" : \"\"\n const task =\n `Synchronize the fork \\`${config.fork.repo}@${config.fork.branch}\\` with upstream ` +\n `\\`${config.upstream.repo}@${config.upstream.branch}\\`.${dryRunNote}\\n\\n` +\n `Working directory: ${config.workingDir}\\n` +\n `Max commits per run: ${config.sync.maxCommitsPerRun}\\n` +\n `Batch size: ${config.sync.batchSize}`\n\n console.error(`\\n=== Backport Agent starting${dryRunNote} ===\\n`)\n\n // --- Provider retry helper ---\n // The SDK has no built-in retry for HTTP errors from the model provider.\n // Gemini (and other providers) occasionally return 503 / rate-limit responses;\n // this wrapper catches those and retries with exponential backoff.\n // Because agent state is persisted on disk (git), restarting the run is safe —\n // the agent will detect already-applied commits from the git log.\n const RETRIABLE_RE = /503|rate.?limit|too many requests|overloaded|service.?unavailable|high.?demand|try again later|temporarily unavailable|exceeded your current quota|quota.*exceeded|check your plan|billing details/i\n const BASE_DELAY_MS = 15_000\n const MAX_ATTEMPTS = 5\n\n async function runWithRetry(): Promise<Awaited<ReturnType<typeof agent.run>>> {\n for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {\n currentAttempt = attempt\n let result: Awaited<ReturnType<typeof agent.run>>\n try {\n result = await agent.run(task)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n if (attempt < MAX_ATTEMPTS && RETRIABLE_RE.test(msg)) {\n const delay = BASE_DELAY_MS * attempt\n process.stderr.write(\n `[Retry] Provider error on attempt ${attempt}/${MAX_ATTEMPTS}: ${msg.slice(0, 120)}\\n` +\n `[Retry] Waiting ${delay / 1000}s before retrying...\\n`,\n )\n iterationOffset += lastSeenIteration\n lastSeenIteration = 0\n await new Promise((r) => setTimeout(r, delay))\n continue\n }\n throw err\n }\n\n // The SDK can return without throwing when the model API errors silently\n // (e.g. invalid model name, 503 absorbed internally). Treat non-completed\n // status as a throw so the retry loop can handle retriable cases.\n if (result.status !== \"completed\") {\n const err = result.error ?? new Error(`Agent run ended with status \"${result.status}\" (model API error?)`)\n const msg = err.message\n if (attempt < MAX_ATTEMPTS && RETRIABLE_RE.test(msg)) {\n const delay = BASE_DELAY_MS * attempt\n process.stderr.write(\n `[Retry] Silent provider error (status=${result.status}) on attempt ${attempt}/${MAX_ATTEMPTS}: ${msg.slice(0, 120)}\\n` +\n `[Retry] Waiting ${delay / 1000}s before retrying...\\n`,\n )\n iterationOffset += lastSeenIteration\n lastSeenIteration = 0\n await new Promise((r) => setTimeout(r, delay))\n continue\n }\n throw err\n }\n\n return result\n }\n throw new Error(\"unreachable\")\n }\n\n // --- Run the agent ---\n // The agent loop runs until the `generate_report` tool is called\n // (`lifecycle: { completesRun: true }`) or an unrecoverable error occurs.\n try {\n const result = await runWithRetry()\n\n console.error(`\\n=== Run complete ===\\n`)\n if (result.outputText) {\n // The generate_report tool completes the run; outputText is the Markdown summary.\n console.log(result.outputText)\n } else {\n // With requireCompletionTool: true this should never happen on a clean run.\n throw new Error(\"Agent run completed but generate_report was never called (empty output). Check the prompt log for details.\")\n }\n } finally {\n userInstructionService.stop()\n }\n}\n\n// Wrap main() in a .catch() handler to ensure the process exits with code 1\n// on any unhandled error, rather than crashing with an unhandled rejection.\nmain()\n .then(() => process.exit(0))\n .catch((err) => {\n console.error(\"Fatal error:\", err instanceof Error ? err.message : String(err))\n process.exit(1)\n })\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"main.mjs","names":[],"sources":["../src/config/schema.ts","../src/config/loader.ts","../src/customizations/schema.ts","../src/customizations/loader.ts","../src/tool-helper.ts","../src/git/git-client.ts","../src/git/git-tools.ts","../src/git/git-init.ts","../src/risk/classify-risk.ts","../src/risk/risk-tools.ts","../src/validation/commands.ts","../src/validation/validation-tools.ts","../src/github/github-tools.ts","../src/reports/report-tools.ts","../src/ai/ai-tools.ts","../src/main.ts"],"sourcesContent":["/**\n * @file config/schema.ts\n *\n * Zod schema for the agent's main configuration file (config.json).\n * All fields are validated and typed at load time via `SyncConfigSchema.parse()`.\n *\n * The top-level object is divided into five sections:\n * - `upstream` – coordinates of the original repository being tracked\n * - `fork` – coordinates of the customised fork maintained by this agent\n * - `workingDir` – filesystem location of the local checkout\n * - `auth` – git authentication (SSH key or HTTP bearer token)\n * - `sync` – behavioural knobs (commit limits, dry-run mode, branch names…)\n * - `customizations` – inline or external customizations manifest (optional)\n * - `models` – LLM model identifiers used for cheap vs. powerful inference\n * - `validation` – shell commands executed after cherry-picking, grouped by risk level\n */\n\nimport { z } from \"zod\"\n\n/**\n * Full Zod validation schema for the backport-agent configuration.\n *\n * All nested objects have sensible defaults so that a minimal config.json only\n * needs to specify `upstream`, `fork`, and `workingDir`.\n *\n * **Important — Zod v4 `.default()` behaviour:**\n * When an entire sub-object is optional, we use `.default(() => ({} as any))`.\n * The factory form `() => value` is required by Zod v4 (unlike v3's plain value form).\n * The `as any` cast is intentional: each individual field already carries its own\n * `.default(…)`, so Zod will fill in all missing keys automatically; the outer\n * `{}` is just an empty trigger that lets the field-level defaults take effect.\n */\nexport const SyncConfigSchema = z.object({\n /**\n * Coordinates of the upstream (canonical) repository.\n * The agent fetches from this remote and picks commits out of it.\n */\n upstream: z.object({\n /** GitHub repository in `owner/repo` format, e.g. `\"cline/cline\"`. */\n repo: z.string().describe(\"owner/repo of the upstream repository\"),\n /**\n * Full git URL for the upstream remote, e.g. `\"git@github.com:org/repo.git\"` (SSH)\n * or `\"https://github.com/org/repo.git\"` (HTTPS).\n * Required when the working directory does not yet exist (for auto-clone setup).\n * Supports any git hosting provider, not just GitHub.\n */\n url: z.string().optional().describe(\"Full git URL (SSH or HTTPS) for the upstream remote\"),\n /** Branch on the upstream repo that the agent tracks, e.g. `\"main\"`. */\n branch: z.string().describe(\"Upstream branch to sync from\"),\n /** Local git remote name pointing to the upstream repo. Defaults to `\"upstream\"`. */\n remote: z.string().default(\"upstream\").describe(\"Git remote name for upstream\"),\n }),\n\n /**\n * Coordinates of the fork (customised) repository.\n * This is where new sync branches are pushed and PRs are opened.\n */\n fork: z.object({\n /** GitHub repository in `owner/repo` format, e.g. `\"TEA-ching/cline\"`. */\n repo: z.string().describe(\"owner/repo of the fork\"),\n /**\n * Full git URL for cloning the fork, e.g. `\"git@github.com:myuser/repo.git\"` (SSH)\n * or `\"https://github.com/myuser/repo.git\"` (HTTPS).\n * If the working directory does not exist the agent will clone this URL automatically.\n * Supports any git hosting provider, not just GitHub.\n */\n url: z.string().optional().describe(\"Full git URL (SSH or HTTPS) used to clone the fork\"),\n /** Target branch in the fork that sync commits are based on, e.g. `\"main\"`. */\n branch: z.string().describe(\"Fork branch to sync into\"),\n /** Local git remote name pointing to the fork. Defaults to `\"origin\"`. */\n remote: z.string().default(\"origin\").describe(\"Git remote name for the fork\"),\n }),\n\n /**\n * Absolute filesystem path to the local git clone of the fork.\n * All git operations are executed with this path as the working directory.\n * Example: `\"/home/ci/repos/my-fork\"`.\n * If the directory does not exist and `fork.url` is set, the agent will\n * clone the fork automatically on startup.\n */\n workingDir: z.string().describe(\"Absolute path to the local clone of the fork\"),\n\n /**\n * Git authentication credentials.\n *\n * Exactly one of `sshKeyPath` or `githubToken` should be set:\n * - `sshKeyPath` — path to an SSH private key; sets `GIT_SSH_COMMAND` for all git calls.\n * Supports `~` expansion. Example: `\"~/.ssh/id_ed25519\"`.\n * - `githubToken` — bearer token for HTTPS remotes (GitHub PAT, GitLab token, etc.);\n * injected via `http.extraHeader`. Works with any git hosting provider.\n * For security, prefer referencing an environment variable with the `$VAR` syntax\n * (e.g. `\"$GITHUB_TOKEN\"`) instead of embedding the raw token. If omitted, the\n * agent falls back to the `GITHUB_TOKEN` environment variable automatically.\n *\n * Both fields are optional — omit this section if git is already authenticated\n * through the system SSH agent or a credential helper.\n */\n auth: z\n .object({\n /**\n * Absolute (or `~`-prefixed) path to the SSH private key.\n * Example: `\"~/.ssh/id_ed25519\"` or `\"/home/ci/.ssh/deploy_key\"`.\n */\n sshKeyPath: z.string().optional().describe(\"Path to the SSH private key (supports ~ expansion)\"),\n /**\n * Bearer token for HTTPS authentication.\n * Prefix with `$` to read from an environment variable at runtime\n * (e.g. `\"$GITHUB_TOKEN\"`), which avoids storing the secret in config.json.\n */\n githubToken: z.string().optional().describe(\n \"HTTP bearer token; use \\\"$ENV_VAR\\\" syntax to read from an environment variable\"\n ),\n })\n .default(() => ({} as any)),\n\n /**\n * Runtime behaviour settings for the sync loop.\n * All fields have defaults, so this entire section is optional in config.json.\n */\n sync: z\n .object({\n /**\n * Maximum number of agent loop iterations per run.\n * Each iteration is one model turn (potentially invoking several tools in parallel).\n * Increase this value for large repos or runs with many conflict resolutions.\n * Defaults to 200.\n */\n maxIterations: z.number().int().positive().default(200),\n /** Maximum number of upstream commits to process in a single agent run. Defaults to 20. */\n maxCommitsPerRun: z.number().int().positive().default(20),\n /**\n * Depth used when first fetching remote refs.\n * Shallow enough to be fast; `ensureMergeBase` will deepen if necessary. Defaults to 200.\n */\n initialFetchDepth: z.number().int().positive().default(200),\n /**\n * Absolute upper bound for history depth when searching for a merge-base.\n * If the merge-base is not found within this depth, a full `--unshallow` fetch is attempted.\n * Defaults to 4000.\n */\n maxFetchDepth: z.number().int().positive().default(4000),\n /**\n * Number of commits to cherry-pick before pausing for human review.\n * Smaller batches reduce blast radius if something goes wrong. Defaults to 5.\n */\n batchSize: z.number().int().positive().default(5),\n /**\n * When true, the agent runs all analysis steps but skips all write operations\n * (no cherry-picks, no branch pushes, no PR creation). Defaults to false.\n * Can also be enabled at runtime via the `DRY_RUN=true` environment variable.\n */\n dryRun: z.boolean().default(false),\n /** When true, the agent opens a draft PR after pushing the sync branch. Defaults to true. */\n createPullRequest: z.boolean().default(true),\n /**\n * Prefix used when naming the auto-generated sync branch.\n * The final branch name is `<branchPrefix><upstreamBranch>-<YYYY-MM-DD>`.\n * Defaults to `\"sync/upstream-\"`.\n */\n branchPrefix: z.string().default(\"sync/upstream-\"),\n })\n // Allow omitting the entire sync block in config.json; each field has its own default.\n .default(() => ({} as any)),\n\n /**\n * LLM model identifiers for the keypoollive provider.\n * Use a cheap/fast model for high-volume triage and a more powerful one for\n * conflict resolution where reasoning quality matters most.\n */\n models: z\n .object({\n /**\n * Model used for fast, inexpensive tasks such as summarising diffs and\n * classifying risk alongside the deterministic rule engine.\n * Defaults to `\"mistral/devstral-latest\"`.\n */\n fast: z.string().default(\"mistral/devstral-latest\").describe(\"Low-cost model for summaries and risk triage\"),\n /**\n * Model used as first attempt for conflict resolution — optimised for code tasks.\n * Falls back to `models.powerful` if this call fails.\n * Defaults to `\"mistral/devstral-latest\"`.\n */\n specialist: z\n .string()\n .default(\"mistral/devstral-latest\")\n .describe(\"Code-specialist model for conflict resolution (first attempt)\"),\n /**\n * Model used for complex conflict resolution that demands deeper reasoning.\n * Invoked as a fallback when `models.specialist` fails.\n * Defaults to `\"mistral/magistral-medium-latest\"`.\n */\n powerful: z\n .string()\n .default(\"mistral/magistral-medium-latest\")\n .describe(\"High-capability model for conflict resolution (fallback)\"),\n })\n // Allow omitting the entire models block; individual fields carry defaults.\n .default(() => ({} as any)),\n\n /**\n * Deterministic merge-strategy overrides by file path.\n *\n * Each entry is either a glob pattern (matched via `minimatch`) or a regex\n * literal in the form `/pattern/flags` (e.g. `\"/^sdk\\\\/.*\\.lock$/i\"`).\n * Patterns are tested against the repo-relative file path.\n *\n * When a conflicted file matches:\n * - `ours` → the fork version (HEAD) is used as-is; AI resolution is skipped.\n * - `theirs` → the upstream version (CHERRY_PICK_HEAD) is used as-is; AI resolution is skipped.\n *\n * `theirs` is checked first; if a file matches both, `theirs` wins.\n */\n resolve: z\n .object({\n /**\n * Patterns for files where the fork version must always be kept.\n * Useful for lock files, generated assets, or files maintained exclusively in the fork.\n */\n ours: z.array(z.string()).default([]).describe(\"Glob/regex patterns — always keep fork version on conflict\"),\n /**\n * Patterns for files where the upstream version must always be taken.\n * Useful for changelogs, upstream-owned config files, or generated files\n * that must not carry fork modifications.\n */\n theirs: z.array(z.string()).default([]).describe(\"Glob/regex patterns — always take upstream version on conflict\"),\n })\n .default(() => ({} as any)),\n\n /**\n * Customizations manifest source.\n *\n * Accepts three forms:\n * - `string` starting with `http://` or `https://` → fetched at runtime.\n * - `string` (any other value) → treated as a local filesystem path.\n * - `object` → the manifest is embedded directly in config.json (JSON equivalent\n * of the YAML structure expected by `CustomizationsSchema`).\n *\n * When omitted the loader falls back to the `BACKPORT_CUSTOMIZATIONS` env var,\n * then to `./customizations.yaml` in the current working directory.\n */\n customizations: z\n .union([z.string(), z.record(z.string(), z.unknown())])\n .optional()\n .describe(\"Path, URL, or inline object for the customizations manifest\"),\n\n /**\n * Report output settings.\n */\n report: z\n .object({\n /**\n * Filesystem directory where the detailed Markdown run report is written.\n * The file name is `report.<timestamp>.md`.\n * Defaults to the current working directory (`.`).\n */\n destination: z.string().default(\".\").describe(\"Directory where detailed run reports are written\"),\n })\n .default(() => ({} as any)),\n\n /**\n * Shell command suites executed after cherry-picking, indexed by risk level.\n * Commands must match the allowlist in `validation/commands.ts` or they will\n * be blocked at execution time.\n */\n validation: z\n .object({\n /**\n * Commands run for low-risk commits (no customisation or build-critical files touched).\n * Defaults to `[\"npm run typecheck\"]`.\n */\n low: z.array(z.string()).default([\"npm run typecheck\"]),\n /**\n * Commands run for medium-risk commits (shared/services code changed).\n * Defaults to typecheck + unit tests.\n */\n medium: z.array(z.string()).default([\"npm run typecheck\", \"npm run test:unit\"]),\n /**\n * Commands run for high-risk commits (customisation zones, build files, lock files…).\n * Defaults to typecheck + unit tests + full build.\n */\n high: z.array(z.string()).default([\"npm run typecheck\", \"npm run test:unit\", \"npm run build\"]),\n })\n // Allow omitting the entire validation block; individual fields carry defaults.\n .default(() => ({} as any)),\n})\n\n/**\n * TypeScript type derived directly from `SyncConfigSchema`.\n * Use this type throughout the codebase instead of repeating the inline shape.\n */\nexport type SyncConfig = z.infer<typeof SyncConfigSchema>\n","/**\n * @file config/loader.ts\n *\n * Loads and validates the agent's main configuration from a JSON file.\n *\n * Resolution order for the config path (first match wins):\n * 1. Explicit `configPath` argument passed by the caller.\n * 2. The `BACKPORT_CONFIG` environment variable.\n * 3. `config.json` in the current working directory.\n *\n * Environment variable overrides applied after parsing:\n * - `DRY_RUN=true` → forces `sync.dryRun = true` regardless of the JSON value.\n */\n\nimport { readFileSync } from \"node:fs\"\nimport { resolve } from \"node:path\"\nimport { SyncConfigSchema, type SyncConfig } from \"./schema.js\"\n\n/**\n * Read, parse, and validate the agent configuration file.\n *\n * The raw JSON is parsed first, then any environment-variable overrides are\n * merged in before the result is validated through `SyncConfigSchema.parse()`.\n * Zod will throw a descriptive `ZodError` if required fields are missing or\n * have the wrong type.\n *\n * @param configPath - Optional explicit path to a `config.json` file.\n * Falls back to `BACKPORT_CONFIG` env var, then `./config.json`.\n * @returns A fully validated `SyncConfig` object with all defaults applied.\n * @throws {Error} If the file cannot be read or cannot be parsed as JSON.\n * @throws {ZodError} If the JSON structure does not satisfy `SyncConfigSchema`.\n */\nexport function loadConfig(configPath?: string): SyncConfig {\n // Determine which config file to read, in priority order.\n const path = configPath ?? process.env.BACKPORT_CONFIG ?? resolve(process.cwd(), \"config.json\")\n\n // Read synchronously — startup is blocking by design; no need for async here.\n const raw = JSON.parse(readFileSync(path, \"utf-8\"))\n\n // Ensure optional top-level sections exist so nested defaults are always applied.\n raw.sync ??= {}\n raw.models ??= {}\n raw.validation ??= {}\n\n // Environment variable overrides — applied before Zod validation so that\n // field-level constraints (e.g. type checks) still apply to the final values.\n if (process.env.KEYPOOL_VAULT_URL) {\n // KEYPOOL_VAULT_URL is consumed by the keypoollive provider, not by this schema.\n // We validate its presence separately in main.ts at agent startup.\n }\n\n if (process.env.DRY_RUN === \"true\") {\n // Allow CI pipelines to safely test the agent without pushing anything.\n raw.sync = { ...(raw.sync ?? {}), dryRun: true }\n }\n\n // Validate and apply defaults. SyncConfigSchema.parse() throws on invalid input.\n return SyncConfigSchema.parse(raw)\n}\n","/**\n * @file customizations/schema.ts\n *\n * Zod schema for the agent's customizations manifest (customizations.yaml).\n *\n * Each \"customization entry\" describes a deliberate deviation from upstream:\n * which file paths it covers, what invariants must remain intact after a sync,\n * and optional shell commands that can verify the customization is still working.\n *\n * The agent uses this manifest to:\n * 1. Detect when an upstream commit touches a customization zone (risk classification).\n * 2. Guide conflict resolution — the LLM knows which files carry fork-specific logic.\n * 3. Produce human-readable PR comments that explain why certain files need review.\n */\n\nimport { z } from \"zod\"\n\n/**\n * Schema for a single customization entry in the manifest.\n *\n * Example YAML entry:\n * ```yaml\n * - id: keypoollive-provider-vscode\n * description: \"Registers the keypoollive LLM provider inside the VS Code extension\"\n * paths:\n * - src/api/providers/keypoollive.ts\n * - src/shared/providers/providers.json\n * invariants:\n * - \"keypoollive must remain listed in providers.json\"\n * testCommands:\n * - \"npm run typecheck\"\n * ```\n */\nexport const CustomizationEntrySchema = z.object({\n /**\n * Short machine-readable identifier for this customization, e.g. `\"keypoollive-provider-vscode\"`.\n * Used in risk reports and decision logs to unambiguously reference the entry.\n */\n id: z.string(),\n\n /**\n * Human-readable description of what this customization does and why it exists.\n * Surfaced in PR comments and agent decision logs.\n */\n description: z.string(),\n\n /**\n * Glob patterns (relative to the repository root) that cover the files owned\n * by this customization. Any upstream commit touching one of these paths will\n * be classified as high risk.\n *\n * Standard minimatch syntax is supported, e.g. `\"src/api/providers/keypoollive/**\"`.\n */\n paths: z.array(z.string()).describe(\"Glob patterns relative to repo root\"),\n\n /**\n * Ordered list of invariants that must remain true after every sync.\n * The agent checks these conceptually during conflict resolution and includes\n * them in the PR body so human reviewers know what to verify.\n *\n * Example: `\"The SCTG_KEY_VAULT_URL constant must not be removed.\"`\n */\n invariants: z.array(z.string()).describe(\"Human-readable invariants that must remain true after sync\"),\n\n /**\n * Optional shell commands to run in order to verify this specific customization\n * is still intact after a sync. These are appended to the validation suite when\n * the commit risk level is \"high\" and this customization is affected.\n *\n * Commands must still match the global allowlist in `validation/commands.ts`.\n */\n testCommands: z.array(z.string()).optional().describe(\"Commands to verify this customization still works\"),\n})\n\n/**\n * Schema for the entire customizations manifest file.\n * The top-level key `customizations` holds the array of entries.\n */\nexport const CustomizationsSchema = z.object({\n /** Array of all known fork customizations. May be empty if the fork has no deviations. */\n customizations: z.array(CustomizationEntrySchema),\n})\n\n/**\n * TypeScript type for a single customization entry, inferred from `CustomizationEntrySchema`.\n */\nexport type CustomizationEntry = z.infer<typeof CustomizationEntrySchema>\n\n/**\n * TypeScript type for the full customizations manifest, inferred from `CustomizationsSchema`.\n */\nexport type Customizations = z.infer<typeof CustomizationsSchema>\n","/**\n * @file customizations/loader.ts\n *\n * Loads and validates the customizations manifest from multiple sources.\n *\n * Resolution order for the manifest (first match wins):\n * 1. Explicit `source` argument passed by the caller.\n * - `string` starting with `http://` or `https://` → fetched via HTTP GET.\n * - `string` (other) → read from the local filesystem.\n * - `object` → used directly as the parsed manifest (JSON/inline form).\n * 2. The `BACKPORT_CUSTOMIZATIONS` environment variable (file path).\n * 3. `customizations.yaml` in the current working directory.\n *\n * The resolved value is parsed with `js-yaml` when it comes from a string/URL,\n * then validated against `CustomizationsSchema` via Zod.\n */\n\nimport { readFileSync } from \"node:fs\"\nimport { resolve } from \"node:path\"\nimport yaml from \"js-yaml\"\nimport { CustomizationsSchema, type Customizations } from \"./schema.js\"\n\n/**\n * Read, parse, and validate the customizations manifest.\n *\n * @param source - Optional source: a file path, an HTTP(S) URL, or an inline object.\n * Falls back to `BACKPORT_CUSTOMIZATIONS` env var, then `./customizations.yaml`.\n * @returns A fully validated `Customizations` object.\n * @throws {Error} If the file/URL cannot be read.\n * @throws {ZodError} If the structure does not satisfy `CustomizationsSchema`.\n */\nexport async function loadCustomizations(source?: string | Record<string, unknown>): Promise<Customizations> {\n // --- Inline object: already parsed, validate directly ---\n if (source !== undefined && typeof source === \"object\") {\n return CustomizationsSchema.parse(source)\n }\n\n // --- Resolve string source ---\n const strSource =\n source ?? process.env.BACKPORT_CUSTOMIZATIONS ?? resolve(process.cwd(), \"customizations.yaml\")\n\n let raw: unknown\n\n if (typeof strSource === \"string\" && (strSource.startsWith(\"http://\") || strSource.startsWith(\"https://\"))) {\n // URL: fetch via HTTP GET\n const response = await fetch(strSource)\n if (!response.ok) {\n throw new Error(`Failed to fetch customizations from ${strSource}: HTTP ${response.status} ${response.statusText}`)\n }\n const text = await response.text()\n raw = yaml.load(text)\n } else {\n // Local file path\n raw = yaml.load(readFileSync(strSource as string, \"utf-8\"))\n }\n\n return CustomizationsSchema.parse(raw)\n}\n\n/**\n * Flatten all glob patterns from every customization entry into a single array.\n *\n * Useful as a quick pre-filter: if a changed file matches any of these patterns\n * the caller knows it needs deeper per-entry inspection.\n *\n * @param customizations - Validated customizations manifest.\n * @returns Deduplicated flat array of all glob patterns across all entries.\n */\nexport function getCustomizationPaths(customizations: Customizations): string[] {\n // flatMap collapses the nested arrays from each entry's `paths` field.\n return customizations.customizations.flatMap((c) => c.paths)\n}\n","/**\n * @file tool-helper.ts\n *\n * Typed wrapper around `createTool` from `@sctg/cline-sdk`.\n *\n * **Problem — overload resolution ambiguity:**\n * `@sctg/cline-shared/dist/tools/create.d.ts` declares two overloads of\n * `createTool`:\n * 1. `createTool(config: { inputSchema: Record<string, unknown>, ... })`\n * 2. `createTool<TSchema extends ZodTypeAny, TOutput>(config: { inputSchema: TSchema, ... })`\n *\n * TypeScript evaluates overloads in declaration order. Because `ZodObject`\n * is structurally assignable to `Record<string, unknown>`, overload 1 always\n * wins, and the inferred input type in `execute` becomes `unknown` instead of\n * the typed schema inference from overload 2.\n *\n * **Solution:**\n * `defineTool` has the correct generic signature (overload 2's types) and casts\n * the config to `any` before forwarding to `createTool`. TypeScript then\n * infers the Zod-typed `execute` parameter correctly at every call site.\n *\n * This is the only place where `as any` is used in the codebase.\n */\n\nimport { createTool } from \"@sctg/cline-agents\"\nimport type { AgentTool, AgentToolContext } from \"@sctg/cline-sdk\"\nimport { z } from \"zod\"\n\n/**\n * Creates a fully-typed agent tool from the provided configuration.\n *\n * This is a thin wrapper around `createTool` that exists solely to fix TypeScript\n * overload resolution. All arguments are forwarded unchanged; the only difference\n * from calling `createTool` directly is that `TSchema` is correctly inferred from\n * `inputSchema`.\n *\n * @typeParam TSchema - Zod schema type for the tool's input object.\n * @typeParam TOutput - Return type of the `execute` function.\n *\n * @param config - Tool configuration object.\n * @param config.name - Machine-readable tool name (snake_case by convention).\n * @param config.description - Natural-language description shown to the LLM.\n * @param config.inputSchema - Zod schema that validates and types the tool's input.\n * @param config.execute - Async function called by the agent runtime. Receives\n * a fully typed `input` (inferred from `TSchema`) and\n * an `AgentToolContext` for runtime metadata.\n * @param config.lifecycle - Optional lifecycle hooks (e.g. `completesRun: true`).\n * @param config.timeoutMs - Optional per-invocation timeout in milliseconds.\n * @param config.retryable - Whether the runtime should retry on transient failure.\n * @param config.maxRetries - Maximum retry attempts (used when `retryable` is `true`).\n * @returns A fully constructed `AgentTool` ready to be passed to the `Agent` constructor.\n */\nexport function defineTool<TSchema extends z.ZodTypeAny, TOutput>(config: {\n name: string\n description: string\n inputSchema: TSchema\n execute: (input: z.infer<TSchema>, context: AgentToolContext) => Promise<TOutput>\n lifecycle?: AgentTool<z.infer<TSchema>, TOutput>[\"lifecycle\"]\n timeoutMs?: number\n retryable?: boolean\n maxRetries?: number\n}): AgentTool<z.infer<TSchema>, TOutput> {\n // Cast to `any` to bypass the overload ambiguity described above.\n // The return type annotation ensures callers still get full type safety.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return createTool(config as any)\n}\n","/**\n * @file git/git-client.ts\n *\n * Low-level git operations used by the backport agent.\n *\n * Design principles:\n * - **No shell interpolation** — all git invocations use `execFileSync` with an\n * explicit argument array. User-supplied strings (SHAs, branch names, file\n * paths) are always passed as separate array items, never concatenated into a\n * shell command string. This prevents command-injection vulnerabilities.\n * - **Synchronous I/O** — the agent runs a single-threaded, sequential workflow;\n * async/await overhead would add complexity without benefit.\n * - **Minimal surface** — each function does exactly one git operation. Higher-\n * level orchestration lives in `git-tools.ts` (agent tool wrappers).\n */\n\nimport { execFileSync } from \"node:child_process\"\nimport { readFileSync, writeFileSync } from \"node:fs\"\n\n/**\n * Executes a git command in the given working directory using `execFileSync`.\n *\n * All standard streams are piped so that stdout and stderr are captured rather\n * than printed to the terminal. The return value is the trimmed stdout string.\n *\n * **Security note:** arguments must always be provided as an array — never as a\n * pre-joined string — to prevent shell injection.\n *\n * @param args - Git sub-command and its arguments, e.g. `[\"cherry\", \"-v\", \"HEAD\"]`.\n * @param cwd - Absolute path to the repository working directory.\n * @returns Trimmed stdout output of the git command.\n * @throws If the git process exits with a non-zero status (e.g. merge conflict).\n */\nexport function git(args: string[], cwd: string): string {\n return execFileSync(\"git\", args, { cwd, encoding: \"utf-8\", stdio: [\"pipe\", \"pipe\", \"pipe\"] }).trim()\n}\n\n/**\n * Represents a single upstream commit that the agent is considering for cherry-picking.\n */\nexport type CandidateCommit = {\n /** Full 40-character SHA of the upstream commit. */\n sha: string\n /** First line of the commit message (subject). */\n subject: string\n /**\n * `true` when `git cherry` determined that an equivalent patch is already\n * present in the fork branch (prefix `-` in cherry output).\n * Such commits are reported but skipped without cherry-picking.\n */\n alreadyApplied: boolean\n}\n\n/**\n * Ensures the local git history is deep enough to compute the merge-base between\n * the upstream branch and the fork branch.\n *\n * Many CI environments start with a shallow clone (`--depth=1`). This function\n * progressively deepens the clone in steps rather than fetching the entire\n * history at once, which keeps network usage low for the common case.\n *\n * Algorithm:\n * 1. Try `git merge-base` at the current depth.\n * 2. If it fails, deepen by the next step value and retry.\n * 3. If even `maxDepth` is insufficient, fall back to `--unshallow`.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @param upstreamRef - Full ref for the upstream branch, e.g. `\"upstream/main\"`.\n * @param forkRef - Full ref for the fork branch, e.g. `\"origin/main\"`.\n * @param maxDepth - Depth ceiling before attempting a full unshallow fetch.\n * Defaults to 4000 (matches `sync.maxFetchDepth` default).\n * @returns The SHA of the common ancestor commit (the merge-base).\n * @throws If the merge-base cannot be determined even after a full fetch.\n */\nexport function ensureMergeBase(\n cwd: string,\n upstreamRef: string,\n forkRef: string,\n maxDepth = 4000,\n): string {\n // Progressive depth ladder: try cheaper options first to minimise fetch size.\n const depths = [200, 500, 1000, 2000, maxDepth]\n\n for (const depth of depths) {\n try {\n // Attempt to find the common ancestor at the current history depth.\n return git([\"merge-base\", upstreamRef, forkRef], cwd)\n } catch {\n if (depth === maxDepth) {\n // Last resort: fetch the entire history and try once more.\n git([\"fetch\", \"--unshallow\"], cwd)\n return git([\"merge-base\", upstreamRef, forkRef], cwd)\n }\n // Deepen the shallow clone by the next step and loop.\n git([\"fetch\", `--deepen=${depth}`], cwd)\n }\n }\n\n throw new Error(\"Could not find merge-base even after full fetch\")\n}\n\n/**\n * Lists all upstream commits that are not yet present in the fork branch.\n *\n * Uses `git cherry` which compares patch content (not just SHA) so that commits\n * that were already cherry-picked (and may have a different SHA in the fork) are\n * correctly identified as already applied.\n *\n * Output format of `git cherry -v <upstream> <fork>`:\n * - Lines prefixed with `+` are **not** in the fork → candidates for cherry-pick.\n * - Lines prefixed with `-` **are** equivalent in the fork → already applied.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @param upstreamRef - Full ref for the upstream branch, e.g. `\"upstream/main\"`.\n * @param forkRef - Full ref for the fork branch, e.g. `\"origin/main\"`.\n * @returns Array of `CandidateCommit` objects, oldest-first.\n */\nexport function listCandidateCommits(\n cwd: string,\n upstreamRef: string,\n forkRef: string,\n): CandidateCommit[] {\n // `git cherry -v <upstream> <fork>` lists commits reachable from <upstream>\n // but not equivalent in <fork>. The `-v` flag adds the subject line.\n const cherryOutput = git([\"cherry\", \"-v\", forkRef, upstreamRef], cwd)\n\n // Empty output means upstream and fork are already in sync.\n if (!cherryOutput) return []\n\n return cherryOutput.split(\"\\n\").map((line) => {\n // Each line: `<marker> <sha> <subject>` where marker is `+` or `-`.\n const marker = line[0]\n const rest = line.slice(2) // skip marker and space\n const spaceIdx = rest.indexOf(\" \")\n const sha = rest.slice(0, spaceIdx)\n const subject = rest.slice(spaceIdx + 1)\n return {\n sha,\n subject,\n // `-` means an equivalent patch already exists in the fork.\n alreadyApplied: marker === \"-\",\n }\n })\n}\n\n/**\n * Returns the list of file paths changed by a single commit.\n *\n * Internally calls `git diff-tree --no-commit-id -r --name-only <sha>` which\n * lists only changed paths without any diff content, keeping the output small.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @param sha - Full or abbreviated commit SHA.\n * @returns Array of repository-relative file paths changed by the commit.\n * Empty array if the commit has no file changes (e.g. an empty commit).\n */\nexport function getCommitChangedFiles(cwd: string, sha: string): string[] {\n const output = git([\"diff-tree\", \"--no-commit-id\", \"-r\", \"--name-only\", sha], cwd)\n // Filter out empty strings that result from splitting a trailing newline.\n return output ? output.split(\"\\n\").filter(Boolean) : []\n}\n\n/**\n * Returns the full diff of a single commit, capped to `maxBytes` characters.\n *\n * The diff includes the commit stat summary (`--stat`) followed by the patch\n * (`--patch`). Large diffs are truncated with a notice so that the LLM context\n * window is not exhausted by a single commit.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @param sha - Full or abbreviated commit SHA.\n * @param maxBytes - Maximum number of characters to return. Defaults to 32 000.\n * @returns Diff string, possibly truncated.\n */\nexport function getCommitDiff(cwd: string, sha: string, maxBytes = 32_000): string {\n const full = git([\"show\", \"--stat\", \"--patch\", sha], cwd)\n // Truncate and append a notice so the LLM knows the diff is incomplete.\n return full.length > maxBytes ? full.slice(0, maxBytes) + \"\\n... [truncated]\" : full\n}\n\n/**\n * Creates a new sync branch from the tip of the fork branch.\n *\n * The branch is created locally; `pushBranch` must be called separately to\n * publish it to the remote.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @param branchName - Name for the new sync branch.\n * @param forkRef - Full ref of the fork branch to branch off, e.g. `\"origin/main\"`.\n */\nexport function createSyncBranch(cwd: string, branchName: string, forkRef: string): void {\n // First check out the fork branch tip to set HEAD correctly.\n git([\"checkout\", forkRef], cwd)\n // Then create and switch to the new sync branch.\n git([\"checkout\", \"-b\", branchName], cwd)\n}\n\n/**\n * Attempts to cherry-pick a single upstream commit onto the current branch.\n *\n * The `-x` flag appends `(cherry picked from commit …)` to the commit message,\n * providing an audit trail in the fork's history.\n *\n * On conflict, the cherry-pick is intentionally left **in progress** rather than\n * aborted. This allows the agent to inspect each conflicted file via\n * `getConflictContext`, resolve them, then call `continueCherryPick`. If the\n * agent cannot resolve the conflicts, it should call `abortCherryPick` instead.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @param sha - Full or abbreviated SHA of the commit to cherry-pick.\n * @returns An object with `success: true` if the cherry-pick applied cleanly,\n * or `success: false` plus the list of conflicted file paths.\n */\nexport function cherryPick(cwd: string, sha: string): { success: boolean; conflictedFiles: string[] } {\n try {\n // -x appends a \"cherry picked from\" note to the commit message.\n git([\"cherry-pick\", \"-x\", sha], cwd)\n return { success: true, conflictedFiles: [] }\n } catch {\n // Git exits non-zero on conflict. Collect the conflicting file paths.\n const status = git([\"diff\", \"--name-only\", \"--diff-filter=U\"], cwd)\n // U = unmerged (conflicted) files.\n const conflictedFiles = status ? status.split(\"\\n\").filter(Boolean) : []\n return { success: false, conflictedFiles }\n }\n}\n\n/**\n * Aborts a cherry-pick that is currently in progress.\n *\n * This resets the index and working tree to the state before `git cherry-pick`\n * was called. It is safe to call even if no cherry-pick is in progress (the\n * error is swallowed silently).\n *\n * @param cwd - Absolute path to the repository working directory.\n */\nexport function abortCherryPick(cwd: string): void {\n try {\n git([\"cherry-pick\", \"--abort\"], cwd)\n } catch {\n // Ignore errors — git returns non-zero if there is no cherry-pick in progress,\n // which is a harmless edge case (e.g. called twice by mistake).\n }\n}\n\n/**\n * Returns the content of a file at a specific git ref.\n *\n * Common use cases:\n * - `ref = \"HEAD\"` → the fork's current version of the file.\n * - `ref = \"CHERRY_PICK_HEAD\"` → the upstream version being cherry-picked.\n * - `ref = \"<sha>\"` → the version at any specific commit.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @param ref - Git ref, symbolic name, or SHA.\n * @param filePath - Repository-relative path of the file, e.g. `\"src/foo.ts\"`.\n * @returns The file content as a UTF-8 string, or `null` if the file does not\n * exist at the given ref (e.g. the file was added by the cherry-picked commit).\n */\nexport function getFileAtRef(cwd: string, ref: string, filePath: string): string | null {\n try {\n // `git show <ref>:<path>` streams the blob content to stdout.\n return git([\"show\", `${ref}:${filePath}`], cwd)\n } catch {\n // Non-zero exit means the path does not exist at that ref.\n return null\n }\n}\n\n/**\n * Reads the current working-tree version of a file, including any conflict markers.\n *\n * After a failed cherry-pick, git leaves conflict markers (`<<<<<<<`, `=======`,\n * `>>>>>>>`) in the file. This function reads that raw content so the agent can\n * analyse it before attempting a resolution.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @param filePath - Repository-relative path of the file, e.g. `\"src/foo.ts\"`.\n * @returns The raw file content as a UTF-8 string (may contain conflict markers).\n * @throws If the file does not exist on disk.\n */\nexport function readWorkingFile(cwd: string, filePath: string): string {\n // Absolute path is constructed by joining cwd and the repo-relative path.\n return readFileSync(`${cwd}/${filePath}`, \"utf-8\")\n}\n\n/**\n * Writes resolved content to a file on disk and stages it with `git add`.\n *\n * Called by the agent after resolving each conflicted file. The file must\n * contain no conflict markers before calling this function.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @param filePath - Repository-relative path of the file, e.g. `\"src/foo.ts\"`.\n * @param content - Fully resolved file content, free of conflict markers.\n */\nexport function writeAndStageFile(cwd: string, filePath: string, content: string): void {\n // Write the resolved content to disk, replacing the conflict-marker version.\n writeFileSync(`${cwd}/${filePath}`, content, \"utf-8\")\n // Stage the file so it is included in the cherry-pick commit.\n git([\"add\", filePath], cwd)\n}\n\n/**\n * Completes an in-progress cherry-pick after all conflicts have been resolved and staged.\n *\n * Uses `GIT_EDITOR=true` to suppress the interactive editor that git would\n * otherwise open for the commit message, making this safe to call in a\n * non-interactive CI environment.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @throws If there are still unstaged conflicted files when this is called.\n */\nexport function continueCherryPick(cwd: string): void {\n // GIT_EDITOR=true accepts the default commit message without opening an editor.\n // --no-edit is also passed as a belt-and-suspenders precaution.\n execFileSync(\"git\", [\"cherry-pick\", \"--continue\", \"--no-edit\"], {\n cwd,\n encoding: \"utf-8\",\n env: { ...process.env, GIT_EDITOR: \"true\" },\n })\n}\n\n/**\n * Pushes the sync branch to the fork remote.\n *\n * A simple non-force push. If the branch already exists on the remote with\n * different history the push will fail — the agent should never force-push to\n * avoid overwriting human commits.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @param remote - Name of the git remote to push to, e.g. `\"origin\"`.\n * @param branchName - Name of the local branch to push.\n */\nexport function pushBranch(cwd: string, remote: string, branchName: string): void {\n git([\"push\", remote, branchName], cwd)\n}\n\n/**\n * Fetches both the upstream and fork remotes to bring local refs up to date.\n *\n * Uses a shallow fetch (`--depth=N`) to keep network usage proportional. The\n * depth here corresponds to `sync.initialFetchDepth`; `ensureMergeBase` will\n * deepen further if needed.\n *\n * @param cwd - Absolute path to the repository working directory.\n * @param upstreamRemote - Name of the upstream git remote, e.g. `\"upstream\"`.\n * @param forkRemote - Name of the fork git remote, e.g. `\"origin\"`.\n * @param depth - Shallow fetch depth.\n */\nexport function fetchRemotes(cwd: string, upstreamRemote: string, forkRemote: string, depth: number): void {\n git([\"fetch\", `--depth=${depth}`, upstreamRemote], cwd)\n git([\"fetch\", `--depth=${depth}`, forkRemote], cwd)\n}\n\n\n","/**\n * @file git/git-tools.ts\n *\n * Factory that creates the agent tools wrapping all low-level git operations.\n *\n * Each tool returned by `makeGitTools` corresponds to a single capability that\n * the LLM can invoke during the sync workflow:\n *\n * 1. `fetch_remotes` — update local refs from upstream and fork.\n * 2. `list_candidate_commits` — discover which upstream commits to sync.\n * 3. `get_commit_details` — inspect changed files and full diff.\n * 4. `create_sync_branch` — branch off the fork tip for this sync run.\n * 5. `cherry_pick_commit` — apply a single commit; reports conflicts.\n * 6. `abort_cherry_pick` — abandon a conflicting cherry-pick.\n * 7. `get_conflict_context` — fetch fork, upstream, and marker-annotated versions.\n * 8. `apply_resolved_file` — write the LLM's resolution and stage it.\n * 9. `continue_cherry_pick` — complete the cherry-pick after all files resolved.\n * 10. `push_sync_branch` — publish the sync branch to the fork remote.\n *\n * All tools respect the `sync.dryRun` flag by returning early with a `dryRun:true`\n * marker instead of performing any mutating operation.\n */\n\nimport { z } from \"zod\"\nimport { readFileSync } from \"node:fs\"\nimport { minimatch } from \"minimatch\"\nimport { defineTool } from \"../tool-helper.js\"\nimport {\n ensureMergeBase,\n listCandidateCommits,\n getCommitChangedFiles,\n getCommitDiff,\n createSyncBranch,\n cherryPick,\n abortCherryPick,\n getFileAtRef,\n writeAndStageFile,\n continueCherryPick,\n pushBranch,\n fetchRemotes,\n} from \"./git-client.js\"\nimport type { SyncConfig } from \"../config/schema.js\"\n\n/**\n * Tests whether a repo-relative file path matches any of the given patterns.\n *\n * Patterns are either:\n * - A glob string (matched via `minimatch` with `matchBase: true`).\n * - A regex literal in the form `/source/flags` (e.g. `\"/^sdk\\\\/.*\\.ts$/i\"`).\n *\n * @param filePath - Repo-relative path of the file to test.\n * @param patterns - Array of glob or regex patterns from the config.\n * @returns `true` if the path matches at least one pattern.\n */\nfunction matchesResolvePattern(filePath: string, patterns: string[]): boolean {\n for (const pattern of patterns) {\n // Regex literal: /source/flags\n if (pattern.startsWith(\"/\") && pattern.lastIndexOf(\"/\") > 0) {\n const lastSlash = pattern.lastIndexOf(\"/\")\n const source = pattern.slice(1, lastSlash)\n const flags = pattern.slice(lastSlash + 1)\n try {\n if (new RegExp(source, flags).test(filePath)) return true\n } catch {\n // Silently skip malformed regex patterns.\n }\n } else {\n // Glob pattern via minimatch.\n if (minimatch(filePath, pattern, { matchBase: true })) return true\n }\n }\n return false\n}\n\n/**\n * Builds and returns all git-related agent tools pre-bound to the provided config.\n *\n * The returned array is spread directly into the `Agent` constructor's `tools`\n * array. Each tool captures `workingDir`, `upstream`, `fork`, and `sync` from\n * the config via closure, so callers never need to pass them per-invocation.\n *\n * @param config - Validated `SyncConfig` loaded from `config.json`.\n * @returns Array of ten agent tools covering the full git workflow.\n */\nexport function makeGitTools(config: SyncConfig) {\n // Destructure frequently-used config sections for brevity inside each tool.\n const { workingDir, upstream, fork, sync } = config\n\n /**\n * Tool: fetch_remotes\n *\n * Fetches the upstream and fork remotes at `sync.initialFetchDepth`, then\n * calls `ensureMergeBase` to deepen the clone if needed. This must be called\n * once at the start of every run before any other git tool.\n */\n const fetchRemotesTool = defineTool({\n name: \"fetch_remotes\",\n description: \"Fetch both upstream and fork remotes to ensure local refs are up to date.\",\n inputSchema: z.object({}),\n execute: async () => {\n // Fetch both remotes at the configured initial depth.\n fetchRemotes(workingDir, upstream.remote, fork.remote, sync.initialFetchDepth)\n // Deepen the clone as needed so that merge-base computation succeeds.\n ensureMergeBase(\n workingDir,\n `${upstream.remote}/${upstream.branch}`,\n `${fork.remote}/${fork.branch}`,\n sync.maxFetchDepth,\n )\n return { success: true }\n },\n })\n\n /**\n * Tool: list_candidate_commits\n *\n * Uses `git cherry` to compare upstream and fork by patch content. Commits\n * that have already been applied (even with a different SHA) are excluded.\n * The result is limited to `sync.maxCommitsPerRun` to prevent the agent from\n * processing an unbounded queue in a single session.\n */\n const listCandidatesTool = defineTool({\n name: \"list_candidate_commits\",\n description:\n \"List upstream commits that are not yet applied to the fork branch. \" +\n \"Uses git cherry to detect already-applied patches by content, not just SHA. \" +\n \"Returns an array of candidate commits with their SHA, subject, and alreadyApplied flag.\",\n inputSchema: z.object({}),\n execute: async () => {\n const candidates = listCandidateCommits(\n workingDir,\n `${upstream.remote}/${upstream.branch}`,\n `${fork.remote}/${fork.branch}`,\n )\n // Filter out already-applied commits and cap to the configured run limit.\n const pending = candidates.filter((c) => !c.alreadyApplied).slice(0, sync.maxCommitsPerRun)\n return { candidates: pending, total: pending.length }\n },\n })\n\n /**\n * Tool: get_commit_details\n *\n * Returns the file list and (optionally) the full diff for a given commit SHA.\n * The diff is truncated at 32 000 characters by `getCommitDiff` to protect\n * the LLM context window. The agent should call this before risk classification\n * and before attempting a cherry-pick.\n */\n const getCommitDetailsTool = defineTool({\n name: \"get_commit_details\",\n description:\n \"Get the changed files and diff for a specific upstream commit. \" +\n \"Use this before classifying risk or attempting a cherry-pick.\",\n inputSchema: z.object({\n sha: z.string().describe(\"The commit SHA to inspect\"),\n /** Set to false to skip the diff and only retrieve the file list. */\n includeDiff: z.boolean().default(true),\n }),\n execute: async ({ sha, includeDiff }) => {\n const changedFiles = getCommitChangedFiles(workingDir, sha)\n // Fetch the diff only when explicitly requested to save context tokens.\n const diff = includeDiff ? getCommitDiff(workingDir, sha) : null\n return { sha, changedFiles, diff }\n },\n })\n\n /**\n * Tool: create_sync_branch\n *\n * Creates a new local branch named `<branchPrefix><upstreamBranch>-<date>`\n * branching off `<forkRemote>/<forkBranch>`. No-ops in dry-run mode.\n * The branch name is returned so subsequent tools can reference it.\n */\n const createSyncBranchTool = defineTool({\n name: \"create_sync_branch\",\n description:\n \"Create a new sync branch from the fork branch tip. \" +\n \"The branch name is auto-generated with today's date. Returns the branch name.\",\n inputSchema: z.object({}),\n execute: async () => {\n // Skip actual branch creation in dry-run mode.\n if (sync.dryRun) return { branchName: null, dryRun: true }\n // Build the branch name from the configured prefix, upstream branch, and today's date.\n const date = new Date().toISOString().slice(0, 10)\n const branchName = `${sync.branchPrefix}${upstream.branch}-${date}`\n createSyncBranch(workingDir, branchName, `${fork.remote}/${fork.branch}`)\n return { branchName }\n },\n })\n\n /**\n * Tool: cherry_pick_commit\n *\n * Attempts to cherry-pick the given SHA. On success, the commit is already\n * committed to the local branch. On conflict, git leaves the cherry-pick in\n * progress; the agent should call `get_conflict_context` / `apply_resolved_file`\n * / `continue_cherry_pick` in sequence, or `abort_cherry_pick` to give up.\n */\n const cherryPickCommitTool = defineTool({\n name: \"cherry_pick_commit\",\n description:\n \"Attempt to cherry-pick a single upstream commit onto the current sync branch. \" +\n \"Returns success:true if clean, or success:false with conflictedFiles if conflicts arose. \" +\n \"On conflict, the cherry-pick is left in progress for the resolve_conflict tool.\",\n inputSchema: z.object({\n sha: z.string().describe(\"Upstream commit SHA to cherry-pick\"),\n }),\n execute: async ({ sha }) => {\n // Dry-run: report success without touching the repository.\n if (sync.dryRun) return { success: true, dryRun: true, conflictedFiles: [] }\n return cherryPick(workingDir, sha)\n },\n })\n\n /**\n * Tool: abort_cherry_pick\n *\n * Calls `git cherry-pick --abort` to discard any partially applied changes and\n * restore the working tree to the state before the cherry-pick started. Should\n * be called when the agent decides a conflict is too complex to resolve safely.\n */\n const abortCherryPickTool = defineTool({\n name: \"abort_cherry_pick\",\n description: \"Abort the current cherry-pick in progress. Call this when a conflict cannot be resolved automatically.\",\n inputSchema: z.object({}),\n execute: async () => {\n abortCherryPick(workingDir)\n return { aborted: true }\n },\n })\n\n /**\n * Tool: get_conflict_context\n *\n * Returns three views of a conflicted file so the LLM has all the information\n * it needs for a principled resolution:\n * - `forkVersion` — the file as it existed in HEAD before the cherry-pick.\n * - `upstreamVersion` — the file as it exists in CHERRY_PICK_HEAD (incoming).\n * - `withMarkers` — the current working-tree content with `<<<<<<<` markers.\n *\n * `forkVersion` or `upstreamVersion` may be `null` if the file is new on one side.\n */\n const getConflictContextTool = defineTool({\n name: \"get_conflict_context\",\n description:\n \"For a conflicted file, return the fork version (HEAD), the upstream version (CHERRY_PICK_HEAD), \" +\n \"and the current file content with conflict markers. Use this to gather context before resolving.\",\n inputSchema: z.object({\n filePath: z.string().describe(\"Repo-relative path of the conflicted file\"),\n }),\n execute: async ({ filePath }) => {\n // Fetch the fork's current committed version (may be null for new files).\n const forkVersion = getFileAtRef(workingDir, \"HEAD\", filePath)\n // Fetch the incoming upstream version (may be null for deleted files).\n const upstreamVersion = getFileAtRef(workingDir, \"CHERRY_PICK_HEAD\", filePath)\n // Read the working-tree file which contains conflict markers.\n let withMarkers: string | null = null\n try {\n withMarkers = readFileSync(`${workingDir}/${filePath}`, \"utf-8\")\n } catch {\n // The file may have been deleted by the upstream commit.\n withMarkers = null\n }\n\n // Deterministic strategy override from config.resolve.\n // `theirs` is checked first; if a file matches both, theirs wins.\n let forcedStrategy: \"ours\" | \"theirs\" | null = null\n const resolveConfig = config.resolve\n if (resolveConfig) {\n if (matchesResolvePattern(filePath, resolveConfig.theirs ?? [])) {\n forcedStrategy = \"theirs\"\n } else if (matchesResolvePattern(filePath, resolveConfig.ours ?? [])) {\n forcedStrategy = \"ours\"\n }\n }\n\n return { filePath, forkVersion, upstreamVersion, withMarkers, forcedStrategy }\n },\n })\n\n /**\n * Tool: apply_resolved_file\n *\n * Writes the LLM-provided resolution for a single conflicted file to disk and\n * runs `git add` to stage it. Must be called for every conflicted file before\n * `continue_cherry_pick`. The `resolvedContent` must be free of conflict markers.\n */\n const applyResolvedFileTool = defineTool({\n name: \"apply_resolved_file\",\n description:\n \"Write the resolved content for a conflicted file and stage it. \" +\n \"Call this for each conflicted file before calling continue_cherry_pick.\",\n inputSchema: z.object({\n filePath: z.string().describe(\"Repo-relative path of the file\"),\n resolvedContent: z.string().describe(\"The fully resolved file content, with no conflict markers\"),\n }),\n execute: async ({ filePath, resolvedContent }) => {\n // Skip file write in dry-run mode.\n if (sync.dryRun) return { staged: false, dryRun: true }\n writeAndStageFile(workingDir, filePath, resolvedContent)\n return { staged: true, filePath }\n },\n })\n\n /**\n * Tool: continue_cherry_pick\n *\n * Finalises the cherry-pick after all conflicted files have been resolved and\n * staged. Internally calls `git cherry-pick --continue --no-edit` with\n * `GIT_EDITOR=true` so that no interactive editor is opened.\n */\n const continueCherryPickTool = defineTool({\n name: \"continue_cherry_pick\",\n description:\n \"Complete the cherry-pick after all conflicted files have been resolved and staged via apply_resolved_file.\",\n inputSchema: z.object({}),\n execute: async () => {\n // Skip in dry-run mode.\n if (sync.dryRun) return { committed: false, dryRun: true }\n continueCherryPick(workingDir)\n return { committed: true }\n },\n })\n\n /**\n * Tool: push_sync_branch\n *\n * Pushes the named sync branch to `fork.remote`. Called once after all commits\n * have been processed. Only a non-force push is performed to avoid overwriting\n * human commits on the remote.\n */\n const pushSyncBranchTool = defineTool({\n name: \"push_sync_branch\",\n description: \"Push the current sync branch to the fork remote.\",\n inputSchema: z.object({\n /** Name of the local sync branch to push, as returned by `create_sync_branch`. */\n branchName: z.string(),\n }),\n execute: async ({ branchName }) => {\n // Skip push in dry-run mode.\n if (sync.dryRun) return { pushed: false, dryRun: true }\n pushBranch(workingDir, fork.remote, branchName)\n return { pushed: true, branchName }\n },\n })\n\n return [\n fetchRemotesTool,\n listCandidatesTool,\n getCommitDetailsTool,\n createSyncBranchTool,\n cherryPickCommitTool,\n abortCherryPickTool,\n getConflictContextTool,\n applyResolvedFileTool,\n continueCherryPickTool,\n pushSyncBranchTool,\n ]\n}\n","/**\n * @file git/git-init.ts\n *\n * Repository initialisation and authentication helpers.\n *\n * Two exported functions:\n *\n * - `applyGitAuth(config)` — configures the process environment so that all\n * subsequent git calls (via `git-client.ts`) use the right credentials.\n * Supports SSH private keys (via `GIT_SSH_COMMAND`) and HTTP bearer tokens\n * (via git's `http.extraHeader` environment config).\n *\n * - `ensureWorkingDir(config)` — makes the `workingDir` ready for the agent:\n * · If the directory does not exist (or is not a git repo): clones `fork.url`.\n * · If it already exists: fetches all remotes to bring it up to date.\n * · Ensures the upstream remote is properly configured when its URL is provided\n * and it differs from the fork remote.\n *\n * Design notes:\n * - All git I/O is synchronous (matches the rest of git-client.ts).\n * - No secrets are stored in child-process arguments — tokens are injected via\n * environment variables only.\n * - `fork.url` supports any git hosting provider (GitHub, GitLab, Gitea, …).\n */\n\nimport { existsSync, mkdirSync } from \"node:fs\"\nimport { dirname } from \"node:path\"\nimport { execFileSync } from \"node:child_process\"\nimport { git } from \"./git-client.js\"\nimport type { SyncConfig } from \"../config/schema.js\"\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolves a config value that may reference an environment variable.\n *\n * If `value` starts with `$` the remainder is treated as an environment variable\n * name. This lets operators write `\"$GITHUB_TOKEN\"` in config.json instead of\n * embedding the raw secret, keeping credentials out of version control.\n *\n * @param value - Raw config string, e.g. `\"ghp_abc123\"` or `\"$MY_TOKEN\"`.\n * @returns The resolved string, or `undefined` if the env var is not set.\n */\nfunction resolveConfigValue(value: string): string | undefined {\n if (value.startsWith(\"$\")) {\n return process.env[value.slice(1)]\n }\n return value\n}\n\n// ---------------------------------------------------------------------------\n// Exports\n// ---------------------------------------------------------------------------\n\n/**\n * Configures process-level environment variables so that all subsequent git\n * operations (in this process and its child processes) use the right credentials.\n *\n * Priority order:\n * 1. SSH key (`config.auth.sshKeyPath`) → sets `GIT_SSH_COMMAND`\n * 2. Token (`config.auth.githubToken`) → sets `GIT_CONFIG_*` http.extraHeader\n * 3. Fallback to `GITHUB_TOKEN` env var → same as (2)\n *\n * If none of the above are set the function is a no-op; git will use whatever\n * credentials are already available in the environment (SSH agent, credential helper…).\n *\n * @param config - Validated `SyncConfig` loaded from `config.json`.\n */\nexport function applyGitAuth(config: SyncConfig): void {\n const { sshKeyPath, githubToken } = config.auth\n\n // --- SSH key ---\n if (sshKeyPath) {\n // Expand leading ~ to the user's home directory.\n const keyPath = sshKeyPath.replace(/^~(?=\\/|$)/, process.env.HOME ?? \"\")\n process.env.GIT_SSH_COMMAND = `ssh -i \"${keyPath}\" -o StrictHostKeyChecking=no -o BatchMode=yes`\n process.stderr.write(`[GitAuth] SSH key configured: ${keyPath}\\n`)\n return\n }\n\n // --- HTTP bearer token ---\n const rawToken = githubToken ?? \"$GITHUB_TOKEN\"\n const token = resolveConfigValue(rawToken)\n if (token) {\n // git supports injecting config via numbered GIT_CONFIG_KEY_N / GIT_CONFIG_VALUE_N env vars.\n // This avoids writing to any config file and works without root access.\n const count = parseInt(process.env.GIT_CONFIG_COUNT ?? \"0\", 10)\n process.env.GIT_CONFIG_COUNT = String(count + 1)\n process.env[`GIT_CONFIG_KEY_${count}`] = \"http.extraHeader\"\n process.env[`GIT_CONFIG_VALUE_${count}`] = `Authorization: Bearer ${token}`\n process.stderr.write(\"[GitAuth] HTTP bearer token auth configured.\\n\")\n }\n}\n\n/**\n * Ensures that `config.workingDir` contains a ready-to-use git repository.\n *\n * **Clone** (directory absent or not a git repo):\n * Clones `config.fork.url` into `config.workingDir`. The parent directory is\n * created if necessary. Throws if `fork.url` is not set.\n *\n * **Sync** (directory is already a git repo):\n * Runs `git fetch --all --prune` to bring all tracked remotes up to date.\n *\n * **Upstream remote**:\n * When `config.upstream.url` is set and `upstream.remote` differs from\n * `fork.remote`, the upstream remote is added (or its URL updated) automatically.\n *\n * @param config - Validated `SyncConfig` loaded from `config.json`.\n * @throws If cloning is required but `fork.url` is not configured.\n */\nexport function ensureWorkingDir(config: SyncConfig): void {\n const { workingDir, upstream, fork } = config\n const isGitRepo = existsSync(`${workingDir}/.git`)\n\n // --- Clone if the working directory does not yet exist ---\n if (!isGitRepo) {\n if (!fork.url) {\n throw new Error(\n `[GitInit] '${workingDir}' is not a git repository and fork.url is not configured. ` +\n `Set fork.url to a valid git URL (SSH or HTTPS) to enable automatic cloning.`,\n )\n }\n // Ensure the parent directory exists (git clone does not create it).\n mkdirSync(dirname(workingDir), { recursive: true })\n process.stderr.write(`[GitInit] Cloning ${fork.url} → ${workingDir} ...\\n`)\n execFileSync(\"git\", [\"clone\", fork.url, workingDir], {\n // Pipe stdin, inherit stdout/stderr so clone progress is visible.\n stdio: [\"pipe\", \"inherit\", \"inherit\"],\n })\n process.stderr.write(\"[GitInit] Clone complete.\\n\")\n } else {\n // --- Fetch all remotes to sync the existing checkout ---\n process.stderr.write(`[GitInit] ${workingDir} found — fetching all remotes...\\n`)\n try {\n git([\"fetch\", \"--all\", \"--prune\"], workingDir)\n process.stderr.write(\"[GitInit] Fetch complete.\\n\")\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n process.stderr.write(`[GitInit] Warning: fetch failed: ${msg}\\n`)\n }\n }\n\n // --- Ensure upstream remote is configured ---\n // Only act when an upstream URL is provided and the upstream remote has a\n // different name than the fork remote (avoids overwriting the fork \"origin\").\n if (upstream.url && upstream.remote !== fork.remote) {\n try {\n const remotes = git([\"remote\"], workingDir).split(\"\\n\").filter(Boolean)\n if (!remotes.includes(upstream.remote)) {\n git([\"remote\", \"add\", upstream.remote, upstream.url], workingDir)\n process.stderr.write(`[GitInit] Added remote '${upstream.remote}' → ${upstream.url}\\n`)\n } else {\n const currentUrl = git([\"remote\", \"get-url\", upstream.remote], workingDir)\n if (currentUrl !== upstream.url) {\n git([\"remote\", \"set-url\", upstream.remote, upstream.url], workingDir)\n process.stderr.write(`[GitInit] Updated remote '${upstream.remote}' → ${upstream.url}\\n`)\n }\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n process.stderr.write(`[GitInit] Warning: could not configure upstream remote: ${msg}\\n`)\n }\n }\n}\n","/**\n * @file risk/classify-risk.ts\n *\n * Purely deterministic commit risk classifier — no LLM is involved.\n *\n * The classifier assigns one of three risk levels to an upstream commit:\n * - **low** — Only touches files that are safe to auto-apply.\n * - **medium** — Touches shared infrastructure (API layer, shared types, services)\n * that may interact with fork customizations.\n * - **high** — Touches build configuration, CI pipelines, lockfiles, protobuf\n * definitions, or a file explicitly listed in the fork's\n * `customizations.yaml`.\n *\n * The LLM agent uses this output as high-level context before deciding whether\n * to attempt a cherry-pick, skip, or request human review.\n *\n * Pattern matching uses `minimatch` (the same glob library used by `.gitignore`\n * and the VS Code extension tree). All patterns are relative to the repository\n * root, as returned by `git diff-tree --name-only`.\n */\n\nimport { minimatch } from \"minimatch\"\nimport type { Customizations } from \"../customizations/schema.js\"\n\n/**\n * The three risk levels assigned to every upstream commit.\n *\n * - `\"low\"` Safe to cherry-pick with minimal validation.\n * - `\"medium\"` Requires standard validation suite before merging.\n * - `\"high\"` Requires full validation + likely human review.\n */\nexport type RiskLevel = \"low\" | \"medium\" | \"high\"\n\n/**\n * Full risk assessment result for a single upstream commit.\n */\nexport type CommitRisk = {\n /** The commit SHA that was classified. */\n sha: string\n /** Computed risk level: \"low\", \"medium\", or \"high\". */\n level: RiskLevel\n /** Human-readable explanations for why each risk factor was triggered. */\n reasons: string[]\n /** `true` if any file in the commit matches a customization zone in the fork. */\n touchesCustomization: boolean\n /** IDs of `CustomizationEntry` objects whose paths were matched by this commit. */\n customizationIds: string[]\n}\n\n/**\n * Glob patterns whose matches unconditionally elevate risk to `\"high\"`.\n *\n * These files are considered build-critical or structurally sensitive:\n * - Dependency manifests and lockfiles (`package.json`, `*-lock.*`, `*.lock`)\n * - CI / GitHub Actions workflows (`.github/workflows/**`)\n * - Build scripts directory (`scripts/**`)\n * - ESBuild config files (`esbuild.*`)\n * - TypeScript project references (`tsconfig*.json`)\n * - Protobuf definitions (`proto/**`) — changes here require proto regeneration\n * and affect the entire RPC layer\n */\nconst HIGH_RISK_PATTERNS = [\n \"package.json\",\n \"package-lock.json\",\n \"pnpm-lock.yaml\",\n \"yarn.lock\",\n \".github/workflows/**\",\n \"scripts/**\",\n \"esbuild.*\",\n \"tsconfig*.json\",\n \"proto/**\",\n]\n\n/**\n * Glob patterns whose matches elevate risk to `\"medium\"` when no high-risk\n * pattern has already been matched.\n *\n * These paths contain shared infrastructure that is more likely to conflict\n * with fork customizations than generic feature code:\n * - `src/core/api/**` — API provider implementations\n * - `src/shared/**` — Shared types and utilities used everywhere\n * - `src/services/**` — Backend services (MCP hub, etc.)\n * - `webview-ui/src/services/**`— Webview service layer\n */\nconst MEDIUM_RISK_PATTERNS = [\n \"src/core/api/**\",\n \"src/shared/**\",\n \"src/services/**\",\n \"webview-ui/src/services/**\",\n]\n\n/**\n * Classifies the risk level of an upstream commit based on which files it changes.\n *\n * Evaluation order (highest-priority first):\n * 1. Fork customization zones (`customizations.yaml`) → always `high`.\n * 2. `HIGH_RISK_PATTERNS` glob matches → always `high`.\n * 3. `MEDIUM_RISK_PATTERNS` glob matches → `medium` (if not already high).\n * 4. Detected file deletions or renames → elevate to at least `medium`.\n * 5. No matches → remains `low`.\n *\n * This function is **pure and deterministic** — given the same inputs it always\n * returns the same output. It has no side effects and performs no I/O.\n *\n * @param sha - Full or abbreviated commit SHA (used to label the result).\n * @param changedFiles - Repository-relative file paths changed by the commit,\n * as returned by `getCommitChangedFiles`.\n * @param customizations - Validated customizations manifest from `loadCustomizations`.\n * @returns A `CommitRisk` record with the computed level, reasons, and customization matches.\n */\nexport function classifyRisk(sha: string, changedFiles: string[], customizations: Customizations): CommitRisk {\n const reasons: string[] = []\n const matchedCustomizationIds: string[] = []\n let level: RiskLevel = \"low\"\n\n // --- Step 1: Check fork customization zones ---\n // Any file that matches a customization's glob pattern triggers high risk,\n // because it means an upstream change directly conflicts with our fork-specific code.\n for (const entry of customizations.customizations) {\n const hits = changedFiles.filter((f) => entry.paths.some((p) => minimatch(f, p)))\n if (hits.length > 0) {\n matchedCustomizationIds.push(entry.id)\n reasons.push(`Touches customization \"${entry.id}\": ${hits.join(\", \")}`)\n level = \"high\"\n }\n }\n\n // --- Step 2: Check high-risk file patterns ---\n // Build infrastructure changes (lockfiles, CI, tsconfig, proto) are always high risk\n // regardless of whether they touch a named customization zone.\n for (const pattern of HIGH_RISK_PATTERNS) {\n const hits = changedFiles.filter((f) => minimatch(f, pattern))\n if (hits.length > 0) {\n if (level !== \"high\") level = \"high\"\n reasons.push(`High-risk file pattern \"${pattern}\": ${hits.join(\", \")}`)\n }\n }\n\n // --- Step 3: Check medium-risk patterns (only if still low) ---\n // These patterns are checked last so that a high-risk determination from steps 1-2\n // is not overwritten. A medium classification means the agent should run the\n // standard validation suite but may still auto-apply.\n if (level === \"low\") {\n for (const pattern of MEDIUM_RISK_PATTERNS) {\n const hits = changedFiles.filter((f) => minimatch(f, pattern))\n if (hits.length > 0) {\n level = \"medium\"\n reasons.push(`Medium-risk pattern \"${pattern}\": ${hits.join(\", \")}`)\n }\n }\n }\n\n // --- Step 4: Deletions and renames ---\n // Removing or renaming files is inherently risky because dependent code may break.\n // Elevate to at least medium if not already high.\n const deletions = changedFiles.filter((f) => f.startsWith(\"DELETE:\") || f.startsWith(\"RENAME:\"))\n if (deletions.length > 0) {\n if (level === \"low\") level = \"medium\"\n reasons.push(`File deletions or renames detected`)\n }\n\n // --- Step 5: Fallback reason ---\n // Always include at least one reason so callers don't have to handle an empty array.\n if (reasons.length === 0) {\n reasons.push(\"No risk patterns matched — appears to be a low-risk change\")\n }\n\n return {\n sha,\n level,\n reasons,\n touchesCustomization: matchedCustomizationIds.length > 0,\n customizationIds: matchedCustomizationIds,\n }\n}\n","/**\n * @file risk/risk-tools.ts\n *\n * Factory that creates the `classify_commit_risk` agent tool.\n *\n * Risk classification is a deterministic gate that runs **before** any LLM\n * reasoning: the agent calls this tool first, learns the risk level and\n * affected customizations, then decides how to proceed (auto-apply, apply with\n * validation, or escalate to human review).\n *\n * The tool is kept in a separate factory function so that the validated\n * `customizations` object (loaded once at startup) can be captured by closure\n * and reused across every invocation without re-parsing the YAML file.\n */\n\nimport { z } from \"zod\"\nimport { defineTool } from \"../tool-helper.js\"\nimport { classifyRisk } from \"./classify-risk.js\"\nimport { getCommitChangedFiles } from \"../git/git-client.js\"\nimport type { SyncConfig } from \"../config/schema.js\"\nimport type { Customizations } from \"../customizations/schema.js\"\n\n/**\n * Builds and returns the `classify_commit_risk` agent tool.\n *\n * The tool is pre-bound to `config` and `customizations` so that callers only\n * need to provide the commit SHA at invocation time.\n *\n * @param config - Validated `SyncConfig` (provides `workingDir`).\n * @param customizations - Validated customizations manifest (provides zone definitions).\n * @returns A single agent tool: `classify_commit_risk`.\n */\nexport function makeRiskTool(config: SyncConfig, customizations: Customizations) {\n return defineTool({\n name: \"classify_commit_risk\",\n description:\n \"Classify the risk level of an upstream commit by analysing which files it changes. \" +\n \"Returns 'low', 'medium', or 'high' with human-readable reasons. \" +\n \"High risk means the commit touches fork customization zones or build-critical files. \" +\n \"This is a deterministic check — no LLM is used here.\",\n inputSchema: z.object({\n /** Full or abbreviated SHA of the upstream commit to classify. */\n sha: z.string().describe(\"Upstream commit SHA to classify\"),\n }),\n execute: async ({ sha }) => {\n // 1. Retrieve the list of paths changed by this commit from git.\n const changedFiles = getCommitChangedFiles(config.workingDir, sha)\n // 2. Run the deterministic pattern matcher against those paths.\n const risk = classifyRisk(sha, changedFiles, customizations)\n // The full CommitRisk object is returned to the agent as tool output.\n return risk\n },\n })\n}\n","/**\n * @file validation/commands.ts\n *\n * Allowlist-based command runner for the validation suite.\n *\n * **Security model:**\n * The LLM agent may suggest commands to run during validation. To prevent\n * arbitrary code execution, every command is checked against a fixed prefix\n * allowlist before being executed. Only standard package-manager test scripts\n * and well-known CLI tools are permitted. The allowlist is hard-coded in this\n * file and cannot be extended by the agent at runtime.\n *\n * **No shell interpolation:**\n * Commands are split on whitespace and executed via `execFileSync(bin, args[])`\n * (not through a shell). This prevents shell metacharacter injection even for\n * allowlisted commands.\n *\n * **Failure fast:**\n * `runValidationSuite` stops at the first failing command so that the agent\n * receives a clear, actionable error rather than a wall of cascading output.\n */\n\nimport { execFileSync } from \"node:child_process\"\n\n/**\n * Result of running a single validation command.\n */\nexport type CommandResult = {\n /** The original command string that was (attempted to be) executed. */\n command: string\n /** `true` if the command exited with code 0. */\n success: boolean\n /** Process exit code. `1` is used when the command was blocked by the allowlist. */\n exitCode: number\n /** Combined stdout + stderr output from the process (or the block reason). */\n output: string\n}\n\n/**\n * Hard-coded allowlist of command prefixes that the agent is permitted to execute.\n *\n * A command is allowed if and only if it starts with one of these strings.\n * The list is intentionally conservative:\n * - `\"npm run \"` — npm scripts defined in package.json\n * - `\"pnpm run \"` — pnpm equivalent\n * - `\"yarn run \"` — yarn equivalent\n * - `\"npx tsc\"` — TypeScript type-checker\n * - `\"npx eslint\"` — ESLint linter\n * - `\"npx vitest\"` — Vitest unit test runner\n * - `\"node --version\"` — Non-destructive version probe (useful for diagnostics)\n */\nexport const ALLOWED_COMMAND_PREFIXES = [\n \"npm run \",\n \"pnpm run \",\n \"yarn run \",\n \"npx tsc\",\n \"npx eslint\",\n \"npx vitest\",\n \"node --version\",\n]\n\n/**\n * Returns `true` if the command starts with an allowlisted prefix.\n *\n * The check is intentionally a simple `startsWith` — it does not parse the\n * full command. This makes the allowlist easy to audit.\n *\n * @param command - The full command string to check.\n * @returns `true` if the command is allowed, `false` if it should be blocked.\n */\nexport function isAllowedCommand(command: string): boolean {\n return ALLOWED_COMMAND_PREFIXES.some((prefix) => command.startsWith(prefix))\n}\n\n/**\n * Runs a single validation command and returns its result.\n *\n * If the command is not on the allowlist, it is immediately blocked and a\n * descriptive error is returned without executing anything.\n *\n * Execution details:\n * - `stdio: [\"pipe\",\"pipe\",\"pipe\"]` — all streams are captured, not printed.\n * - `timeout: 120_000` ms — commands are killed if they take more than 2 minutes.\n * - No `shell: true` — the command is parsed by whitespace split and executed directly.\n *\n * @param command - Full command string, e.g. `\"npm run typecheck\"`.\n * @param cwd - Absolute working directory in which to run the command.\n * @returns A `CommandResult` with the success flag, exit code, and captured output.\n */\nexport function runValidationCommand(command: string, cwd: string): CommandResult {\n // Security gate: reject any command not matching the allowlist.\n if (!isAllowedCommand(command)) {\n return {\n command,\n success: false,\n exitCode: 1,\n output: `Blocked: command \"${command}\" is not in the allowed list`,\n }\n }\n\n // Split on whitespace to build the [binary, ...args] array for execFileSync.\n // This avoids passing a shell-interpolated string.\n const parts = command.split(\" \")\n const bin = parts[0]\n const args = parts.slice(1)\n\n try {\n const output = execFileSync(bin, args, {\n cwd,\n encoding: \"utf-8\",\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n // 2-minute safety timeout — prevents hung test runners from blocking the agent.\n timeout: 120_000,\n })\n return { command, success: true, exitCode: 0, output }\n } catch (err: unknown) {\n // execFileSync throws when the process exits non-zero. Extract diagnostic info.\n const e = err as { status?: number; stdout?: string; stderr?: string; message?: string }\n // Combine all output streams for maximum debuggability.\n const output = [e.stdout, e.stderr, e.message].filter(Boolean).join(\"\\n\")\n return { command, success: false, exitCode: e.status ?? 1, output }\n }\n}\n\n/**\n * Runs an ordered list of validation commands, stopping on the first failure.\n *\n * \"Fail fast\" semantics are intentional: the agent should see the first broken\n * command and address it rather than drowning in cascading failures.\n *\n * @param commands - Ordered array of command strings to execute.\n * @param cwd - Absolute working directory for all commands.\n * @returns Array of `CommandResult` objects, one per executed command.\n * If a command fails, no subsequent commands are executed.\n */\nexport function runValidationSuite(commands: string[], cwd: string): CommandResult[] {\n const results: CommandResult[] = []\n for (const command of commands) {\n const result = runValidationCommand(command, cwd)\n results.push(result)\n // Stop on first failure — no point running further checks.\n if (!result.success) break\n }\n return results\n}\n","/**\n * @file validation/validation-tools.ts\n *\n * Factory that creates the `run_validation` agent tool.\n *\n * Validation is the agent's safety net: before accepting a cherry-picked commit\n * as \"done\", the agent runs the suite appropriate for the commit's risk level to\n * confirm that the fork still builds and passes tests.\n *\n * Risk → suite mapping (configured in `config.validation`):\n * - `\"low\"` → `config.validation.low` (e.g. only typecheck)\n * - `\"medium\"` → `config.validation.medium` (e.g. typecheck + unit tests)\n * - `\"high\"` → `config.validation.high` (e.g. full build + integration tests)\n *\n * The agent may append extra customization-specific commands via the\n * `extraCommands` input field. All commands (base suite + extras) are passed\n * through the same `ALLOWED_COMMAND_PREFIXES` allowlist in `commands.ts`.\n */\n\nimport { z } from \"zod\"\nimport { defineTool } from \"../tool-helper.js\"\nimport { runValidationSuite } from \"./commands.js\"\nimport type { SyncConfig } from \"../config/schema.js\"\nimport type { RiskLevel } from \"../risk/classify-risk.js\"\n\n/**\n * Builds and returns the `run_validation` agent tool.\n *\n * The tool is pre-bound to `config` so that the caller only needs to supply the\n * risk level and any optional extra commands at invocation time.\n *\n * @param config - Validated `SyncConfig` (provides `workingDir` and `validation` suites).\n * @returns A single agent tool: `run_validation`.\n */\nexport function makeValidationTool(config: SyncConfig) {\n return defineTool({\n name: \"run_validation\",\n description:\n \"Run the validation suite appropriate for a given risk level. \" +\n \"'low' runs only typecheck. 'medium' adds unit tests. 'high' adds build and integration tests. \" +\n \"All commands are allowlisted — arbitrary commands are rejected. \" +\n \"Returns success status and per-command output.\",\n inputSchema: z.object({\n /** Risk level computed by `classify_commit_risk`. Determines which suite to run. */\n riskLevel: z.enum([\"low\", \"medium\", \"high\"]).describe(\"Risk level determines which suite to run\"),\n /**\n * Optional additional commands to append to the standard suite.\n * Useful for customization-specific verification commands listed in\n * `customizations.yaml` under `testCommands`.\n * Each command must still match the `ALLOWED_COMMAND_PREFIXES` allowlist.\n */\n extraCommands: z\n .array(z.string())\n .optional()\n .describe(\"Additional commands to append, must match the allowed prefix list\"),\n }),\n execute: async ({ riskLevel, extraCommands = [] }) => {\n // Dry-run: skip all command execution and report success.\n if (config.sync.dryRun) {\n return { dryRun: true, results: [], allPassed: true }\n }\n\n // Map each risk level to its configured command list from config.validation.\n const suites: Record<RiskLevel, string[]> = {\n low: config.validation.low,\n medium: config.validation.medium,\n high: config.validation.high,\n }\n\n // Combine the standard suite with any caller-provided extra commands.\n const commands = [...suites[riskLevel], ...extraCommands]\n // Execute each command in order, stopping on the first failure.\n const results = runValidationSuite(commands, config.workingDir)\n const allPassed = results.every((r) => r.success)\n\n return { riskLevel, results, allPassed }\n },\n // 5-minute overall timeout for the entire suite (individual commands have 2-min timeouts).\n timeoutMs: 300_000,\n })\n}\n","/**\n * @file github/github-tools.ts\n *\n * GitHub API tools that allow the agent to manage pull requests on the fork\n * repository. All operations go through the official `@octokit/rest` client\n * which enforces HTTPS and authenticated requests.\n *\n * The three tools returned by `makeGitHubTools` cover the PR lifecycle:\n * 1. `find_existing_sync_pr` — check whether a previous run already opened a PR,\n * and if so, recover its machine-readable state so the current run can resume.\n * 2. `create_sync_pr` — open a new draft PR with the sync branch, embedding\n * both human-readable Markdown and a hidden machine-readable state block.\n * 3. `add_human_review_comment` — flag specific files or decisions for a human\n * reviewer by posting a comment on the PR.\n *\n * **Idempotency** is achieved via `STATE_MARKER_START/END` HTML comment markers\n * embedded inside the PR body. On each run the agent first calls\n * `find_existing_sync_pr`, which extracts and parses the JSON state block if\n * present, allowing the run to skip already-processed commits.\n *\n * **Authentication** is read from the `GITHUB_TOKEN` environment variable at\n * tool invocation time (not at module load time) so that the token is never\n * stored in process memory longer than necessary.\n */\n\nimport { z } from \"zod\"\nimport { defineTool } from \"../tool-helper.js\"\nimport { Octokit } from \"@octokit/rest\"\nimport type { SyncConfig } from \"../config/schema.js\"\n\n/**\n * Creates and returns an authenticated Octokit instance using the `GITHUB_TOKEN`\n * environment variable.\n *\n * Called inside each tool's `execute` function rather than once at module level\n * to keep the token out of long-lived closures.\n *\n * @returns Authenticated `Octokit` REST client.\n * @throws If `GITHUB_TOKEN` is not set in the environment.\n */\nfunction makeOctokit(): Octokit {\n const token = process.env.GITHUB_TOKEN\n if (!token) throw new Error(\"GITHUB_TOKEN environment variable is required\")\n return new Octokit({ auth: token })\n}\n\n/**\n * Parses an `\"owner/repo\"` string into its component parts.\n *\n * @param repoStr - Repository string in `\"owner/repo\"` format.\n * @returns An object with `owner` and `repo` string fields.\n * @throws If the string does not contain exactly one `/` separator.\n */\nfunction parseRepo(repoStr: string): { owner: string; repo: string } {\n const [owner, repo] = repoStr.split(\"/\")\n if (!owner || !repo) throw new Error(`Invalid repo format: \"${repoStr}\", expected \"owner/repo\"`)\n return { owner, repo }\n}\n\n/**\n * Opening delimiter of the hidden JSON state block embedded in the PR body.\n *\n * The state block is wrapped in an HTML comment so it is invisible in the\n * rendered PR view but can be extracted programmatically on re-runs.\n * Example embedded block:\n * ```\n * <!-- backport-agent-state\n * { \"processedShas\": [\"abc123\", \"def456\"] }\n * -->\n * ```\n */\nconst STATE_MARKER_START = \"<!-- backport-agent-state\\n\"\n\n/**\n * Closing delimiter of the hidden JSON state block embedded in the PR body.\n * @see STATE_MARKER_START\n */\nconst STATE_MARKER_END = \"\\n-->\"\n\n/**\n * Builds and returns the three GitHub API agent tools.\n *\n * All tools capture `fork`, `upstream`, and `sync` from the config via closure.\n * In dry-run mode, every tool returns early with `{ dryRun: true }` and performs\n * no network requests.\n *\n * @param config - Validated `SyncConfig` loaded from `config.json`.\n * @returns Array of three agent tools: `[findExistingPrTool, createSyncPrTool, addHumanReviewCommentTool]`.\n */\nexport function makeGitHubTools(config: SyncConfig) {\n // Destructure the config sections needed by the tools.\n const { fork, upstream, sync } = config\n\n /**\n * Tool: find_existing_sync_pr\n *\n * Queries the fork repository for open PRs whose title starts with\n * `\"Sync upstream\"` and whose body contains the `STATE_MARKER_START` sentinel.\n *\n * If a matching PR is found, the hidden state JSON is extracted and returned\n * so the calling agent can resume from where a previous run left off.\n */\n const findExistingPrTool = defineTool({\n name: \"find_existing_sync_pr\",\n description:\n \"Search for an existing open sync PR created by the backport agent. \" +\n \"Returns the PR number and current state JSON if found, null otherwise.\",\n inputSchema: z.object({}),\n execute: async () => {\n // Skip network call in dry-run mode.\n if (sync.dryRun) return { pr: null, dryRun: true }\n\n const octokit = makeOctokit()\n const { owner, repo } = parseRepo(fork.repo)\n\n // List open PRs whose head branch starts with the configured prefix.\n const { data: prs } = await octokit.pulls.list({\n owner,\n repo,\n state: \"open\",\n head: `${owner}:${sync.branchPrefix}`,\n per_page: 10,\n })\n\n // Find the most recent backport-agent PR by checking title and body marker.\n const agentPr = prs.find(\n (pr) => pr.title.startsWith(\"Sync upstream\") && pr.body?.includes(STATE_MARKER_START),\n )\n if (!agentPr) return { pr: null }\n\n // Extract the embedded JSON state from the PR body.\n let agentState: Record<string, unknown> | null = null\n if (agentPr.body) {\n const start = agentPr.body.indexOf(STATE_MARKER_START)\n const end = agentPr.body.indexOf(STATE_MARKER_END, start)\n if (start !== -1 && end !== -1) {\n try {\n // Slice out just the JSON content between the two markers.\n agentState = JSON.parse(agentPr.body.slice(start + STATE_MARKER_START.length, end))\n } catch {\n // Malformed JSON is treated as missing state — the run starts fresh.\n agentState = null\n }\n }\n }\n return { pr: { number: agentPr.number, url: agentPr.html_url, state: agentState } }\n },\n })\n\n /**\n * Tool: create_sync_pr\n *\n * Opens a draft pull request from the sync branch into `fork.branch`. The PR\n * body consists of the agent-generated Markdown summary followed by the hidden\n * state block. Labels are applied as a best-effort operation (non-fatal if\n * they don't exist on the repository).\n */\n const createSyncPrTool = defineTool({\n name: \"create_sync_pr\",\n description:\n \"Create a draft pull request on the fork repository with the sync branch. \" +\n \"Embeds a hidden state block for idempotent re-runs. Returns the PR URL.\",\n inputSchema: z.object({\n /** Name of the local/remote sync branch created by `create_sync_branch`. */\n branchName: z.string(),\n /** Human-readable Markdown body shown in the GitHub PR UI. */\n markdownBody: z.string().describe(\"Human-readable PR body in Markdown\"),\n /** Machine-readable JSON state to embed as a hidden comment for re-run idempotency. */\n agentState: z.record(z.string(), z.unknown()).describe(\"Machine-readable state to embed in the PR body\"),\n /** Labels to apply to the PR. Defaults to `[\"sync\", \"agent-generated\"]`. */\n labels: z.array(z.string()).default([\"sync\", \"agent-generated\"]),\n }),\n execute: async ({ branchName, markdownBody, agentState, labels }) => {\n // Skip PR creation in dry-run mode.\n if (sync.dryRun) return { url: null, dryRun: true }\n\n const octokit = makeOctokit()\n const { owner, repo } = parseRepo(fork.repo)\n const date = new Date().toISOString().slice(0, 10)\n\n // Build the hidden state block: JSON surrounded by the HTML comment markers.\n const hiddenState = `${STATE_MARKER_START}${JSON.stringify(agentState, null, 2)}${STATE_MARKER_END}`\n // Concatenate the human-readable body with the hidden state block.\n const body = `${markdownBody}\\n\\n${hiddenState}`\n\n // Create the draft PR via the GitHub REST API.\n const { data: pr } = await octokit.pulls.create({\n owner,\n repo,\n title: `Sync upstream ${upstream.branch} into ${fork.branch} (${date})`,\n body,\n head: branchName,\n base: fork.branch,\n draft: true,\n })\n\n // Apply labels — best-effort; labels may not exist on the repository.\n try {\n await octokit.issues.addLabels({ owner, repo, issue_number: pr.number, labels })\n } catch {\n // Non-fatal: labels are cosmetic and their absence does not affect workflow.\n }\n\n return { url: pr.html_url, number: pr.number }\n },\n })\n\n /**\n * Tool: add_human_review_comment\n *\n * Posts a Markdown comment on an existing sync PR. Used when the agent\n * encounters a conflict or edge case it cannot safely resolve automatically\n * and needs to escalate to a human reviewer.\n */\n const addHumanReviewCommentTool = defineTool({\n name: \"add_human_review_comment\",\n description:\n \"Add a comment to the sync PR flagging a specific file or decision for human review. \" +\n \"Use when the agent cannot safely resolve a conflict automatically.\",\n inputSchema: z.object({\n /** PR number on the fork repository to comment on. */\n prNumber: z.number().int(),\n /** Markdown-formatted comment body explaining what needs human attention. */\n comment: z.string().describe(\"Markdown comment explaining what needs human attention\"),\n }),\n execute: async ({ prNumber, comment }) => {\n // Skip comment posting in dry-run mode.\n if (sync.dryRun) return { commented: false, dryRun: true }\n\n const octokit = makeOctokit()\n const { owner, repo } = parseRepo(fork.repo)\n // Post the comment as a regular issue comment (PRs are issues in GitHub API).\n await octokit.issues.createComment({ owner, repo, issue_number: prNumber, body: comment })\n return { commented: true }\n },\n })\n\n return [findExistingPrTool, createSyncPrTool, addHumanReviewCommentTool]\n}\n","/**\n * @file reports/report-tools.ts\n *\n * Factory that creates the `generate_report` agent tool.\n *\n * The report tool is the **terminal step** of every sync run: the agent calls it\n * after processing all candidate commits. Setting `lifecycle: { completesRun: true }`\n * signals to the `@sctg/cline-sdk` runtime that the agent should stop after this\n * tool returns, so the tool doubles as both report generator and run terminator.\n *\n * The tool produces:\n * - A human-readable **Markdown string** suitable for use as a GitHub PR body.\n * - A compact **agentState** object that can be embedded in the PR body (hidden\n * HTML comment) for idempotent re-runs (see `github-tools.ts`).\n * - Boolean flags `allPassed` and `needsHumanReview` for caller logic.\n * - A detailed **Markdown file** written to `config.report.destination` combining\n * the PR-body summary, a Mermaid workflow diagram generated by the fast model,\n * and a full transcript of every AI sub-agent call from the prompts JSONL log.\n *\n * Report sections (PR body):\n * 1. Header — date, upstream ref, fork ref, sync branch name.\n * 2. Summary — counts of applied / needs-review / blocked commits.\n * 3. Applied commits — listed with risk badges and conflict-resolution notes.\n * 4. Human review required — conflicted or validation-failed commits with reasons.\n * 5. Blocked commits — SHAs that were not attempted at all.\n * 6. Agent decision log — ordered audit trail of key agent decisions.\n *\n * Additional sections (detailed file only):\n * 7. Mermaid workflow diagram — generated by the fast LLM from the run summary.\n * 8. AI sub-agent call log — full prompt/response transcript from the JSONL log.\n */\n\nimport { z } from \"zod\"\nimport { readFileSync, writeFileSync, mkdirSync, existsSync } from \"node:fs\"\nimport { resolve as resolvePath, join as joinPath, relative as relativePath } from \"node:path\"\nimport { git } from \"../git/git-client.js\"\nimport { Agent } from \"@sctg/cline-sdk\"\nimport { defineTool } from \"../tool-helper.js\"\nimport type { SyncConfig } from \"../config/schema.js\"\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Shape of a single entry in the `.prompts.jsonl` log file.\n */\ninterface PromptLogEntry {\n timestamp: string\n tool: string\n model: string\n durationMs: number\n prompt: string\n response: string\n error?: string\n inputTokens?: number\n outputTokens?: number\n cacheReadTokens?: number\n cacheWriteTokens?: number\n totalCost?: number\n}\n\n/**\n * Reads the JSONL prompt log, returning all valid entries.\n * Silently skips malformed lines so a corrupt log cannot block the report.\n */\nfunction readPromptLog(logPath: string): PromptLogEntry[] {\n if (!existsSync(logPath)) return []\n try {\n return readFileSync(logPath, \"utf8\")\n .split(\"\\n\")\n .filter(Boolean)\n .flatMap((line) => {\n try {\n return [JSON.parse(line) as PromptLogEntry]\n } catch {\n return []\n }\n })\n } catch {\n return []\n }\n}\n\n/**\n * Instantiates a minimal sub-Agent with no tools for a single reasoning turn.\n * Identical in purpose to the helper in `ai-tools.ts` but local to avoid a\n * cross-module import cycle.\n */\nfunction makeReportSubAgent(modelId: string, systemPrompt: string): Agent {\n return new Agent({\n providerId: \"keypoollive\",\n modelId,\n apiKey: \"auto\",\n systemPrompt,\n tools: [],\n })\n}\n\n/**\n * Calls the fast model to produce a Mermaid flowchart summarising the agent run.\n * Returns a fenced mermaid code block string, or a fallback placeholder on error.\n */\nasync function generateMermaidDiagram(\n config: SyncConfig,\n runSummary: string,\n): Promise<string> {\n const systemPrompt = `You are a technical diagram generator. When given a summary of a Git backport agent run, \\\nproduce a single Mermaid flowchart (flowchart TD) that visually represents: \\\n(1) each candidate commit as a node labelled with its short SHA and subject, \\\n(2) the risk level applied to each commit (low / medium / high), \\\n(3) which AI tools were invoked (analyze_commit_for_backport, check_customization_compatibility, resolve_conflict_with_ai), \\\n(4) the final disposition of each commit (applied ✅, blocked ⛔, needs-review ⚠️, skipped ⟳). \\\nUse colour-coded styles: applied=green, blocked=red, needs-review=orange, skipped=grey. \\\nOutput ONLY the raw Mermaid code, no prose, no code fence.`\n\n const userPrompt = `Agent run summary:\\n\\n${runSummary}\\n\\nOutput the Mermaid flowchart now.`\n\n try {\n const subAgent = makeReportSubAgent(config.models.fast, systemPrompt)\n const result = await subAgent.run(userPrompt)\n const raw = (result.outputText ?? \"\").trim()\n // Strip any accidental code fence if the model added one.\n const inner = raw.replace(/^```(?:mermaid)?\\n?/i, \"\").replace(/\\n?```$/, \"\").trim()\n return \"```mermaid\\n\" + inner + \"\\n```\"\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n return `<!-- Mermaid generation failed: ${msg} -->`\n }\n}\n\n// ---------------------------------------------------------------------------\n// Zod schemas\n// ---------------------------------------------------------------------------\n\n/**\n * Zod schema for the result of processing a single upstream commit.\n *\n * The agent populates one `CommitResult` per candidate commit processed during\n * the run. These are aggregated by the report tool to produce the final summary.\n */\nconst CommitResultSchema = z.object({\n /** Full SHA of the upstream commit. */\n sha: z.string(),\n /** Commit subject line (first line of the message). */\n subject: z.string(),\n /** Risk level assigned by `classify_commit_risk`. */\n riskLevel: z.enum([\"low\", \"medium\", \"high\"]),\n /**\n * Final disposition of this commit:\n * - `\"applied\"` — cherry-picked cleanly with no conflicts.\n * - `\"skipped\"` — already applied in the fork (git cherry found equivalent patch).\n * - `\"conflict-resolved\"` — had conflicts that the agent resolved automatically.\n * - `\"conflict-blocked\"` — had conflicts the agent could not safely resolve; needs human review.\n * - `\"validation-failed\"` — cherry-picked cleanly but the validation suite failed.\n */\n status: z.enum([\"applied\", \"skipped\", \"conflict-resolved\", \"conflict-blocked\", \"validation-failed\"]),\n /** Paths of files that had merge conflicts (populated for conflict-* statuses). */\n conflictedFiles: z.array(z.string()).optional(),\n /** Human-readable reasons why this commit needs manual review. */\n humanReviewReasons: z.array(z.string()).optional(),\n /** Per-command validation results, populated when `status === \"validation-failed\"`. */\n validationResults: z.array(z.object({ command: z.string(), success: z.boolean(), output: z.string() })).optional(),\n})\n\n/**\n * TypeScript type for a single commit result, inferred from `CommitResultSchema`.\n */\nexport type CommitResult = z.infer<typeof CommitResultSchema>\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Builds and returns the `generate_report` agent tool.\n *\n * @param config - Validated `SyncConfig` — used for model IDs and `report.destination`.\n * @param promptLogPath - Absolute path to the JSONL file written by `logPrompt()` in ai-tools.ts.\n * @returns A single agent tool: `generate_report` (with `completesRun: true`).\n */\nexport function makeReportTool(config: SyncConfig, promptLogPath: string) {\n return defineTool({\n name: \"generate_report\",\n description:\n \"Generate the final sync report as a Markdown string suitable for a PR body. \" +\n \"Call this as the LAST step after all commits have been processed. \" +\n \"Returns the report text AND signals that the agent run is complete.\",\n inputSchema: z.object({\n /** Name of the sync branch, or `null` in dry-run mode. */\n syncBranch: z.string().nullable(),\n /** Full ref of the upstream branch, e.g. `\"upstream/main\"`. */\n upstreamRef: z.string(),\n /** Full ref of the fork branch, e.g. `\"origin/main\"`. */\n forkRef: z.string(),\n /** Array of per-commit results — one entry per processed candidate. */\n commitResults: z.array(CommitResultSchema),\n /** Commits that were not attempted at all, with mandatory reasons. */\n blockedCommits: z.array(z.object({\n /** Full or abbreviated SHA of the blocked commit. */\n sha: z.string(),\n /** Human-readable reason why this commit was not attempted. */\n reason: z.string().describe(\"Why this commit was not attempted (AI analysis result, policy, etc.)\"),\n /** Risk level from classify_commit_risk, if known. */\n riskLevel: z.enum([\"low\", \"medium\", \"high\"]).optional(),\n })).describe(\"Commits not attempted, each with a specific reason\"),\n /**\n * All SHAs returned by list_candidate_commits — used to detect silently dropped commits.\n * Pass the complete list so the report can cross-check accountability.\n */\n allCandidateShas: z.array(z.string()).optional().describe(\n \"Complete list of SHAs from list_candidate_commits, used to detect unaccounted commits\"\n ),\n /** Ordered list of key decisions the agent made during this run, for audit purposes. */\n agentDecisions: z.array(z.string()).describe(\"Audit trail of key decisions made during this run\"),\n }),\n // completesRun:true tells the SDK to stop the agent loop after this tool returns.\n lifecycle: { completesRun: true },\n execute: async ({ syncBranch, upstreamRef, forkRef, commitResults, blockedCommits, agentDecisions, allCandidateShas }) => {\n const date = new Date().toISOString()\n const timestampSlug = date.replace(/[:.]/g, \"-\").replace(\"T\", \"_\").slice(0, 19)\n\n // --- Partition commits by final status for the summary section ---\n const applied = commitResults.filter((r) => [\"applied\", \"conflict-resolved\"].includes(r.status))\n const needsReview = commitResults.filter((r) => [\"conflict-blocked\", \"validation-failed\"].includes(r.status))\n const allPassed = needsReview.length === 0\n\n // --- Accountability check: detect silently dropped commits ---\n const processedShas = new Set([\n ...commitResults.map((r) => r.sha),\n ...blockedCommits.map((c) => c.sha),\n ])\n const unaccounted = (allCandidateShas ?? []).filter(\n (sha) => !processedShas.has(sha) && !processedShas.has(sha.slice(0, 8))\n )\n\n // --- Build the PR-body Markdown line by line ---\n const prBodyLines: string[] = [\n \"## Backport Agent — Sync Report\",\n \"\",\n `**Date**: ${date}`,\n `**Upstream ref**: \\`${upstreamRef}\\``,\n `**Fork ref**: \\`${forkRef}\\``,\n `**Sync branch**: ${syncBranch ? `\\`${syncBranch}\\`` : \"_dry-run (no branch created)_\"}`,\n \"\",\n \"### Summary\",\n \"\",\n `- ✅ Applied: ${applied.length}`,\n `- ⚠️ Needs human review: ${needsReview.length}`,\n `- ⛔ Blocked (not attempted): ${blockedCommits.length}`,\n ...(unaccounted.length > 0 ? [`- 🔴 Unaccounted (agent bug): ${unaccounted.length}`] : []),\n \"\",\n ]\n\n if (applied.length > 0) {\n prBodyLines.push(\"### Applied commits\", \"\")\n for (const r of applied) {\n const badge = r.status === \"conflict-resolved\" ? \" _(conflict resolved by agent)_\" : \"\"\n prBodyLines.push(`- \\`${r.sha.slice(0, 8)}\\` [${r.riskLevel}] ${r.subject}${badge}`)\n }\n prBodyLines.push(\"\")\n }\n\n if (needsReview.length > 0) {\n prBodyLines.push(\"### ⚠️ Human review required\", \"\")\n for (const r of needsReview) {\n prBodyLines.push(`- \\`${r.sha.slice(0, 8)}\\` ${r.subject}`)\n if (r.conflictedFiles?.length) prBodyLines.push(` - Conflicted files: ${r.conflictedFiles.join(\", \")}`)\n if (r.humanReviewReasons?.length) {\n for (const reason of r.humanReviewReasons) prBodyLines.push(` - ${reason}`)\n }\n }\n prBodyLines.push(\"\")\n }\n\n if (blockedCommits.length > 0) {\n prBodyLines.push(\"### Blocked commits (not attempted)\", \"\")\n for (const { sha, reason, riskLevel } of blockedCommits) {\n const badge = riskLevel ? ` [${riskLevel}]` : \"\"\n prBodyLines.push(`- \\`${sha.slice(0, 8)}\\`${badge} — ${reason}`)\n }\n prBodyLines.push(\"\")\n }\n\n if (unaccounted.length > 0) {\n prBodyLines.push(\"### 🔴 Unaccounted commits (agent processing gap)\", \"\")\n for (const sha of unaccounted) prBodyLines.push(`- \\`${sha.slice(0, 8)}\\` — not processed or reported by agent`)\n prBodyLines.push(\"\")\n }\n\n if (agentDecisions.length > 0) {\n prBodyLines.push(\"### Agent decision log\", \"\")\n for (const decision of agentDecisions) prBodyLines.push(`- ${decision}`)\n prBodyLines.push(\"\")\n }\n\n const report = prBodyLines.join(\"\\n\")\n\n // --- Build the machine-readable state object for idempotent re-runs ---\n const agentState = {\n generatedAt: date,\n appliedShas: applied.map((r) => r.sha),\n blockedShas: blockedCommits.map((c) => c.sha),\n needsReviewShas: needsReview.map((r) => r.sha),\n unaccountedShas: unaccounted,\n }\n\n // -----------------------------------------------------------------------\n // Detailed report file — combines PR body, Mermaid diagram, and AI call log\n // -----------------------------------------------------------------------\n\n // Build a compact plain-text run summary for the Mermaid prompt.\n const runSummaryForDiagram = [\n `Upstream: ${upstreamRef} Fork: ${forkRef}`,\n `Applied (${applied.length}): ${applied.map((r) => `${r.sha.slice(0, 8)} [${r.riskLevel}] ${r.subject}`).join(\" | \") || \"none\"}`,\n `Blocked (${blockedCommits.length}): ${blockedCommits.map((c) => `${c.sha.slice(0, 8)} — ${c.reason}`).join(\" | \") || \"none\"}`,\n `Needs review (${needsReview.length}): ${needsReview.map((r) => r.sha.slice(0, 8)).join(\", \") || \"none\"}`,\n `AI calls: ${readPromptLog(promptLogPath).map((e) => `${e.tool}(${e.model}, ${e.durationMs}ms)`).join(\", \") || \"none\"}`,\n ].join(\"\\n\")\n\n const mermaidBlock = await generateMermaidDiagram(config, runSummaryForDiagram)\n\n // Read and format the prompt log entries.\n const promptEntries = readPromptLog(promptLogPath)\n\n const detailedLines: string[] = [\n \"# Backport Agent — Detailed Run Report\",\n \"\",\n \"> This file is generated automatically. It is intended for human analysts and\",\n \"> AI reasoning models to assess agent performance, decision quality, and LLM behavior.\",\n \"\",\n `**Run timestamp**: ${date}`,\n `**Models used**: fast=\\`${config.models.fast}\\` powerful=\\`${config.models.powerful}\\``,\n `**Prompt log**: \\`${promptLogPath}\\``,\n \"\",\n \"---\",\n \"\",\n \"## Summary (PR body)\",\n \"\",\n report,\n \"\",\n \"---\",\n \"\",\n \"## Agent Workflow Diagram\",\n \"\",\n \"> Generated by the fast model from the run summary. Evaluate the agent's decision path.\",\n \"\",\n mermaidBlock,\n \"\",\n \"---\",\n \"\",\n `## AI Sub-Agent Call Log (${promptEntries.length} call${promptEntries.length === 1 ? \"\" : \"s\"})`,\n \"\",\n \"> Each entry is one sub-agent invocation. Review prompt/response pairs to assess\",\n \"> reasoning quality, hallucinations, and decision accuracy.\",\n \"\",\n ]\n\n for (let i = 0; i < promptEntries.length; i++) {\n const e = promptEntries[i]\n const statusBadge = e.error ? \"❌ Error\" : \"✅ OK\"\n detailedLines.push(\n `### Call ${i + 1} / ${promptEntries.length} — \\`${e.tool}\\` ${statusBadge}`,\n \"\",\n `| Field | Value |`,\n `|---|---|`,\n `| **Timestamp** | ${e.timestamp} |`,\n `| **Tool** | \\`${e.tool}\\` |`,\n `| **Model** | \\`${e.model}\\` |`,\n `| **Duration** | ${e.durationMs} ms |`,\n ...(e.inputTokens != null ? [`| **Tokens in** | ${e.inputTokens} |`] : []),\n ...(e.outputTokens != null ? [`| **Tokens out** | ${e.outputTokens} |`] : []),\n ...(e.cacheReadTokens != null && e.cacheReadTokens > 0 ? [`| **Cache read** | ${e.cacheReadTokens} |`] : []),\n ...(e.cacheWriteTokens != null && e.cacheWriteTokens > 0 ? [`| **Cache write** | ${e.cacheWriteTokens} |`] : []),\n ...(e.totalCost != null ? [`| **Cost** | $${e.totalCost.toFixed(6)} |`] : []),\n ...(e.error ? [`| **Error** | ${e.error} |`] : []),\n \"\",\n \"**Prompt sent to sub-agent:**\",\n \"\",\n \"```\",\n e.prompt,\n \"```\",\n \"\",\n \"**Response received:**\",\n \"\",\n \"```\",\n e.response || \"(empty)\",\n \"```\",\n \"\",\n \"---\",\n \"\",\n )\n }\n\n if (promptEntries.length === 0) {\n detailedLines.push(\"_No AI sub-agent calls were made during this run._\", \"\", \"---\", \"\")\n }\n\n // Performance summary table.\n if (promptEntries.length > 0) {\n const totalMs = promptEntries.reduce((sum, e) => sum + e.durationMs, 0)\n const totalIn = promptEntries.reduce((sum, e) => sum + (e.inputTokens ?? 0), 0)\n const totalOut = promptEntries.reduce((sum, e) => sum + (e.outputTokens ?? 0), 0)\n const totalCost = promptEntries.reduce((sum, e) => sum + (e.totalCost ?? 0), 0)\n const byTool = new Map<string, { count: number; totalMs: number; totalIn: number; totalOut: number }>()\n for (const e of promptEntries) {\n const cur = byTool.get(e.tool) ?? { count: 0, totalMs: 0, totalIn: 0, totalOut: 0 }\n byTool.set(e.tool, {\n count: cur.count + 1,\n totalMs: cur.totalMs + e.durationMs,\n totalIn: cur.totalIn + (e.inputTokens ?? 0),\n totalOut: cur.totalOut + (e.outputTokens ?? 0),\n })\n }\n const hasTokenData = totalIn > 0 || totalOut > 0\n detailedLines.push(\n \"## Performance Summary\",\n \"\",\n `**Total AI time**: ${totalMs} ms across ${promptEntries.length} call(s)`,\n ...(hasTokenData ? [\n `**Total input tokens**: ${totalIn}`,\n `**Total output tokens**: ${totalOut}`,\n ...(totalCost > 0 ? [`**Total estimated cost**: $${totalCost.toFixed(6)}`] : []),\n ] : []),\n \"\",\n hasTokenData\n ? \"| Tool | Calls | Total ms | Avg ms | Input tok | Output tok |\"\n : \"| Tool | Calls | Total ms | Avg ms |\",\n hasTokenData ? \"|---|---|---|---|---|---|\" : \"|---|---|---|---|\",\n ...[...byTool.entries()].map(\n ([tool, s]) => hasTokenData\n ? `| \\`${tool}\\` | ${s.count} | ${s.totalMs} | ${Math.round(s.totalMs / s.count)} | ${s.totalIn} | ${s.totalOut} |`\n : `| \\`${tool}\\` | ${s.count} | ${s.totalMs} | ${Math.round(s.totalMs / s.count)} |`,\n ),\n \"\",\n )\n }\n\n // Write the detailed report to disk (skipped in dry-run mode).\n if (config.sync.dryRun) {\n process.stderr.write(\"[Report] Dry-run mode — detailed report not written to disk.\\n\")\n } else {\n try {\n const destDir = resolvePath(config.workingDir, config.report.destination)\n mkdirSync(destDir, { recursive: true })\n const reportFilename = `report.${timestampSlug}.md`\n const reportFilePath = joinPath(destDir, reportFilename)\n writeFileSync(reportFilePath, detailedLines.join(\"\\n\"), \"utf8\")\n process.stderr.write(`[Report] Detailed report written to: ${reportFilePath}\\n`)\n\n // Commit and push the report file to the sync branch if inside the repo.\n const relPath = relativePath(config.workingDir, reportFilePath)\n if (syncBranch && !relPath.startsWith(\"..\")) {\n git([\"add\", relPath], config.workingDir)\n git([\"commit\", \"-m\", `chore(backport): add run report ${reportFilename}`], config.workingDir)\n git([\"push\", config.fork.remote, syncBranch], config.workingDir)\n process.stderr.write(`[Report] Report committed and pushed to ${config.fork.remote}/${syncBranch}\\n`)\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n process.stderr.write(`[Report] Warning: could not write/commit detailed report: ${msg}\\n`)\n }\n }\n\n return { report, agentState, allPassed, needsHumanReview: needsReview.length > 0 }\n },\n })\n}\n\n","/**\n * @file ai/ai-tools.ts\n *\n * AI-powered agent tools that spawn focused sub-agents for tasks requiring\n * deep language model reasoning beyond what the deterministic rule engine\n * can provide.\n *\n * **Why sub-agents?**\n * The main backport agent drives the orchestration loop and calls deterministic\n * git/GitHub/validation tools. Some decisions — conflict resolution, semantic\n * understanding of diffs, customisation compatibility — benefit from a dedicated\n * LLM call with a narrowly scoped system prompt and no distracting tool context.\n * Spawning a sub-`Agent` with `tools: []` gives exactly that: a single-turn\n * reasoning call that returns structured JSON.\n *\n * **Tools exported by `makeAiTools`:**\n *\n * 1. `resolve_conflict_with_ai` — Given a three-way conflict (base / ours /\n * theirs), produce a resolved file body with no conflict markers.\n * Uses `config.models.powerful` for maximum reasoning quality.\n *\n * 2. `analyze_commit_for_backport` — Given a commit SHA, message, diff, and\n * changed-file list, produce a structured semantic assessment of what the\n * commit does and how risky it is to backport.\n * Uses `config.models.fast` (analytical but not critical-path).\n *\n * 3. `check_customization_compatibility` — Given a diff and a list of\n * customisation records (pattern + description), reason about whether the\n * upstream changes could semantically break fork-specific behaviour even\n * if no textual conflict exists.\n * Uses `config.models.fast`.\n *\n * **JSON extraction:**\n * Sub-agents are instructed to emit only a JSON object. In practice, some\n * models wrap the object in a markdown code fence. `extractJson` strips the\n * fence when present so `JSON.parse` always receives clean text.\n *\n * **Error handling:**\n * If the sub-agent call fails (network error, model error, parse error), each\n * tool returns a structured fallback object with `error` set rather than\n * throwing, so the main agent can log the failure and continue.\n */\n\nimport { z } from \"zod\"\nimport { Agent } from \"@sctg/cline-sdk\"\nimport { defineTool } from \"../tool-helper.js\"\nimport type { SyncConfig } from \"../config/schema.js\"\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Attempts to extract a JSON value from raw LLM output.\n *\n * The LLM may wrap its JSON in a markdown code fence (```` ```json … ``` ````\n * or ```` ``` … ``` ````). This helper strips the fences when present so\n * `JSON.parse` receives valid JSON text.\n *\n * @typeParam T - Expected shape of the parsed value.\n * @param text - Raw string output from the sub-agent.\n * @returns The parsed value cast to `T`.\n * @throws `SyntaxError` if no valid JSON can be found in the text.\n */\nfunction extractJson<T>(text: string): T {\n // 1. Try ```json … ``` code fence (most common for structured output)\n const jsonFenceMatch = text.match(/```json\\s*([\\s\\S]*?)```/)\n if (jsonFenceMatch) {\n return JSON.parse(jsonFenceMatch[1].trim()) as T\n }\n\n // 2. Try generic ``` … ``` code fence\n const genericFenceMatch = text.match(/```\\s*([\\s\\S]*?)```/)\n if (genericFenceMatch) {\n return JSON.parse(genericFenceMatch[1].trim()) as T\n }\n\n // 3. Try to find an inline JSON object `{…}` in the text\n const inlineObjectMatch = text.match(/\\{[\\s\\S]*\\}/)\n if (inlineObjectMatch) {\n return JSON.parse(inlineObjectMatch[0]) as T\n }\n\n // 4. Last resort: parse the full trimmed text\n return JSON.parse(text.trim()) as T\n}\n\n// ---------------------------------------------------------------------------\n// PromptLogger — records all sub-agent LLM interactions to a JSONL file\n// ---------------------------------------------------------------------------\n\nimport { appendFileSync } from \"node:fs\"\n\n/**\n * Token usage breakdown from a single sub-agent LLM call.\n */\ninterface TokenUsage {\n inputTokens: number\n outputTokens: number\n cacheReadTokens: number\n cacheWriteTokens: number\n totalCost?: number\n}\n\n/**\n * Appends a single prompt/response record to the run's JSONL log file.\n *\n * The log file path is `./run-<timestamp>.prompts.jsonl` (relative to cwd).\n * It is created on first write and appended on subsequent calls.\n * Each line is a self-contained JSON object — suitable for streaming analysis\n * and incremental loading without parsing the entire file.\n *\n * @param logPath - Absolute path to the JSONL log file for this run.\n * @param toolName - The backport agent tool that invoked the sub-agent.\n * @param modelId - LLM model identifier used for this call.\n * @param prompt - Full user prompt sent to the sub-agent.\n * @param response - Raw text response received from the sub-agent.\n * @param durationMs - Wall-clock time for the sub-agent call in milliseconds.\n * @param error - Optional error message if the call failed.\n * @param usage - Optional token usage breakdown from the LLM response.\n */\nfunction logPrompt(\n logPath: string,\n toolName: string,\n modelId: string,\n prompt: string,\n response: string,\n durationMs: number,\n error?: string | null,\n usage?: TokenUsage | null,\n): void {\n const record = {\n timestamp: new Date().toISOString(),\n tool: toolName,\n model: modelId,\n durationMs,\n prompt,\n response,\n ...(error ? { error } : {}),\n ...(usage ? {\n inputTokens: usage.inputTokens,\n outputTokens: usage.outputTokens,\n cacheReadTokens: usage.cacheReadTokens,\n cacheWriteTokens: usage.cacheWriteTokens,\n ...(usage.totalCost != null ? { totalCost: usage.totalCost } : {}),\n } : {}),\n }\n try {\n appendFileSync(logPath, JSON.stringify(record) + \"\\n\", \"utf8\")\n } catch {\n // Log write failures are non-fatal — the agent run must not be blocked.\n process.stderr.write(`[PromptLogger] Warning: could not write to ${logPath}\\n`)\n }\n}\n\n/**\n * Creates a minimal sub-`Agent` configured with the keypoollive provider.\n *\n * The sub-agent has an empty tools array — it performs a single reasoning turn\n * and returns its text output via `result.outputText`.\n *\n * @param modelId - Model identifier to use (fast or powerful).\n * @param systemPrompt - System prompt that scopes the sub-agent's behaviour.\n * @returns A configured `Agent` instance ready to call `.run(userPrompt)`.\n */\nfunction makeSubAgent(modelId: string, systemPrompt: string): Agent {\n return new Agent({\n providerId: \"keypoollive\",\n modelId,\n apiKey: \"auto\",\n systemPrompt,\n tools: [],\n })\n}\n\n// ---------------------------------------------------------------------------\n// Exported factory\n// ---------------------------------------------------------------------------\n\n/**\n * Builds and returns all AI-powered agent tools pre-bound to the provided config.\n *\n * @param config - Validated `SyncConfig` loaded from `config.json`.\n * @param logPath - Absolute path to the JSONL prompt log file for this run.\n * Created by `main.ts` as `run-<timestamp>.prompts.jsonl`.\n * @returns Array of three agent tools for AI-assisted analysis.\n */\nexport function makeAiTools(config: SyncConfig, logPath: string) {\n // -------------------------------------------------------------------------\n // Tool 1: resolve_conflict_with_ai\n // -------------------------------------------------------------------------\n\n /**\n * Tool: resolve_conflict_with_ai\n *\n * Spawns a focused sub-agent to produce a conflict-free merged version of a\n * file that has a three-way merge conflict.\n *\n * The sub-agent receives the base (common ancestor), our (fork) version, and\n * their (upstream) version of the file, together with the upstream commit\n * message for context. It reasons about the intent of each side and returns\n * a merged body with no conflict markers.\n *\n * Uses `config.models.powerful` because incorrect conflict resolution can\n * silently break fork-specific behaviour.\n */\n const resolveConflictTool = defineTool({\n name: \"resolve_conflict_with_ai\",\n description:\n \"Resolves a three-way merge conflict in a single file using AI reasoning. \" +\n \"Provide the base (common ancestor), our (fork) version, and their (upstream) version \" +\n \"of the file, plus the upstream commit message for context. \" +\n \"Returns the resolved file content with no conflict markers, a confidence level, \" +\n \"and a brief reasoning summary. \" +\n \"Use this when cherry_pick_commit reports a conflict and you need to resolve a specific file.\",\n inputSchema: z.object({\n /**\n * Repository-relative path to the conflicted file (e.g. `src/core/api/index.ts`).\n * Used only for display/reasoning context — no filesystem access is performed.\n */\n filePath: z.string().describe(\"Repo-relative path of the conflicted file\"),\n\n /**\n * Content of the file at the common ancestor commit (merge base).\n * May be an empty string when the file was created on both branches independently.\n */\n baseContent: z.string().describe(\"File content at the common ancestor (merge base); empty string if none\"),\n\n /**\n * Content of the file in the fork branch (our version, with our customisations).\n */\n ourContent: z.string().describe(\"File content in the fork branch (our customised version)\"),\n\n /**\n * Content of the file in the upstream commit (their version).\n */\n theirContent: z.string().describe(\"File content from the upstream commit (their version)\"),\n\n /**\n * The full commit message of the upstream change being cherry-picked.\n * Helps the model understand the intent of the upstream change.\n */\n commitMessage: z.string().describe(\"Upstream commit message, used to understand the intent of the change\"),\n\n /**\n * Optional: a human-readable note describing relevant fork customisations\n * in this file (e.g. \"This file contains the keypoollive provider registration\").\n * When provided, the model uses it to decide which parts must not be overwritten.\n */\n customizationNote: z\n .string()\n .optional()\n .describe(\"Optional description of fork customisations in this file to help preserve them\"),\n }),\n execute: async ({ filePath, baseContent, ourContent, theirContent, commitMessage, customizationNote }) => {\n /**\n * System prompt focuses the sub-agent exclusively on conflict resolution.\n * The model must output only a single JSON object.\n */\n const systemPrompt = [\n \"You are an expert software engineer specialising in Git merge conflict resolution.\",\n \"Your sole task is to produce a clean merged version of a conflicted file.\",\n \"\",\n \"Rules:\",\n \"- Output ONLY a valid JSON object. No prose, no explanations outside the JSON.\",\n '- The JSON must have exactly three fields: \"resolvedContent\", \"confidence\", \"reasoning\".',\n '- \"resolvedContent\": the complete resolved file content as a string. MUST contain zero conflict markers (<<<<<<<, =======, >>>>>>>).',\n '- \"confidence\": one of \"high\", \"medium\", or \"low\".',\n ' - \"high\": you are certain the resolution is correct and preserves all intent.',\n ' - \"medium\": the resolution is plausible but you had to make a judgment call.',\n ' - \"low\": the resolution is uncertain; a human should review it.',\n '- \"reasoning\": a single sentence explaining the key decision you made.',\n \"\",\n \"Resolution strategy:\",\n \"1. Understand what the UPSTREAM change was trying to achieve (read the commit message).\",\n \"2. Understand what the FORK version preserves (customisations, local patches).\",\n \"3. Integrate both intents: keep fork customisations AND apply the upstream change where safe.\",\n \"4. When in doubt, prefer preserving fork customisations and mark confidence as 'low'.\",\n ].join(\"\\n\")\n\n const customizationSection = customizationNote\n ? `\\nFork customisation context:\\n${customizationNote}\\n`\n : \"\"\n\n const userPrompt =\n `Resolve the merge conflict in file: ${filePath}\\n` +\n `Upstream commit message: ${commitMessage}\\n` +\n customizationSection +\n `\\n--- BASE (common ancestor) ---\\n${baseContent || \"(file did not exist at merge base)\"}\\n` +\n `\\n--- OURS (fork version) ---\\n${ourContent}\\n` +\n `\\n--- THEIRS (upstream version) ---\\n${theirContent}\\n` +\n `\\nOutput the JSON object now.`\n\n // Try specialist model first; fall back to powerful on any failure.\n const modelsToTry = [\n { modelId: config.models.specialist, label: \"specialist\" },\n { modelId: config.models.powerful, label: \"powerful\" },\n ]\n\n let lastError: string | null = null\n for (const { modelId, label } of modelsToTry) {\n try {\n const subAgent = makeSubAgent(modelId, systemPrompt)\n const t0 = Date.now()\n const result = await subAgent.run(userPrompt)\n const durationMs = Date.now() - t0\n logPrompt(logPath, \"resolve_conflict_with_ai\", modelId, userPrompt, result.outputText ?? \"\", durationMs, null, result.usage)\n const output = extractJson<{\n resolvedContent: string\n confidence: \"high\" | \"medium\" | \"low\"\n reasoning: string\n }>(result.outputText ?? \"\")\n\n return {\n resolvedContent: output.resolvedContent,\n confidence: output.confidence,\n reasoning: output.reasoning,\n error: null,\n }\n } catch (err) {\n lastError = err instanceof Error ? err.message : String(err)\n process.stderr.write(\n `[resolve_conflict_with_ai] ${label} model (${modelId}) failed: ${lastError.slice(0, 120)} — ${\n label === \"specialist\" ? \"retrying with powerful model\" : \"giving up\"\n }\\n`,\n )\n logPrompt(logPath, \"resolve_conflict_with_ai\", modelId, userPrompt, \"\", 0, lastError)\n }\n }\n\n return {\n resolvedContent: \"\",\n confidence: \"low\" as const,\n reasoning: `AI resolution failed on all models: ${lastError}`,\n error: lastError,\n }\n },\n })\n\n // -------------------------------------------------------------------------\n // Tool 2: analyze_commit_for_backport\n // -------------------------------------------------------------------------\n\n /**\n * Tool: analyze_commit_for_backport\n *\n * Spawns a sub-agent to produce a semantic assessment of an upstream commit\n * and its implications for the fork.\n *\n * Unlike the deterministic `classify_commit_risk` tool, this tool reasons\n * about the *intent* of the commit: what architectural or behavioural change\n * it introduces, whether it touches concepts relevant to the fork's\n * customisations, and what the recommended backport action is.\n *\n * Uses `config.models.fast` (analytical, not on the critical path).\n */\n const analyzeCommitTool = defineTool({\n name: \"analyze_commit_for_backport\",\n description:\n \"Performs a semantic analysis of an upstream commit to understand its intent and \" +\n \"assess how complex it is to backport into the fork. \" +\n \"Returns a structured assessment with a human-readable summary, key changes, \" +\n \"complexity rating, semantic risk factors, and a backport recommendation. \" +\n \"Use this before cherry-picking a high-risk commit to better understand what it does.\",\n inputSchema: z.object({\n /**\n * Full commit SHA (40-character hex string).\n */\n sha: z.string().describe(\"Full commit SHA\"),\n\n /**\n * The commit's subject + body as returned by `git log --format=%B`.\n */\n commitMessage: z.string().describe(\"Full commit message (subject + body)\"),\n\n /**\n * Full unified diff of the commit as returned by `git show --format=` or\n * `git diff <parent> <sha>`.\n */\n diff: z.string().describe(\"Full unified diff of the commit\"),\n\n /**\n * List of file paths changed by this commit (relative to repo root).\n */\n changedFiles: z.array(z.string()).describe(\"List of file paths changed by this commit\"),\n }),\n execute: async ({ sha, commitMessage, diff, changedFiles }) => {\n const systemPrompt = [\n \"You are an expert software engineer specialising in Git history analysis.\",\n \"Your task is to analyse a single upstream commit and assess how complex it is to\",\n \"backport it into a heavily customised fork.\",\n \"\",\n \"Output ONLY a valid JSON object with exactly these fields:\",\n ' \"summary\": string — 2-3 sentence description of what this commit does.',\n ' \"keyChanges\": string[] — bullet-point list of the most important code changes.',\n ' \"backportComplexity\": \"trivial\" | \"moderate\" | \"complex\"',\n ' - \"trivial\": small, isolated change with no side effects.',\n ' - \"moderate\": meaningful change but scope is clear and contained.',\n ' - \"complex\": refactor, API change, or broad change that may interact with customisations.',\n ' \"semanticRiskFactors\": string[] — list of reasons why this commit could break a fork',\n ' (e.g. \"renames exported interface\", \"changes provider registration pattern\").',\n ' Empty array if no risks detected.',\n ' \"recommendation\": \"apply\" | \"apply-with-care\" | \"review-required\" | \"skip\"',\n ' - \"apply\": safe to cherry-pick automatically.',\n ' - \"apply-with-care\": cherry-pick but verify validation passes.',\n ' - \"review-required\": human should review before merging.',\n ' - \"skip\": commit should not be backported.',\n ].join(\"\\n\")\n\n const userPrompt =\n `Commit SHA: ${sha}\\n` +\n `Commit message:\\n${commitMessage}\\n\\n` +\n `Changed files (${changedFiles.length}):\\n${changedFiles.join(\"\\n\")}\\n\\n` +\n `Diff:\\n${diff}\\n\\n` +\n `Output the JSON object now.`\n\n try {\n const subAgent = makeSubAgent(config.models.fast, systemPrompt)\n const t0 = Date.now()\n const result = await subAgent.run(userPrompt)\n const durationMs = Date.now() - t0\n logPrompt(logPath, \"analyze_commit_for_backport\", config.models.fast, userPrompt, result.outputText ?? \"\", durationMs, null, result.usage)\n const output = extractJson<{\n summary: string\n keyChanges: string[]\n backportComplexity: \"trivial\" | \"moderate\" | \"complex\"\n semanticRiskFactors: string[]\n recommendation: \"apply\" | \"apply-with-care\" | \"review-required\" | \"skip\"\n }>(result.outputText ?? \"\")\n\n return {\n summary: output.summary,\n keyChanges: output.keyChanges,\n backportComplexity: output.backportComplexity,\n semanticRiskFactors: output.semanticRiskFactors,\n recommendation: output.recommendation,\n error: null,\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logPrompt(logPath, \"analyze_commit_for_backport\", config.models.fast, userPrompt, \"\", 0, message)\n return {\n summary: `Analysis failed: ${message}`,\n keyChanges: [],\n backportComplexity: \"complex\" as const,\n semanticRiskFactors: [`AI analysis unavailable: ${message}`],\n recommendation: \"review-required\" as const,\n error: message,\n }\n }\n },\n })\n\n // -------------------------------------------------------------------------\n // Tool 3: check_customization_compatibility\n // -------------------------------------------------------------------------\n\n /**\n * Tool: check_customization_compatibility\n *\n * Spawns a sub-agent to reason about whether an upstream diff is semantically\n * compatible with the fork's declared customisations.\n *\n * The deterministic `classify_commit_risk` tool matches file globs to flag\n * risky commits. This tool goes further: it reads the *descriptions* of\n * customisations and asks the model whether the upstream change could break\n * the described behaviour, even if the changed files don't match the glob.\n *\n * Uses `config.models.fast` (fast reasoning, results used to augment the\n * main agent's risk assessment rather than as a hard gate).\n */\n const checkCompatibilityTool = defineTool({\n name: \"check_customization_compatibility\",\n description:\n \"Checks whether an upstream diff is semantically compatible with the fork's customisations. \" +\n \"Goes beyond file-path glob matching: the AI reads the customisation descriptions and \" +\n \"reasons about whether the upstream change could break declared fork-specific behaviour. \" +\n \"Returns a compatibility verdict, a list of affected customisations, semantic conflicts, \" +\n \"warnings, and a recommendation. \" +\n \"Use this when classify_commit_risk returns 'medium' or 'high' and you want deeper insight.\",\n inputSchema: z.object({\n /**\n * Full unified diff of the upstream commit being evaluated.\n */\n diff: z.string().describe(\"Full unified diff of the upstream commit\"),\n\n /**\n * List of customisation entries loaded from `customizations.yaml`.\n * Each entry has a glob pattern (identifying which files are customised)\n * and a human-readable description of what the customisation does.\n */\n customizations: z\n .array(\n z.object({\n /**\n * Glob pattern (e.g. `src/core/api/providers/**`) identifying files\n * that belong to this customisation zone.\n */\n pattern: z.string().describe(\"Glob pattern for customised file paths\"),\n /**\n * Human-readable description of what this customisation does and why\n * it must be preserved.\n */\n description: z.string().describe(\"Description of the fork customisation\"),\n }),\n )\n .describe(\"Customisation entries from customizations.yaml\"),\n }),\n execute: async ({ diff, customizations }) => {\n /**\n * If there are no customisations defined, there is nothing to check.\n * Return a trivially compatible result without calling the LLM.\n */\n if (customizations.length === 0) {\n return {\n compatible: true,\n affectedCustomizations: [],\n semanticConflicts: [],\n warnings: [],\n recommendation: \"No customisations defined; upstream change is safe to apply.\",\n error: null,\n }\n }\n\n const customizationList = customizations\n .map((c, i) => ` ${i + 1}. Pattern: ${c.pattern}\\n Description: ${c.description}`)\n .join(\"\\n\")\n\n const systemPrompt = [\n \"You are an expert software engineer specialising in fork maintenance and semantic conflict detection.\",\n \"Your task is to assess whether an upstream Git diff could break the customised behaviour\",\n \"of a fork, given a list of declared customisations.\",\n \"\",\n \"Output ONLY a valid JSON object with exactly these fields:\",\n ' \"compatible\": boolean — true if the upstream change is unlikely to break any customisation.',\n ' \"affectedCustomizations\": string[] — names/patterns of customisations potentially affected.',\n ' \"semanticConflicts\": string[] — specific ways the upstream change could break fork behaviour.',\n ' Each entry is a concrete description (e.g. \"renames ApiProvider enum',\n ' value used by keypoollive provider registration\").',\n ' Empty array if no conflicts detected.',\n ' \"warnings\": string[] — non-blocking concerns worth noting (e.g. \"touches shared types',\n ' used by customised components\").',\n ' Empty array if none.',\n ' \"recommendation\": string — one sentence advising the agent what to do.',\n \"\",\n \"Important: focus on SEMANTIC compatibility, not textual conflicts.\",\n \"A change can be textually clean but still break the fork (e.g. renaming an interface\",\n \"that the fork's custom provider implements).\",\n ].join(\"\\n\")\n\n const userPrompt =\n `Declared fork customisations:\\n${customizationList}\\n\\n` +\n `Upstream diff to evaluate:\\n${diff}\\n\\n` +\n `Output the JSON object now.`\n\n try {\n const subAgent = makeSubAgent(config.models.fast, systemPrompt)\n const t0 = Date.now()\n const result = await subAgent.run(userPrompt)\n const durationMs = Date.now() - t0\n logPrompt(logPath, \"check_customization_compatibility\", config.models.fast, userPrompt, result.outputText ?? \"\", durationMs, null, result.usage)\n const output = extractJson<{\n compatible: boolean\n affectedCustomizations: string[]\n semanticConflicts: string[]\n warnings: string[]\n recommendation: string\n }>(result.outputText ?? \"\")\n\n return {\n compatible: output.compatible,\n affectedCustomizations: output.affectedCustomizations,\n semanticConflicts: output.semanticConflicts,\n warnings: output.warnings,\n recommendation: output.recommendation,\n error: null,\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logPrompt(logPath, \"check_customization_compatibility\", config.models.fast, userPrompt, \"\", 0, message)\n return {\n compatible: false,\n affectedCustomizations: [],\n semanticConflicts: [],\n warnings: [`AI compatibility check failed: ${message}`],\n recommendation: \"Treat as potentially incompatible; request human review.\",\n error: message,\n }\n }\n },\n })\n\n // Return all three tools in a single array for spreading into the main Agent.\n return [resolveConflictTool, analyzeCommitTool, checkCompatibilityTool]\n}\n","/**\n * @file main.ts\n *\n * Entry point for the Backport Agent CLI.\n *\n * **Initialization sequence:**\n * 1. Parse CLI arguments (`--verbose`, `--config`, `--backport-customizations`,\n * `--keypool-vault-url`, `--keypool-live-secret`, `--keypool-state-file`, `--dry-run`).\n * 2. Validate required environment variables (`KEYPOOL_VAULT_URL` or `KEYPOOL_LIVE_SECRET`).\n * 3. Load and validate `config.json` via `loadConfig()`.\n * 4. Load and validate `customizations.yaml` via `loadCustomizations()`.\n * 5. Assemble all agent tools from the individual factory functions.\n * 6. Instantiate the `Agent` with the keypoollive provider, system prompt, and tools.\n * 7. Subscribe to runtime events to stream assistant output to stdout.\n * 8. Call `agent.run(task)` with the sync task description.\n * 9. Print the final report (or exit with code 1 on any fatal error).\n *\n * **Provider:**\n * `keypoollive` with `apiKey: \"auto\"` uses the `KEYPOOL_VAULT_URL` environment\n * variable to resolve API keys at runtime via an encrypted vault, enabling\n * automatic key rotation without storing secrets in the codebase.\n */\n/// <reference types=\"node\" />\n// ---------------------------------------------------------------------------\n// CLI argument parsing — runs before .env loading so flags can override env.\n// ---------------------------------------------------------------------------\n{\n const argv = process.argv.slice(2)\n function getArgValue(name: string): string | undefined {\n const idx = argv.indexOf(name)\n return idx !== -1 && idx + 1 < argv.length ? argv[idx + 1] : undefined\n }\n function hasFlag(name: string): boolean {\n return argv.includes(name)\n }\n\n if (hasFlag(\"--verbose\")) process.env.VERBOSE = \"true\"\n if (hasFlag(\"--dry-run\")) process.env.DRY_RUN = \"true\"\n const cliConfig = getArgValue(\"--config\")\n if (cliConfig) process.env._CLI_CONFIG_PATH = cliConfig\n const cliCustomizations = getArgValue(\"--backport-customizations\")\n if (cliCustomizations) process.env.BACKPORT_CUSTOMIZATIONS = cliCustomizations\n const cliVaultUrl = getArgValue(\"--keypool-vault-url\")\n if (cliVaultUrl) process.env.KEYPOOL_VAULT_URL = cliVaultUrl\n const cliLiveSecret = getArgValue(\"--keypool-live-secret\")\n if (cliLiveSecret) process.env.KEYPOOL_LIVE_SECRET = cliLiveSecret\n const cliStateFile = getArgValue(\"--keypool-state-file\")\n if (cliStateFile) process.env.KEYPOOL_STATE_FILE = cliStateFile\n}\n// Load .env file if present — allows setting KEYPOOL_VAULT_URL, KEYPOOL_LIVE_SECRET,\n// BACKPORT_CUSTOMIZATIONS, etc. without modifying the shell environment.\n// Uses Node.js 20.6+ built-in --env-file support via the `dotenv` fallback.\nimport { existsSync } from \"node:fs\"\nimport { resolve as resolvePath } from \"node:path\"\n{\n const envPath = resolvePath(process.cwd(), \".env\")\n if (existsSync(envPath)) {\n const { config } = await import(\"dotenv\")\n config({ path: envPath })\n }\n}\nimport { Agent, createBuiltinTools, createUserInstructionConfigService } from \"@sctg/cline-sdk\"\nimport type { UserInstructionConfigRecord } from \"@sctg/cline-sdk\"\nimport { loadConfig } from \"./config/loader.js\"\nimport { loadCustomizations } from \"./customizations/loader.js\"\nimport { makeGitTools } from \"./git/git-tools.js\"\nimport { applyGitAuth, ensureWorkingDir } from \"./git/git-init.js\"\nimport { makeRiskTool } from \"./risk/risk-tools.js\"\nimport { makeValidationTool } from \"./validation/validation-tools.js\"\nimport { makeGitHubTools } from \"./github/github-tools.js\"\nimport { makeReportTool } from \"./reports/report-tools.js\"\nimport { makeAiTools } from \"./ai/ai-tools.js\"\n\n// ---------------------------------------------------------------------------\n// System prompt — defines the agent's responsibilities and constraints.\n// This is a multi-line template string embedded directly in main.ts so that\n// the full agent workflow is visible in a single place for auditability.\n// ---------------------------------------------------------------------------\nconst SYSTEM_PROMPT = `You are the Backport Agent, a specialist in safely synchronizing a customized Git fork with its upstream repository.\n\n## Your mission\nIntegrate upstream commits into the fork branch while preserving all fork-specific customizations.\nProduce a draft pull request with a clear report. Never push directly to the main branch.\n\n## Core workflow (follow this exactly)\n\n1. Call fetch_remotes to ensure refs are up to date.\n2. Call list_candidate_commits to get pending upstream commits (already filtered, newest-last).\n - Record all returned SHAs immediately. You are accountable for every single one.\n3. For each candidate commit (process ALL of them — no silent skips):\n a. Call get_commit_details to inspect changed files and diff.\n b. Call classify_commit_risk to determine risk level deterministically.\n c. Risk-based decision:\n - LOW risk: proceed directly to step 5 (cherry-pick). No AI analysis needed.\n - MEDIUM risk: call analyze_commit_for_backport for context, then proceed to cherry-pick.\n - HIGH risk (touches a customization zone):\n * MANDATORY: Call check_customization_compatibility — pass the diff and all affected customization IDs.\n * MANDATORY: Call analyze_commit_for_backport — pass sha, message, diff, and changed files.\n * Read both responses carefully:\n - If both tools confirm the change is SAFE or ORTHOGONAL to the customization (e.g., it modifies a\n different provider, unrelated docs section, or infrastructure that doesn't overlap with fork code):\n → proceed to cherry-pick (step 5). Do NOT block on risk level alone.\n - If the tools identify a genuine semantic conflict (same code paths, incompatible invariants):\n → add to blockedCommits with a precise reason from the AI analysis.\n - If uncertain: still attempt the cherry-pick; conflicts will surface in step 5c.\n d. Commits with alreadyApplied: true → record as \"skipped\" in commitResults.\n4. Create the sync branch via create_sync_branch (once, before first cherry-pick).\n5. For each non-skipped commit (process lowest risk first):\n a. Call cherry_pick_commit.\n b. If success: record as \"applied\" in commitResults and proceed to next.\n c. If conflicts: for each conflicted file, call get_conflict_context, then attempt resolution.\n - Check the \\`forcedStrategy\\` field returned by get_conflict_context:\n * \\`forcedStrategy: \"ours\"\\` → use \\`forkVersion\\` directly as resolvedContent; call apply_resolved_file immediately. No AI call needed.\n * \\`forcedStrategy: \"theirs\"\\` → use \\`upstreamVersion\\` directly as resolvedContent; call apply_resolved_file immediately. No AI call needed.\n * \\`forcedStrategy: null\\` → proceed with AI resolution below.\n - (When forcedStrategy is null) Call resolve_conflict_with_ai with the base/ours/theirs content to get an AI-proposed resolution.\n - If confidence is \"high\" or \"medium\": verify no conflict markers remain, then call apply_resolved_file, then continue_cherry_pick.\n - If confidence is \"low\" or the tool returned an error: call abort_cherry_pick, mark commit as conflict-blocked.\n6. Call run_validation with the highest risk level encountered in this run.\n7. If validation fails: note it in the report, mark relevant commits as validation-failed.\n8. Call push_sync_branch (unless dry-run).\n9. Call find_existing_sync_pr to check for an existing PR.\n10. Call generate_report with the full summary of all decisions.\n11. Call create_sync_pr with the report as body (unless an existing PR was found and up to date).\n\n## Accountability (enforced — never skip)\n- You received a finite list of SHAs from list_candidate_commits.\n- EVERY SHA must appear in generate_report: either in commitResults (as applied/skipped/conflict-blocked/validation-failed) OR in blockedCommits.\n- No commit may be silently dropped. If you are unsure what to do with a commit, add it to blockedCommits with reason \"deferred: needs human triage\".\n- blockedCommits entries MUST include a specific human-readable reason (not just the SHA).\n- Pass allCandidateShas to generate_report — it cross-checks accountability automatically.\n\n## Hard constraints (never violate)\n- NEVER block a commit solely because classify_commit_risk returns \"high\" — always run the mandatory AI tools first.\n- NEVER apply a resolved file with conflict markers (<<<, ===, >>>) still present.\n- NEVER call continue_cherry_pick before all conflicted files are staged.\n- NEVER fabricate file content — only use content from get_conflict_context.\n- NEVER run commands that are not available as tools.\n- NEVER skip generate_report — it ends the run and produces the output.\n- If KEYPOOL_VAULT_URL is not set and apiKey is \"auto\", the run will fail before reaching this point.\n`\n\n// ---------------------------------------------------------------------------\n// Entry point — async main() is wrapped in .catch() for clean error exit.\n// ---------------------------------------------------------------------------\n\n/**\n * Main async entry point.\n *\n * Orchestrates the full agent lifecycle from environment validation through\n * report output. On any unhandled error the process exits with code 1.\n *\n * @throws On missing environment variables, invalid config, or agent failure.\n */\nasync function main() {\n // --- Environment validation ---\n // Fail fast if the provider cannot authenticate: avoids a confusing runtime\n // error deep inside the agent run.\n if (!process.env.KEYPOOL_VAULT_URL && !process.env.KEYPOOL_LIVE_SECRET) {\n console.error(\n \"Error: KEYPOOL_VAULT_URL environment variable is required for the keypoollive provider.\\n\" +\n \"Set it to your encrypted vault URL (e.g. https://raw.githubusercontent.com/.../ai.json.XXXX.enc)\\n\" +\n \"along with KEYPOOL_LIVE_SECRET as the decryption key.\",\n )\n process.exit(1)\n }\n\n // --- Config & customization loading ---\n // Both loaders throw descriptive errors if the files are missing or invalid.\n const config = loadConfig(process.env._CLI_CONFIG_PATH)\n // loadCustomizations supports: string path, URL, or inline object from config.\n const customizations = await loadCustomizations(\n config.customizations ?? process.env.BACKPORT_CUSTOMIZATIONS,\n )\n\n // --- Authentication + working directory setup ---\n // applyGitAuth sets process-level env vars (GIT_SSH_COMMAND or GIT_CONFIG_*)\n // before any git call is made, so all subsequent operations use the right creds.\n // ensureWorkingDir clones the fork repo if it doesn't exist, or fetches all\n // remotes if it does, bringing the checkout up to date before the agent starts.\n applyGitAuth(config)\n ensureWorkingDir(config)\n\n const userInstructionService = createUserInstructionConfigService({\n skills: { workspacePath: config.workingDir },\n })\n\n await userInstructionService.start()\n\n // --- Tool assembly ---\n // Each factory returns one or more AgentTool instances bound to the config.\n const gitTools = makeGitTools(config) // 10 tools for git operations\n const riskTool = makeRiskTool(config, customizations) // 1 tool for risk classification\n const validationTool = makeValidationTool(config) // 1 tool for validation suite\n const githubTools = makeGitHubTools(config) // 3 tools for GitHub PR management\n // Prompt log file for this run — every sub-agent LLM call is appended here.\n const promptLogPath = resolvePath(`run-${Date.now()}.prompts.jsonl`)\n process.stderr.write(`[PromptLogger] Writing sub-agent logs to: ${promptLogPath}\\n`)\n\n const reportTool = makeReportTool(config, promptLogPath) // 1 terminal tool (completesRun: true)\n const aiTools = makeAiTools(config, promptLogPath) // 3 AI-powered analysis tools\n\n // --- SDK built-in tools ---\n // Add Cline integrated tools so the agent can also perform generic workspace\n // operations through the standard runtime surface.\n const builtinTools = createBuiltinTools({\n cwd: config.workingDir,\n enableReadFiles: true,\n enableSearch: true,\n enableBash: true,\n enableWebFetch: true,\n enableApplyPatch: true,\n enableEditor: true,\n enableSkills: true,\n enableAskQuestion: true,\n enableSubmitAndExit: true,\n executors: {\n // Resolve skills from the workspace through the SDK's user-instruction service.\n skills: async (skill: string, args: string | undefined) => {\n const configuredSkills = userInstructionService.listRecords(\"skill\")\n const match = configuredSkills.find(\n (record: UserInstructionConfigRecord) => record.id === skill || record.item.name === skill || record.filePath === skill,\n )\n\n if (!match || match.item.disabled) {\n const availableSkills = configuredSkills\n .filter((record: UserInstructionConfigRecord) => !record.item.disabled)\n .map((record: UserInstructionConfigRecord) => record.item.name)\n\n return availableSkills.length > 0\n ? `Skill \"${skill}\" is not available. Known skills: ${availableSkills.join(\", \")}`\n : `No configured skills are available in this backport-agent runtime.`\n }\n\n const parts = [\n `Skill: ${match.item.name}`,\n match.item.description ? `Description: ${match.item.description}` : null,\n args ? `Arguments: ${args}` : null,\n \"Instructions:\",\n match.item.instructions,\n ].filter(Boolean)\n\n return parts.join(\"\\n\")\n },\n // Headless CI mode: ask_question is surfaced but should not block runs.\n askQuestion: async (question: string, options: string[]) => {\n const normalizedOptions = options.length > 0 ? options.join(\" | \") : \"(no options)\"\n return `Question recorded (headless mode): ${question} [${normalizedOptions}]`\n },\n // Keep submit_and_exit functional for compatibility with integrated flows.\n submit: async (summary: string, verified: boolean) =>\n `submit_and_exit acknowledged (verified=${verified ? \"true\" : \"false\"}): ${summary}`,\n },\n })\n\n // Flatten all tools into a single array for the Agent constructor.\n const allTools = [...builtinTools, ...gitTools, riskTool, validationTool, ...githubTools, reportTool, ...aiTools]\n\n // --- Agent instantiation ---\n // Using the keypoollive provider with \"auto\" apiKey — the SDK resolves the\n // actual API key at runtime via KEYPOOL_VAULT_URL.\n // config.models.fast selects the model configured for speed (see config.json).\n const agent = new Agent({\n providerId: \"keypoollive\",\n modelId: config.models.fast,\n apiKey: \"auto\", // SDK resolves via KEYPOOL_VAULT_URL at invocation time\n systemPrompt: SYSTEM_PROMPT,\n tools: allTools,\n maxIterations: config.sync.maxIterations,\n // Prevent the run from ending until generate_report (completesRun: true) is called.\n completionPolicy: { requireCompletionTool: true },\n })\n\n // --- Event subscription ---\n // Stream assistant text deltas to stdout so the operator can watch progress.\n // Tool-level progress (iterations, tool calls) is gated behind VERBOSE=true to\n // keep non-verbose runs clean. Set VERBOSE=true in .env or the shell to enable.\n const verbose = process.env.VERBOSE === \"true\"\n let lastEventWasText = false\n // Track the highest iteration seen so far across all attempts, so that when\n // the agent is restarted after a provider error the displayed counter is\n // continuous rather than resetting to 1.\n let iterationOffset = 0\n let lastSeenIteration = 0\n let currentAttempt = 1\n agent.subscribe((event: Parameters<Parameters<typeof agent.subscribe>[0]>[0]) => {\n const rawIter = (event as unknown as { iteration?: number }).iteration\n if (typeof rawIter === \"number\" && rawIter > lastSeenIteration) {\n lastSeenIteration = rawIter\n }\n const displayIter = typeof rawIter === \"number\" ? iterationOffset + rawIter : \"?\"\n if (event.type === \"assistant-text-delta\") {\n lastEventWasText = true\n process.stdout.write(event.text)\n } else if (event.type === \"tool-started\" && verbose) {\n // Ensure tool log starts on a fresh line after any streamed text.\n if (lastEventWasText) process.stderr.write(\"\\n\")\n lastEventWasText = false\n const inp = event.toolCall.input as Record<string, unknown>\n const preview =\n inp && typeof inp === \"object\" && Object.keys(inp).length > 0\n ? Object.keys(inp)\n .slice(0, 2)\n .map((k) => `${k}=${JSON.stringify(inp[k]).slice(0, 60)}`)\n .join(\", \")\n : \"(no input)\"\n process.stderr.write(`[→ iter ${displayIter}] ${event.toolCall.toolName}(${preview})\\n`)\n } else if (event.type === \"tool-finished\" && verbose) {\n lastEventWasText = false\n const result = event.toolCall as unknown as { toolName: string }\n process.stderr.write(`[← iter ${displayIter}] ${result.toolName ?? event.toolCall.toolName} done\\n`)\n } else if ((event.type === \"iteration_start\" || event.type === \"turn-started\") && verbose) {\n const retrySuffix = currentAttempt > 1 ? ` - Retry ${currentAttempt - 1}` : \"\"\n process.stderr.write(`\\n--- iteration ${displayIter}${retrySuffix} ---\\n`)\n }\n })\n\n // --- Task construction ---\n const dryRunNote = config.sync.dryRun ? \" [DRY RUN — no changes will be pushed]\" : \"\"\n const task =\n `Synchronize the fork \\`${config.fork.repo}@${config.fork.branch}\\` with upstream ` +\n `\\`${config.upstream.repo}@${config.upstream.branch}\\`.${dryRunNote}\\n\\n` +\n `Working directory: ${config.workingDir}\\n` +\n `Max commits per run: ${config.sync.maxCommitsPerRun}\\n` +\n `Batch size: ${config.sync.batchSize}`\n\n console.error(`\\n=== Backport Agent starting${dryRunNote} ===\\n`)\n\n // --- Provider retry helper ---\n // The SDK has no built-in retry for HTTP errors from the model provider.\n // Gemini (and other providers) occasionally return 503 / rate-limit responses;\n // this wrapper catches those and retries with exponential backoff.\n // Because agent state is persisted on disk (git), restarting the run is safe —\n // the agent will detect already-applied commits from the git log.\n const RETRIABLE_RE = /503|rate.?limit|too many requests|overloaded|service.?unavailable|high.?demand|try again later|temporarily unavailable|exceeded your current quota|quota.*exceeded|check your plan|billing details/i\n const BASE_DELAY_MS = 15_000\n const MAX_ATTEMPTS = 5\n\n async function runWithRetry(): Promise<Awaited<ReturnType<typeof agent.run>>> {\n for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {\n currentAttempt = attempt\n let result: Awaited<ReturnType<typeof agent.run>>\n try {\n result = await agent.run(task)\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n if (attempt < MAX_ATTEMPTS && RETRIABLE_RE.test(msg)) {\n const delay = BASE_DELAY_MS * attempt\n process.stderr.write(\n `[Retry] Provider error on attempt ${attempt}/${MAX_ATTEMPTS}: ${msg.slice(0, 120)}\\n` +\n `[Retry] Waiting ${delay / 1000}s before retrying...\\n`,\n )\n iterationOffset += lastSeenIteration\n lastSeenIteration = 0\n await new Promise((r) => setTimeout(r, delay))\n continue\n }\n throw err\n }\n\n // The SDK can return without throwing when the model API errors silently\n // (e.g. invalid model name, 503 absorbed internally). Treat non-completed\n // status as a throw so the retry loop can handle retriable cases.\n if (result.status !== \"completed\") {\n const err = result.error ?? new Error(`Agent run ended with status \"${result.status}\" (model API error?)`)\n const msg = err.message\n if (attempt < MAX_ATTEMPTS && RETRIABLE_RE.test(msg)) {\n const delay = BASE_DELAY_MS * attempt\n process.stderr.write(\n `[Retry] Silent provider error (status=${result.status}) on attempt ${attempt}/${MAX_ATTEMPTS}: ${msg.slice(0, 120)}\\n` +\n `[Retry] Waiting ${delay / 1000}s before retrying...\\n`,\n )\n iterationOffset += lastSeenIteration\n lastSeenIteration = 0\n await new Promise((r) => setTimeout(r, delay))\n continue\n }\n throw err\n }\n\n return result\n }\n throw new Error(\"unreachable\")\n }\n\n // --- Run the agent ---\n // The agent loop runs until the `generate_report` tool is called\n // (`lifecycle: { completesRun: true }`) or an unrecoverable error occurs.\n try {\n const result = await runWithRetry()\n\n console.error(`\\n=== Run complete ===\\n`)\n if (result.outputText) {\n // The generate_report tool completes the run; outputText is the Markdown summary.\n console.log(result.outputText)\n } else {\n // With requireCompletionTool: true this should never happen on a clean run.\n throw new Error(\"Agent run completed but generate_report was never called (empty output). Check the prompt log for details.\")\n }\n } finally {\n userInstructionService.stop()\n }\n}\n\n// Wrap main() in a .catch() handler to ensure the process exits with code 1\n// on any unhandled error, rather than crashing with an unhandled rejection.\nmain()\n .then(() => process.exit(0))\n .catch((err) => {\n console.error(\"Fatal error:\", err instanceof Error ? err.message : String(err))\n process.exit(1)\n })\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sctg/backport-agent",
|
|
3
|
-
"version": "0.1.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Generic fork sync & preservation agent powered by @sctg/cline-sdk",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=22"
|
|
8
8
|
},
|
|
9
|
-
"repository": {
|
|
10
|
-
"type": "git",
|
|
11
|
-
"url": "https://github.com/sctg-development/backport-agent.git"
|
|
12
|
-
},
|
|
13
9
|
"bin": {
|
|
14
10
|
"backport-agent": "./dist/main.mjs"
|
|
15
11
|
},
|