@defend-tech/opencode-optima 0.1.74 → 0.1.76

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/index.js CHANGED
@@ -8628,6 +8628,8 @@ var activeClickUpWebhookListeners = /* @__PURE__ */ new Map();
8628
8628
  var activeClickUpWebhookLifecycleRegistry = /* @__PURE__ */ new Map();
8629
8629
  var activeClickUpTaskRoutes = /* @__PURE__ */ new Map();
8630
8630
  var GITHUB_WEBHOOK_EVENTS = ["pull_request", "pull_request_review", "pull_request_review_comment", "issue_comment"];
8631
+ var OPTIMA_GITHUB_COMMITTER_NAME = "Optima Product Manager";
8632
+ var OPTIMA_GITHUB_COMMITTER_EMAIL = "optima-product-manager[bot]@users.noreply.github.com";
8631
8633
  function isRootDirectory(candidate) {
8632
8634
  const resolved = path6.resolve(candidate);
8633
8635
  return resolved === path6.parse(resolved).root;
@@ -9288,6 +9290,40 @@ function addClickUpWorktreeForBranch({ baseWorktree, branch, worktreePath, start
9288
9290
  runGitFn(baseWorktree, ["worktree", "add", "-b", branch, worktreePath, startPoint]);
9289
9291
  }
9290
9292
  }
9293
+ function normalizeOptimaGitIdentity(identity = {}) {
9294
+ return {
9295
+ name: String(identity.name || OPTIMA_GITHUB_COMMITTER_NAME).trim(),
9296
+ email: String(identity.email || OPTIMA_GITHUB_COMMITTER_EMAIL).trim(),
9297
+ sign: identity.sign === true,
9298
+ signingKey: String(identity.signingKey || identity.signing_key || "").trim(),
9299
+ signingKeyFile: String(identity.signingKeyFile || identity.signing_key_file || "").trim(),
9300
+ signingFormat: String(identity.signingFormat || identity.signing_format || "ssh").trim().toLowerCase()
9301
+ };
9302
+ }
9303
+ function configureOptimaWorktreeGitIdentity({ worktreePath, identity = {}, runGitFn = runGit } = {}) {
9304
+ if (!worktreePath || !fs5.existsSync(worktreePath)) return { configured: false, reason: "worktree_missing" };
9305
+ const normalized = normalizeOptimaGitIdentity(identity);
9306
+ if (!normalized.name || !normalized.email) return { configured: false, reason: "identity_missing" };
9307
+ try {
9308
+ runGitFn(worktreePath, ["config", "user.name", normalized.name]);
9309
+ runGitFn(worktreePath, ["config", "user.email", normalized.email]);
9310
+ } catch (error) {
9311
+ return { configured: false, reason: "git_config_failed", message: error.message };
9312
+ }
9313
+ let signingKey = normalized.signingKey;
9314
+ if (!signingKey && normalized.signingKeyFile) signingKey = expandHomePath(normalized.signingKeyFile);
9315
+ if (normalized.sign) {
9316
+ if (!signingKey) return { configured: false, reason: "signing_key_missing", name: normalized.name, email: normalized.email, sign: true };
9317
+ try {
9318
+ runGitFn(worktreePath, ["config", "commit.gpgsign", "true"]);
9319
+ runGitFn(worktreePath, ["config", "gpg.format", normalized.signingFormat || "ssh"]);
9320
+ runGitFn(worktreePath, ["config", "user.signingkey", signingKey]);
9321
+ } catch (error) {
9322
+ return { configured: false, reason: "git_signing_config_failed", message: error.message, name: normalized.name, email: normalized.email, sign: true };
9323
+ }
9324
+ }
9325
+ return { configured: true, name: normalized.name, email: normalized.email, sign: normalized.sign, signingFormat: normalized.signingFormat, signingKeyConfigured: Boolean(signingKey) };
9326
+ }
9291
9327
  function clickUpOpenChamberWorktreeName(branch = "") {
9292
9328
  return String(branch || "").trim().replace(/\//g, "-");
9293
9329
  }
@@ -9438,7 +9474,7 @@ async function registerOpenChamberClickUpWorktree({ openchamberBaseUrl, opencode
9438
9474
  const visibility = await syncOpenChamberWorktreeVisibility({ openchamberBaseUrl: openchamberBaseUrl || baseUrl, opencodeBaseUrl: opencodeBaseUrl || baseUrl, baseWorktree, worktreePath, branch, fetchImpl });
9439
9475
  return { branch, worktree: path6.resolve(worktreePath), reused: true, provider: "openchamber", openChamber: { source, visibility } };
9440
9476
  }
9441
- async function ensureClickUpTaskWorktreeOpenChamber({ baseWorktree = "", taskId, taskType = "Tarea", parentTaskId = "", subtaskId = "", existingMetadata = {}, runGitFn = runGit, openchamberBaseUrl = "", opencodeBaseUrl = "", baseUrl = "", fetchImpl = globalThis.fetch, log = null } = {}) {
9477
+ async function ensureClickUpTaskWorktreeOpenChamber({ baseWorktree = "", taskId, taskType = "Tarea", parentTaskId = "", subtaskId = "", existingMetadata = {}, runGitFn = runGit, openchamberBaseUrl = "", opencodeBaseUrl = "", baseUrl = "", fetchImpl = globalThis.fetch, log = null, gitIdentity = {} } = {}) {
9442
9478
  const effectiveOpenChamberBaseUrl = openchamberBaseUrl || baseUrl;
9443
9479
  const effectiveOpenCodeBaseUrl = opencodeBaseUrl || baseUrl;
9444
9480
  const effectiveParent = parentTaskId || taskId;
@@ -9449,27 +9485,33 @@ async function ensureClickUpTaskWorktreeOpenChamber({ baseWorktree = "", taskId,
9449
9485
  const existing = safeExistingClickUpWorktree({ metadata: existingMetadata, branch });
9450
9486
  if (existing) {
9451
9487
  const registered = await registerOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, branch, worktreePath: existing.worktree, fetchImpl, source: "metadata_reuse" });
9488
+ const identity2 = configureOptimaWorktreeGitIdentity({ worktreePath: registered.worktree, identity: gitIdentity, runGitFn });
9452
9489
  log?.({ type: "openchamber_worktree_registered", taskId, branch, worktree: registered.worktree, source: "metadata_reuse", visibility: registered.openChamber.visibility });
9453
- return { ...registered, parentBranch: parentBranch || void 0, prTarget };
9490
+ return { ...registered, parentBranch: parentBranch || void 0, prTarget, gitIdentity: identity2 };
9454
9491
  }
9455
9492
  const worktreePath = deriveClickUpWorktree({ baseWorktree, taskId, taskType, parentTaskId: effectiveParent, subtaskId });
9456
9493
  if (fs5.existsSync(worktreePath)) {
9457
9494
  const registered = await registerOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, branch, worktreePath, fetchImpl, source: "existing_directory" });
9495
+ const identity2 = configureOptimaWorktreeGitIdentity({ worktreePath: registered.worktree, identity: gitIdentity, runGitFn });
9458
9496
  log?.({ type: "openchamber_worktree_registered", taskId, branch, worktree: registered.worktree, source: "existing_directory", visibility: registered.openChamber.visibility });
9459
- return { ...registered, parentBranch: parentBranch || void 0, prTarget };
9497
+ return { ...registered, parentBranch: parentBranch || void 0, prTarget, gitIdentity: identity2 };
9460
9498
  }
9461
9499
  let parentBootstrap = null;
9462
9500
  if (isSubtask) {
9463
9501
  const parentWorktree = deriveClickUpWorktree({ baseWorktree, taskId: effectiveParent, taskType, parentTaskId: effectiveParent });
9464
9502
  if (fs5.existsSync(parentWorktree)) {
9465
9503
  const registeredParent = await registerOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, branch: parentBranch, worktreePath: parentWorktree, fetchImpl, source: "parent_existing_directory" });
9504
+ const parentIdentity = configureOptimaWorktreeGitIdentity({ worktreePath: registeredParent.worktree, identity: gitIdentity, runGitFn });
9466
9505
  parentBootstrap = { branch: parentBranch, worktree: registeredParent.worktree, reused: true, provider: "openchamber", visibility: registeredParent.openChamber.visibility };
9506
+ if (parentIdentity.configured) parentBootstrap.gitIdentity = parentIdentity;
9467
9507
  } else {
9468
9508
  const parentStartPoint = resolveClickUpDevStartPoint(baseWorktree, runGitFn);
9469
9509
  const parentBranchExists = clickUpGitRefExists(baseWorktree, parentBranch, runGitFn);
9470
9510
  const createdParent = await createOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, baseWorktree, branch: parentBranch, worktreePath: parentWorktree, startPoint: parentStartPoint, branchExists: parentBranchExists, fetchImpl });
9471
9511
  const parentVisibility = await syncOpenChamberWorktreeVisibility({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, worktreePath: createdParent.worktree, branch: parentBranch, fetchImpl });
9472
9512
  parentBootstrap = { branch: parentBranch, worktree: createdParent.worktree, startPoint: parentBranchExists ? parentBranch : parentStartPoint, reused: parentBranchExists, provider: "openchamber", visibility: parentVisibility };
9513
+ const parentIdentity = configureOptimaWorktreeGitIdentity({ worktreePath: parentBootstrap.worktree, identity: gitIdentity, runGitFn });
9514
+ if (parentIdentity.configured) parentBootstrap.gitIdentity = parentIdentity;
9473
9515
  log?.({ type: "openchamber_worktree_created", taskId: effectiveParent, branch: parentBranch, worktree: parentBootstrap.worktree, startPoint: parentBootstrap.startPoint, visibility: parentVisibility });
9474
9516
  }
9475
9517
  }
@@ -9477,8 +9519,9 @@ async function ensureClickUpTaskWorktreeOpenChamber({ baseWorktree = "", taskId,
9477
9519
  const branchExists = clickUpGitRefExists(baseWorktree, branch, runGitFn);
9478
9520
  const created = await createOpenChamberClickUpWorktree({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, baseWorktree, branch, worktreePath, startPoint, branchExists, fetchImpl });
9479
9521
  const visibility = await syncOpenChamberWorktreeVisibility({ openchamberBaseUrl: effectiveOpenChamberBaseUrl, opencodeBaseUrl: effectiveOpenCodeBaseUrl, baseWorktree, worktreePath: created.worktree, branch, fetchImpl });
9522
+ const identity = configureOptimaWorktreeGitIdentity({ worktreePath: created.worktree, identity: gitIdentity, runGitFn });
9480
9523
  log?.({ type: "openchamber_worktree_created", taskId, branch, worktree: created.worktree, startPoint: branchExists ? branch : startPoint, visibility });
9481
- return { branch, worktree: created.worktree, reused: false, startPoint, parentBranch: parentBranch || void 0, prTarget, parentBootstrap: parentBootstrap || void 0, provider: "openchamber", openChamber: { source: "created", visibility } };
9524
+ return { branch, worktree: created.worktree, reused: false, startPoint, parentBranch: parentBranch || void 0, prTarget, parentBootstrap: parentBootstrap || void 0, provider: "openchamber", openChamber: { source: "created", visibility }, gitIdentity: identity };
9482
9525
  }
9483
9526
  async function ensureClickUpTaskWorktreeForWebhook({ opencodeBaseUrl = "", opencodeBaseUrlConfigured = false, openchamberBaseUrl = "", openchamberBaseUrlConfigured = false, clickupClient = null, webhookWorktree = process.cwd(), fetchImpl = globalThis.fetch, ...options } = {}) {
9484
9527
  void opencodeBaseUrlConfigured;
@@ -9491,15 +9534,18 @@ async function ensureClickUpTaskWorktreeForWebhook({ opencodeBaseUrl = "", openc
9491
9534
  throw new Error(message);
9492
9535
  }
9493
9536
  }
9494
- function ensureClickUpTaskWorktree({ baseWorktree = "", taskId, taskType = "Tarea", parentTaskId = "", subtaskId = "", existingMetadata = {}, runGitFn = runGit, allowNonGitFallback = false } = {}) {
9537
+ function ensureClickUpTaskWorktree({ baseWorktree = "", taskId, taskType = "Tarea", parentTaskId = "", subtaskId = "", existingMetadata = {}, runGitFn = runGit, allowNonGitFallback = false, gitIdentity = {} } = {}) {
9495
9538
  const effectiveParent = parentTaskId || taskId;
9496
9539
  const isSubtask = isClickUpSubtaskRoute({ taskType, parentTaskId: effectiveParent, subtaskId, taskId });
9497
9540
  const parentBranch = isSubtask ? deriveClickUpBranchName({ taskType, parentTaskId: effectiveParent }) : "";
9498
9541
  const branch = deriveClickUpBranchName({ taskType, parentTaskId: effectiveParent, subtaskId, taskId });
9499
9542
  const existing = safeExistingClickUpWorktree({ metadata: existingMetadata, branch });
9500
- if (existing) return { ...existing, branch, parentBranch: parentBranch || void 0, prTarget: parentBranch || "dev" };
9543
+ if (existing) return { ...existing, branch, parentBranch: parentBranch || void 0, prTarget: parentBranch || "dev", gitIdentity: configureOptimaWorktreeGitIdentity({ worktreePath: existing.worktree, identity: gitIdentity, runGitFn }) };
9501
9544
  const worktreePath = deriveClickUpWorktree({ baseWorktree, taskId, taskType, parentTaskId: effectiveParent, subtaskId });
9502
- if (fs5.existsSync(worktreePath)) return { branch, worktree: path6.resolve(worktreePath), reused: true, parentBranch: parentBranch || void 0, prTarget: parentBranch || "dev" };
9545
+ if (fs5.existsSync(worktreePath)) {
9546
+ const resolvedWorktree2 = path6.resolve(worktreePath);
9547
+ return { branch, worktree: resolvedWorktree2, reused: true, parentBranch: parentBranch || void 0, prTarget: parentBranch || "dev", gitIdentity: configureOptimaWorktreeGitIdentity({ worktreePath: resolvedWorktree2, identity: gitIdentity, runGitFn }) };
9548
+ }
9503
9549
  if (allowNonGitFallback) {
9504
9550
  fs5.mkdirSync(worktreePath, { recursive: true });
9505
9551
  return { branch, worktree: path6.resolve(worktreePath), reused: false, fallback: "non_git", parentBranch: parentBranch || void 0, prTarget: parentBranch || "dev", startPoint: parentBranch || "dev" };
@@ -9512,13 +9558,18 @@ function ensureClickUpTaskWorktree({ baseWorktree = "", taskId, taskType = "Tare
9512
9558
  const parentBranchExists = clickUpGitRefExists(baseWorktree, parentBranch, runGitFn);
9513
9559
  addClickUpWorktreeForBranch({ baseWorktree, branch: parentBranch, worktreePath: parentWorktree, startPoint: parentStartPoint, runGitFn });
9514
9560
  parentBootstrap = { branch: parentBranch, worktree: path6.resolve(parentWorktree), startPoint: parentBranchExists ? parentBranch : parentStartPoint, reused: parentBranchExists };
9561
+ const parentIdentity = configureOptimaWorktreeGitIdentity({ worktreePath: parentBootstrap.worktree, identity: gitIdentity, runGitFn });
9562
+ if (parentIdentity.configured) parentBootstrap.gitIdentity = parentIdentity;
9515
9563
  } else {
9516
9564
  parentBootstrap = { branch: parentBranch, worktree: path6.resolve(parentWorktree), reused: true };
9565
+ const parentIdentity = configureOptimaWorktreeGitIdentity({ worktreePath: parentBootstrap.worktree, identity: gitIdentity, runGitFn });
9566
+ if (parentIdentity.configured) parentBootstrap.gitIdentity = parentIdentity;
9517
9567
  }
9518
9568
  }
9519
9569
  const startPoint = isSubtask ? parentBranch : resolveClickUpDevStartPoint(baseWorktree, runGitFn);
9520
9570
  addClickUpWorktreeForBranch({ baseWorktree, branch, worktreePath, startPoint, runGitFn });
9521
- return { branch, worktree: path6.resolve(worktreePath), reused: false, startPoint, parentBranch: parentBranch || void 0, prTarget: parentBranch || "dev", parentBootstrap: parentBootstrap || void 0 };
9571
+ const resolvedWorktree = path6.resolve(worktreePath);
9572
+ return { branch, worktree: resolvedWorktree, reused: false, startPoint, parentBranch: parentBranch || void 0, prTarget: parentBranch || "dev", parentBootstrap: parentBootstrap || void 0, gitIdentity: configureOptimaWorktreeGitIdentity({ worktreePath: resolvedWorktree, identity: gitIdentity, runGitFn }) };
9522
9573
  }
9523
9574
  function normalizeClickUpDefinitionDocParent(parent = {}) {
9524
9575
  const docId = String(parent.doc_id || parent.docId || CLICKUP_DEFINITION_DOC_PARENT.doc_id).trim();
@@ -9897,6 +9948,8 @@ function defaultGitHubWebhookPublicUrl(clickupPublicUrl = "") {
9897
9948
  function normalizeGitHubWebhookConfig(rawGithub = {}, clickupWebhook = {}) {
9898
9949
  const github = isPlainObject(rawGithub) ? rawGithub : {};
9899
9950
  const webhook = isPlainObject(github.webhook) ? github.webhook : {};
9951
+ const app = isPlainObject(github.app) ? github.app : {};
9952
+ const committer = isPlainObject(github.committer) ? github.committer : {};
9900
9953
  const enabled = github.enabled === true || webhook.enabled === true;
9901
9954
  const events = Array.isArray(webhook.events) && webhook.events.length > 0 ? [...new Set(webhook.events.map((event) => String(event || "").trim()).filter(Boolean))] : [...GITHUB_WEBHOOK_EVENTS];
9902
9955
  const publicUrl = String(webhook.public_url || webhook.publicUrl || github.public_url || github.publicUrl || defaultGitHubWebhookPublicUrl(clickupWebhook.publicUrl)).trim();
@@ -9910,6 +9963,21 @@ function normalizeGitHubWebhookConfig(rawGithub = {}, clickupWebhook = {}) {
9910
9963
  repository,
9911
9964
  owner: String(github.owner || ownerFromRepo || "").trim(),
9912
9965
  repo: String(github.repo_name || github.repoName || nameFromRepo || "").trim(),
9966
+ app: {
9967
+ enabled: github.app_enabled === true || github.appEnabled === true || app.enabled === true,
9968
+ appId: String(app.app_id || app.appId || github.app_id || github.appId || "").trim(),
9969
+ installationId: String(app.installation_id || app.installationId || github.installation_id || github.installationId || "").trim(),
9970
+ privateKey: String(app.private_key || app.privateKey || github.private_key || github.privateKey || "").trim(),
9971
+ privateKeyFile: String(app.private_key_file || app.privateKeyFile || github.private_key_file || github.privateKeyFile || "").trim()
9972
+ },
9973
+ committer: {
9974
+ name: String(committer.name || github.committer_name || github.committerName || OPTIMA_GITHUB_COMMITTER_NAME).trim(),
9975
+ email: String(committer.email || github.committer_email || github.committerEmail || OPTIMA_GITHUB_COMMITTER_EMAIL).trim(),
9976
+ sign: committer.sign === true || github.committer_sign === true || github.committerSign === true,
9977
+ signingKey: String(committer.signing_key || committer.signingKey || github.signing_key || github.signingKey || "").trim(),
9978
+ signingKeyFile: String(committer.signing_key_file || committer.signingKeyFile || github.signing_key_file || github.signingKeyFile || "").trim(),
9979
+ signingFormat: String(committer.signing_format || committer.signingFormat || github.signing_format || github.signingFormat || "ssh").trim().toLowerCase()
9980
+ },
9913
9981
  webhook: {
9914
9982
  enabled,
9915
9983
  publicUrl,
@@ -10004,6 +10072,11 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
10004
10072
  const missingGitHubEvents = GITHUB_WEBHOOK_EVENTS.filter((event) => !config.github.webhook.events.includes(event));
10005
10073
  if (missingGitHubEvents.length > 0) errors.push(`clickup.github.webhook.events missing: ${missingGitHubEvents.join(", ")}`);
10006
10074
  }
10075
+ if (config.github.app.enabled) {
10076
+ if (!config.github.app.appId) errors.push("clickup.github.app.app_id is required when GitHub App auth is enabled");
10077
+ if (!config.github.app.installationId) errors.push("clickup.github.app.installation_id is required when GitHub App auth is enabled");
10078
+ if (!config.github.app.privateKey && !config.github.app.privateKeyFile) errors.push("clickup.github.app.private_key_file is required when GitHub App auth is enabled");
10079
+ }
10007
10080
  if (config.opencode.baseUrl) {
10008
10081
  try {
10009
10082
  new URL(config.opencode.baseUrl);
@@ -10159,26 +10232,392 @@ function createClickUpApiClient(config, fetchImpl = globalThis.fetch) {
10159
10232
  }
10160
10233
  };
10161
10234
  }
10235
+ function base64UrlJson(value) {
10236
+ return Buffer.from(JSON.stringify(value)).toString("base64url");
10237
+ }
10238
+ function resolveGitHubAppPrivateKey(app = {}) {
10239
+ const direct = resolveSecretReference(app.privateKey || "");
10240
+ if (direct) return direct.replace(/\\n/g, "\n");
10241
+ const file = expandHomePath(app.privateKeyFile || "");
10242
+ if (!file) return "";
10243
+ try {
10244
+ return fs5.readFileSync(file, "utf8").trim().replace(/\\n/g, "\n");
10245
+ } catch {
10246
+ return "";
10247
+ }
10248
+ }
10249
+ function createGitHubAppJwt({ appId, privateKey, now = () => /* @__PURE__ */ new Date() } = {}) {
10250
+ const key = String(privateKey || "").trim();
10251
+ const issuer = String(appId || "").trim();
10252
+ if (!issuer || !key) throw new Error("GitHub App app_id and private key are required");
10253
+ const nowSeconds = Math.floor(now().getTime() / 1e3);
10254
+ const header = base64UrlJson({ alg: "RS256", typ: "JWT" });
10255
+ const payload = base64UrlJson({ iat: nowSeconds - 60, exp: nowSeconds + 540, iss: issuer });
10256
+ const unsigned = `${header}.${payload}`;
10257
+ const signature = crypto.createSign("RSA-SHA256").update(unsigned).end().sign(key, "base64url");
10258
+ return `${unsigned}.${signature}`;
10259
+ }
10260
+ function encodeGitHubPathSegment(value) {
10261
+ return encodeURIComponent(String(value || ""));
10262
+ }
10263
+ function encodeGitHubBranchRef(branch = "") {
10264
+ return String(branch || "").split("/").map(encodeGitHubPathSegment).join("/");
10265
+ }
10266
+ function parseNullSeparatedGitOutput(output = "") {
10267
+ return String(output || "").split("\0").map((item) => item.trim()).filter(Boolean);
10268
+ }
10269
+ function listWorktreeChangedPaths(worktree, runGitFn = runGit) {
10270
+ const tracked = parseNullSeparatedGitOutput(runGitFn(worktree, ["diff", "--name-only", "-z", "HEAD", "--"]));
10271
+ const untracked = parseNullSeparatedGitOutput(runGitFn(worktree, ["ls-files", "--others", "--exclude-standard", "-z", "--"]));
10272
+ return [.../* @__PURE__ */ new Set([...tracked, ...untracked])].filter((item) => item && !item.startsWith("../") && !path6.isAbsolute(item));
10273
+ }
10274
+ function currentGitBranch(worktree, runGitFn = runGit) {
10275
+ return String(runGitFn(worktree, ["rev-parse", "--abbrev-ref", "HEAD"]) || "").trim();
10276
+ }
10277
+ function treeEntryForWorktreePath(worktree, gitPath) {
10278
+ const absolutePath = path6.join(worktree, ...String(gitPath || "").split("/"));
10279
+ if (!fs5.existsSync(absolutePath)) return { path: gitPath, mode: "100644", type: "blob", sha: null, deleted: true };
10280
+ const stat = fs5.lstatSync(absolutePath);
10281
+ if (stat.isDirectory()) return null;
10282
+ if (stat.isSymbolicLink()) {
10283
+ return {
10284
+ path: gitPath,
10285
+ mode: "120000",
10286
+ type: "blob",
10287
+ content: fs5.readlinkSync(absolutePath),
10288
+ encoding: "utf-8"
10289
+ };
10290
+ }
10291
+ return {
10292
+ path: gitPath,
10293
+ mode: stat.mode & 73 ? "100755" : "100644",
10294
+ type: "blob",
10295
+ content: fs5.readFileSync(absolutePath).toString("base64"),
10296
+ encoding: "base64"
10297
+ };
10298
+ }
10299
+ function appendGitHubQuery(pathname, params = {}) {
10300
+ const query = new URLSearchParams();
10301
+ for (const [key, value] of Object.entries(params || {})) {
10302
+ if (value === void 0 || value === null || value === "") continue;
10303
+ query.set(key, String(value));
10304
+ }
10305
+ const suffix = query.toString();
10306
+ return suffix ? `${pathname}?${suffix}` : pathname;
10307
+ }
10308
+ function timestampMs(value = "") {
10309
+ const parsed = Date.parse(String(value || ""));
10310
+ return Number.isFinite(parsed) ? parsed : 0;
10311
+ }
10312
+ function sortNewestFirst(items = []) {
10313
+ return [...items].sort((a, b) => {
10314
+ const bTime = Math.max(timestampMs(b?.updated_at), timestampMs(b?.created_at));
10315
+ const aTime = Math.max(timestampMs(a?.updated_at), timestampMs(a?.created_at));
10316
+ return bTime - aTime;
10317
+ });
10318
+ }
10319
+ function matchesVercelStatusContext(context = "", expected = "") {
10320
+ const actual = String(context || "").trim();
10321
+ const wanted = String(expected || "").trim();
10322
+ if (wanted && actual === wanted) return true;
10323
+ const lower = actual.toLowerCase();
10324
+ if (!lower.includes("vercel")) return false;
10325
+ if (!wanted) return lower.includes("preproduction") || lower.includes("defend-preproduction");
10326
+ const wantedLower = wanted.toLowerCase();
10327
+ return wantedLower.split(/\s+|–|-/).filter((part) => part && part !== "vercel").every((part) => lower.includes(part));
10328
+ }
10329
+ function selectFunctionalDeploymentUrl(status = {}, deployment = {}) {
10330
+ for (const candidate of [
10331
+ status?.environment_url,
10332
+ deployment?.environment_url,
10333
+ status?.target_url,
10334
+ deployment?.target_url
10335
+ ]) {
10336
+ const value = String(candidate || "").trim();
10337
+ if (!value) continue;
10338
+ try {
10339
+ const url = new URL(value);
10340
+ if (url.protocol === "http:" || url.protocol === "https:") return value;
10341
+ } catch {
10342
+ }
10343
+ }
10344
+ return "";
10345
+ }
10346
+ async function checkFunctionalDeploymentUrl(url, fetchImpl = globalThis.fetch) {
10347
+ const target = String(url || "").trim();
10348
+ if (!target) return { ok: false, reason: "url_missing" };
10349
+ if (typeof fetchImpl !== "function") return { ok: false, reason: "fetch_unavailable", url: target };
10350
+ const signal = typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(8e3) : void 0;
10351
+ const probe = async (method) => {
10352
+ const response = await fetchImpl(target, { method, redirect: "follow", signal });
10353
+ return {
10354
+ ok: response.status >= 200 && response.status < 400,
10355
+ status: response.status,
10356
+ statusText: response.statusText || "",
10357
+ url: response.url || target,
10358
+ method
10359
+ };
10360
+ };
10361
+ try {
10362
+ const head = await probe("HEAD");
10363
+ if (head.ok) return head;
10364
+ const get = await probe("GET");
10365
+ return get.ok ? get : { ...get, reason: "http_not_ok" };
10366
+ } catch (error) {
10367
+ try {
10368
+ const get = await probe("GET");
10369
+ return get.ok ? get : { ...get, reason: "http_not_ok" };
10370
+ } catch (secondError) {
10371
+ return { ok: false, reason: "request_failed", url: target, error: secondError.message || error.message };
10372
+ }
10373
+ }
10374
+ }
10162
10375
  function createGitHubApiClient(config = {}, fetchImpl = globalThis.fetch) {
10163
- const token = resolveSecretReference(config?.apiToken);
10376
+ const staticToken = resolveSecretReference(config?.apiToken);
10377
+ const appConfig = isPlainObject(config?.app) ? config.app : {};
10378
+ const appId = resolveSecretReference(appConfig.appId || appConfig.app_id || "");
10379
+ const installationId = resolveSecretReference(appConfig.installationId || appConfig.installation_id || "");
10380
+ const appEnabled = appConfig.enabled === true || Boolean(appId && installationId && (appConfig.privateKey || appConfig.privateKeyFile));
10381
+ const privateKey = resolveGitHubAppPrivateKey(appConfig);
10164
10382
  const owner = String(config?.owner || "").trim();
10165
10383
  const repo = String(config?.repo || "").trim();
10166
- const request = async (pathname) => {
10384
+ let installationToken = null;
10385
+ let installationTokenExpiresAt = 0;
10386
+ const requestJson = async (url, { method = "GET", headers = {}, body = void 0 } = {}) => {
10167
10387
  if (typeof fetchImpl !== "function") throw new Error("fetch is unavailable; inject a GitHub client for live PR lookup");
10168
- if (!owner || !repo) throw new Error("GitHub repository owner/repo is not configured");
10169
- const response = await fetchImpl(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}${pathname}`, {
10388
+ const response = await fetchImpl(url, {
10389
+ method,
10170
10390
  headers: {
10171
10391
  Accept: "application/vnd.github+json",
10172
10392
  "X-GitHub-Api-Version": "2022-11-28",
10173
- ...token ? { Authorization: `Bearer ${token}` } : {}
10174
- }
10393
+ ...body === void 0 ? {} : { "Content-Type": "application/json" },
10394
+ ...headers
10395
+ },
10396
+ ...body === void 0 ? {} : { body: JSON.stringify(body) }
10175
10397
  });
10176
10398
  if (!response.ok) throw new Error(`GitHub API request failed: ${response.status}`);
10177
- return response.json();
10399
+ return response.status === 204 ? null : response.json();
10400
+ };
10401
+ const getAuthToken = async () => {
10402
+ if (!appEnabled) return staticToken || "";
10403
+ const nowMs = Date.now();
10404
+ if (installationToken && installationTokenExpiresAt - 6e4 > nowMs) return installationToken;
10405
+ const jwt = createGitHubAppJwt({ appId, privateKey });
10406
+ const installation = await requestJson(`https://api.github.com/app/installations/${encodeURIComponent(installationId)}/access_tokens`, {
10407
+ method: "POST",
10408
+ headers: { Authorization: `Bearer ${jwt}` }
10409
+ });
10410
+ installationToken = String(installation?.token || "").trim();
10411
+ installationTokenExpiresAt = Date.parse(installation?.expires_at || "") || nowMs + 3e6;
10412
+ if (!installationToken) throw new Error("GitHub App installation token response did not include a token");
10413
+ return installationToken;
10178
10414
  };
10415
+ const request = async (pathname, { method = "GET", body = void 0 } = {}) => {
10416
+ if (!owner || !repo) throw new Error("GitHub repository owner/repo is not configured");
10417
+ const token = await getAuthToken();
10418
+ return requestJson(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}${pathname}`, {
10419
+ method,
10420
+ body,
10421
+ headers: token ? { Authorization: `Bearer ${token}` } : {}
10422
+ });
10423
+ };
10424
+ const getRef = async (branch) => request(`/git/ref/heads/${encodeGitHubBranchRef(branch)}`);
10425
+ const getCommitObject = async (sha) => request(`/git/commits/${encodeURIComponent(sha)}`);
10426
+ const createBlob = async ({ content, encoding }) => request("/git/blobs", { method: "POST", body: { content, encoding } });
10427
+ const createTree = async ({ baseTree, tree }) => request("/git/trees", { method: "POST", body: { base_tree: baseTree, tree } });
10428
+ const createCommit = async ({ message, treeSha, parents }) => request("/git/commits", { method: "POST", body: { message, tree: treeSha, parents } });
10429
+ const updateRef = async ({ branch, sha, force = false }) => request(`/git/refs/heads/${encodeGitHubBranchRef(branch)}`, { method: "PATCH", body: { sha, force } });
10179
10430
  return {
10180
10431
  async getPullRequest(number) {
10181
10432
  return request(`/pulls/${encodeURIComponent(number)}`);
10433
+ },
10434
+ async createPullRequest({ title, head, base, body = "", draft = false, maintainerCanModify = true }) {
10435
+ return request("/pulls", {
10436
+ method: "POST",
10437
+ body: {
10438
+ title: String(title || ""),
10439
+ head: String(head || ""),
10440
+ base: String(base || ""),
10441
+ body: String(body || ""),
10442
+ draft: draft === true,
10443
+ maintainer_can_modify: maintainerCanModify !== false
10444
+ }
10445
+ });
10446
+ },
10447
+ async createIssueComment({ issueNumber, body }) {
10448
+ return request(`/issues/${encodeURIComponent(issueNumber)}/comments`, { method: "POST", body: { body: String(body || "") } });
10449
+ },
10450
+ async replyToReviewComment({ commentId, body }) {
10451
+ return request(`/pulls/comments/${encodeURIComponent(commentId)}/replies`, { method: "POST", body: { body: String(body || "") } });
10452
+ },
10453
+ async createPullRequestReview({ pullNumber, body, event = "COMMENT" }) {
10454
+ return request(`/pulls/${encodeURIComponent(pullNumber)}/reviews`, { method: "POST", body: { body: String(body || ""), event: String(event || "COMMENT").toUpperCase() } });
10455
+ },
10456
+ async mergePullRequest({ pullNumber, commitTitle = "", commitMessage = "", mergeMethod = "squash" }) {
10457
+ const body = {
10458
+ merge_method: String(mergeMethod || "squash")
10459
+ };
10460
+ if (commitTitle) body.commit_title = String(commitTitle);
10461
+ if (commitMessage) body.commit_message = String(commitMessage);
10462
+ return request(`/pulls/${encodeURIComponent(pullNumber)}/merge`, { method: "PUT", body });
10463
+ },
10464
+ async getCombinedStatus(ref) {
10465
+ return request(`/commits/${encodeURIComponent(String(ref || ""))}/status`);
10466
+ },
10467
+ async listDeployments({ sha = "", ref = "", environment = "", perPage = 30 } = {}) {
10468
+ return request(appendGitHubQuery("/deployments", {
10469
+ sha,
10470
+ ref,
10471
+ environment,
10472
+ per_page: perPage
10473
+ }));
10474
+ },
10475
+ async listDeploymentStatuses(deploymentId) {
10476
+ return request(`/deployments/${encodeURIComponent(deploymentId)}/statuses`);
10477
+ },
10478
+ async verifyVercelPullRequestDeployment({
10479
+ pullNumber,
10480
+ context = "Vercel \u2013 defend-preproduction",
10481
+ environment = "Preview \u2013 defend-preproduction",
10482
+ requireFunctionalUrl = true
10483
+ } = {}) {
10484
+ const pr = await this.getPullRequest(pullNumber);
10485
+ const headSha = String(pr?.head?.sha || "").trim();
10486
+ if (!headSha) return { ok: true, ready: false, reason: "pr_head_sha_missing", pull_request: { number: pullNumber } };
10487
+ const combined = await this.getCombinedStatus(headSha);
10488
+ const statuses = Array.isArray(combined?.statuses) ? combined.statuses : [];
10489
+ const matchingStatuses = statuses.filter((status) => matchesVercelStatusContext(status?.context, context));
10490
+ const selectedStatus = sortNewestFirst(matchingStatuses)[0] || null;
10491
+ if (!selectedStatus) {
10492
+ return {
10493
+ ok: true,
10494
+ ready: false,
10495
+ reason: "vercel_status_missing",
10496
+ pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
10497
+ required_context: context,
10498
+ combined_state: combined?.state || null
10499
+ };
10500
+ }
10501
+ if (String(selectedStatus.state || "").toLowerCase() !== "success") {
10502
+ return {
10503
+ ok: true,
10504
+ ready: false,
10505
+ reason: "vercel_status_not_success",
10506
+ pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
10507
+ required_context: context,
10508
+ status: selectedStatus,
10509
+ combined_state: combined?.state || null
10510
+ };
10511
+ }
10512
+ let deployments = await this.listDeployments({ sha: headSha, environment });
10513
+ if (!Array.isArray(deployments) || deployments.length === 0) deployments = await this.listDeployments({ sha: headSha });
10514
+ const selectedDeployment = sortNewestFirst(Array.isArray(deployments) ? deployments : []).find((deployment) => !environment || String(deployment?.environment || "") === environment) || sortNewestFirst(Array.isArray(deployments) ? deployments : [])[0] || null;
10515
+ if (!selectedDeployment?.id) {
10516
+ return {
10517
+ ok: true,
10518
+ ready: false,
10519
+ reason: "vercel_deployment_missing",
10520
+ pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
10521
+ required_environment: environment,
10522
+ status: selectedStatus
10523
+ };
10524
+ }
10525
+ const deploymentStatuses = await this.listDeploymentStatuses(selectedDeployment.id);
10526
+ const selectedDeploymentStatus = sortNewestFirst(Array.isArray(deploymentStatuses) ? deploymentStatuses : [])[0] || null;
10527
+ if (!selectedDeploymentStatus || String(selectedDeploymentStatus.state || "").toLowerCase() !== "success") {
10528
+ return {
10529
+ ok: true,
10530
+ ready: false,
10531
+ reason: "vercel_deployment_not_success",
10532
+ pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
10533
+ required_environment: environment,
10534
+ status: selectedStatus,
10535
+ deployment: selectedDeployment,
10536
+ deployment_status: selectedDeploymentStatus
10537
+ };
10538
+ }
10539
+ const url = selectFunctionalDeploymentUrl(selectedDeploymentStatus, selectedDeployment);
10540
+ const urlCheck = requireFunctionalUrl ? await checkFunctionalDeploymentUrl(url, fetchImpl) : { ok: Boolean(url), reason: url ? void 0 : "url_missing", url };
10541
+ if (requireFunctionalUrl && !urlCheck.ok) {
10542
+ return {
10543
+ ok: true,
10544
+ ready: false,
10545
+ reason: "vercel_url_not_functional",
10546
+ pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
10547
+ required_environment: environment,
10548
+ status: selectedStatus,
10549
+ deployment: selectedDeployment,
10550
+ deployment_status: selectedDeploymentStatus,
10551
+ url,
10552
+ url_check: urlCheck
10553
+ };
10554
+ }
10555
+ return {
10556
+ ok: true,
10557
+ ready: true,
10558
+ reason: "vercel_pr_deployment_ready",
10559
+ pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
10560
+ required_context: context,
10561
+ required_environment: environment,
10562
+ status: selectedStatus,
10563
+ deployment: selectedDeployment,
10564
+ deployment_status: selectedDeploymentStatus,
10565
+ url,
10566
+ url_check: urlCheck
10567
+ };
10568
+ },
10569
+ async commitWorktree({ worktree, branch = "", message = "", runGitFn = runGit, syncLocal = false } = {}) {
10570
+ const directory = path6.resolve(String(worktree || ""));
10571
+ if (!directory || !fs5.existsSync(directory)) throw new Error("worktree does not exist");
10572
+ const targetBranch = String(branch || currentGitBranch(directory, runGitFn)).trim();
10573
+ if (!targetBranch || targetBranch === "HEAD") throw new Error("target branch is required for GitHub API commit");
10574
+ const commitMessage = String(message || "").trim();
10575
+ if (!commitMessage) throw new Error("commit message is required");
10576
+ const changedPaths = listWorktreeChangedPaths(directory, runGitFn);
10577
+ if (changedPaths.length === 0) return { ok: true, action: "no_changes", branch: targetBranch, changedPaths: [] };
10578
+ const ref = await getRef(targetBranch);
10579
+ const headSha = ref?.object?.sha;
10580
+ if (!headSha) throw new Error(`GitHub ref for ${targetBranch} did not include a head sha`);
10581
+ const headCommit = await getCommitObject(headSha);
10582
+ const baseTree = headCommit?.tree?.sha;
10583
+ if (!baseTree) throw new Error(`GitHub commit ${headSha} did not include a tree sha`);
10584
+ const tree = [];
10585
+ for (const gitPath of changedPaths) {
10586
+ const entry = treeEntryForWorktreePath(directory, gitPath);
10587
+ if (!entry) continue;
10588
+ if (entry.deleted) {
10589
+ tree.push({ path: entry.path, mode: entry.mode, type: entry.type, sha: null });
10590
+ continue;
10591
+ }
10592
+ const blob = await createBlob({ content: entry.content, encoding: entry.encoding });
10593
+ if (!blob?.sha) throw new Error(`GitHub blob creation failed for ${gitPath}`);
10594
+ tree.push({ path: entry.path, mode: entry.mode, type: entry.type, sha: blob.sha });
10595
+ }
10596
+ if (tree.length === 0) return { ok: true, action: "no_changes", branch: targetBranch, changedPaths: [] };
10597
+ const nextTree = await createTree({ baseTree, tree });
10598
+ const nextCommit = await createCommit({ message: commitMessage, treeSha: nextTree.sha, parents: [headSha] });
10599
+ if (!nextCommit?.sha) throw new Error("GitHub commit creation did not return a sha");
10600
+ const updatedRef = await updateRef({ branch: targetBranch, sha: nextCommit.sha, force: false });
10601
+ if (syncLocal) {
10602
+ runGitFn(directory, ["fetch", "origin", targetBranch]);
10603
+ runGitFn(directory, ["reset", "--hard", "FETCH_HEAD"]);
10604
+ }
10605
+ return {
10606
+ ok: true,
10607
+ action: "committed",
10608
+ branch: targetBranch,
10609
+ before: headSha,
10610
+ after: nextCommit.sha,
10611
+ changedPaths,
10612
+ treeEntries: tree.length,
10613
+ verification: nextCommit.verification || null,
10614
+ ref: updatedRef
10615
+ };
10616
+ },
10617
+ async authMode() {
10618
+ if (appEnabled) return { mode: "github_app", appId, installationId };
10619
+ if (staticToken) return { mode: "token" };
10620
+ return { mode: "anonymous" };
10182
10621
  }
10183
10622
  };
10184
10623
  }
@@ -11758,7 +12197,22 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
11758
12197
  const subtaskId = parentTaskId && parentTaskId !== taskId ? taskId : "";
11759
12198
  let taskRoute;
11760
12199
  try {
11761
- taskRoute = await ensureTaskWorktree({ baseWorktree: config.basePath, taskId, taskType, parentTaskId, subtaskId, existingMetadata: metadata, allowNonGitFallback: process.env.NODE_ENV === "test" || config.test === true, opencodeBaseUrl: config.opencode?.baseUrl, opencodeBaseUrlConfigured: config.opencode?.baseUrlConfigured === true, openchamberBaseUrl: config.openchamber?.baseUrl, openchamberBaseUrlConfigured: config.openchamber?.baseUrlConfigured === true, clickupClient, webhookWorktree: worktree });
12200
+ taskRoute = await ensureTaskWorktree({
12201
+ baseWorktree: config.basePath,
12202
+ taskId,
12203
+ taskType,
12204
+ parentTaskId,
12205
+ subtaskId,
12206
+ existingMetadata: metadata,
12207
+ allowNonGitFallback: process.env.NODE_ENV === "test" || config.test === true,
12208
+ opencodeBaseUrl: config.opencode?.baseUrl,
12209
+ opencodeBaseUrlConfigured: config.opencode?.baseUrlConfigured === true,
12210
+ openchamberBaseUrl: config.openchamber?.baseUrl,
12211
+ openchamberBaseUrlConfigured: config.openchamber?.baseUrlConfigured === true,
12212
+ clickupClient,
12213
+ webhookWorktree: worktree,
12214
+ gitIdentity: config.github?.committer
12215
+ });
11762
12216
  } catch (error) {
11763
12217
  const message = `Optima webhook could not create or reuse a task worktree for ${taskId}: ${error.message}`;
11764
12218
  appendClickUpWebhookLocalLog(worktree, { type: "task_worktree_failed", taskId, message });
@@ -13630,6 +14084,14 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
13630
14084
  }
13631
14085
  });
13632
14086
  };
14087
+ const requireGitHubAppClient = async () => {
14088
+ if (!runtimeGitHubClient?.authMode) return { ok: false, error: "github_client_unavailable" };
14089
+ const auth = await runtimeGitHubClient.authMode();
14090
+ if (auth.mode !== "github_app") {
14091
+ return { ok: false, error: "github_app_auth_required", auth };
14092
+ }
14093
+ return { ok: true, auth };
14094
+ };
13633
14095
  const tools = {
13634
14096
  optima_init: tool({
13635
14097
  description: "Initialize the Optima workflow and CodeMap in the current repository",
@@ -13883,6 +14345,177 @@ Restart or reload OpenCode manually if the newly scaffolded config or agents are
13883
14345
  return JSON.stringify(result, null, 2);
13884
14346
  }
13885
14347
  }),
14348
+ optima_github_auth_mode: tool({
14349
+ description: "Report whether Optima GitHub operations use anonymous, user token, or GitHub App installation authentication",
14350
+ args: {},
14351
+ async execute() {
14352
+ try {
14353
+ if (!runtimeGitHubClient?.authMode) return JSON.stringify({ ok: false, error: "github_client_unavailable" }, null, 2);
14354
+ return JSON.stringify({ ok: true, ...await runtimeGitHubClient.authMode() }, null, 2);
14355
+ } catch (error) {
14356
+ return JSON.stringify({ ok: false, error: error.message }, null, 2);
14357
+ }
14358
+ }
14359
+ }),
14360
+ optima_github_create_pr: tool({
14361
+ description: "Create a GitHub PR through the Optima GitHub App identity so human owners can approve it",
14362
+ args: {
14363
+ title: tool.schema.string().describe("Pull request title"),
14364
+ head: tool.schema.string().describe("Source branch"),
14365
+ base: tool.schema.string().describe("Target branch"),
14366
+ body: tool.schema.string().describe("Pull request body"),
14367
+ draft: tool.schema.string().describe("Set to 'true' to create a draft PR")
14368
+ },
14369
+ async execute(args) {
14370
+ try {
14371
+ const auth = await requireGitHubAppClient();
14372
+ if (!auth.ok) return JSON.stringify(auth, null, 2);
14373
+ if (!runtimeGitHubClient?.createPullRequest) return JSON.stringify({ ok: false, error: "github_client_unavailable" }, null, 2);
14374
+ const result = await runtimeGitHubClient.createPullRequest({
14375
+ title: args.title,
14376
+ head: args.head,
14377
+ base: args.base,
14378
+ body: args.body,
14379
+ draft: String(args.draft || "").toLowerCase() === "true"
14380
+ });
14381
+ return JSON.stringify({ ok: true, pull_request: result }, null, 2);
14382
+ } catch (error) {
14383
+ return JSON.stringify({ ok: false, error: error.message }, null, 2);
14384
+ }
14385
+ }
14386
+ }),
14387
+ optima_github_comment_pr: tool({
14388
+ description: "Post a PR conversation comment through the Optima GitHub App identity",
14389
+ args: {
14390
+ pr_number: tool.schema.number().describe("Pull request number"),
14391
+ body: tool.schema.string().describe("Markdown comment body")
14392
+ },
14393
+ async execute(args) {
14394
+ try {
14395
+ const auth = await requireGitHubAppClient();
14396
+ if (!auth.ok) return JSON.stringify(auth, null, 2);
14397
+ if (!runtimeGitHubClient?.createIssueComment) return JSON.stringify({ ok: false, error: "github_client_unavailable" }, null, 2);
14398
+ const result = await runtimeGitHubClient.createIssueComment({ issueNumber: args.pr_number, body: args.body });
14399
+ return JSON.stringify({ ok: true, comment: result }, null, 2);
14400
+ } catch (error) {
14401
+ return JSON.stringify({ ok: false, error: error.message }, null, 2);
14402
+ }
14403
+ }
14404
+ }),
14405
+ optima_github_reply_review_comment: tool({
14406
+ description: "Reply to a GitHub PR review comment through the Optima GitHub App identity",
14407
+ args: {
14408
+ comment_id: tool.schema.number().describe("GitHub review comment id to reply to"),
14409
+ body: tool.schema.string().describe("Markdown reply body")
14410
+ },
14411
+ async execute(args) {
14412
+ try {
14413
+ const auth = await requireGitHubAppClient();
14414
+ if (!auth.ok) return JSON.stringify(auth, null, 2);
14415
+ if (!runtimeGitHubClient?.replyToReviewComment) return JSON.stringify({ ok: false, error: "github_client_unavailable" }, null, 2);
14416
+ const result = await runtimeGitHubClient.replyToReviewComment({ commentId: args.comment_id, body: args.body });
14417
+ return JSON.stringify({ ok: true, comment: result }, null, 2);
14418
+ } catch (error) {
14419
+ return JSON.stringify({ ok: false, error: error.message }, null, 2);
14420
+ }
14421
+ }
14422
+ }),
14423
+ optima_github_review_pr: tool({
14424
+ description: "Create a GitHub PR review through the Optima GitHub App identity",
14425
+ args: {
14426
+ pr_number: tool.schema.number().describe("Pull request number"),
14427
+ body: tool.schema.string().describe("Markdown review body"),
14428
+ event: tool.schema.string().describe("Review event: COMMENT, APPROVE, or REQUEST_CHANGES")
14429
+ },
14430
+ async execute(args) {
14431
+ try {
14432
+ const auth = await requireGitHubAppClient();
14433
+ if (!auth.ok) return JSON.stringify(auth, null, 2);
14434
+ if (!runtimeGitHubClient?.createPullRequestReview) return JSON.stringify({ ok: false, error: "github_client_unavailable" }, null, 2);
14435
+ const result = await runtimeGitHubClient.createPullRequestReview({ pullNumber: args.pr_number, body: args.body, event: args.event || "COMMENT" });
14436
+ return JSON.stringify({ ok: true, review: result }, null, 2);
14437
+ } catch (error) {
14438
+ return JSON.stringify({ ok: false, error: error.message }, null, 2);
14439
+ }
14440
+ }
14441
+ }),
14442
+ optima_github_verify_vercel_pr: tool({
14443
+ description: "Verify that a PR head commit has a successful Vercel preproduction deployment and a functional deployment URL before validation/handoff",
14444
+ args: {
14445
+ pr_number: tool.schema.number().describe("Pull request number"),
14446
+ context: tool.schema.string().describe("Required GitHub status context; defaults to 'Vercel \u2013 defend-preproduction'"),
14447
+ environment: tool.schema.string().describe("Required GitHub deployment environment; defaults to 'Preview \u2013 defend-preproduction'"),
14448
+ require_functional_url: tool.schema.string().describe("Set to 'false' to skip HTTP probing of the deployment URL")
14449
+ },
14450
+ async execute(args) {
14451
+ try {
14452
+ const auth = await requireGitHubAppClient();
14453
+ if (!auth.ok) return JSON.stringify(auth, null, 2);
14454
+ if (!runtimeGitHubClient?.verifyVercelPullRequestDeployment) return JSON.stringify({ ok: false, error: "github_client_unavailable" }, null, 2);
14455
+ const result = await runtimeGitHubClient.verifyVercelPullRequestDeployment({
14456
+ pullNumber: args.pr_number,
14457
+ context: args.context || "Vercel \u2013 defend-preproduction",
14458
+ environment: args.environment || "Preview \u2013 defend-preproduction",
14459
+ requireFunctionalUrl: String(args.require_functional_url || "true").toLowerCase() !== "false"
14460
+ });
14461
+ return JSON.stringify(result, null, 2);
14462
+ } catch (error) {
14463
+ return JSON.stringify({ ok: false, error: error.message }, null, 2);
14464
+ }
14465
+ }
14466
+ }),
14467
+ optima_github_merge_pr: tool({
14468
+ description: "Merge a GitHub PR through the Optima GitHub App identity after required human approval gates pass",
14469
+ args: {
14470
+ pr_number: tool.schema.number().describe("Pull request number"),
14471
+ commit_title: tool.schema.string().describe("Optional merge commit title"),
14472
+ commit_message: tool.schema.string().describe("Optional merge commit message"),
14473
+ merge_method: tool.schema.string().describe("merge, squash, or rebase; defaults to squash")
14474
+ },
14475
+ async execute(args) {
14476
+ try {
14477
+ const auth = await requireGitHubAppClient();
14478
+ if (!auth.ok) return JSON.stringify(auth, null, 2);
14479
+ if (!runtimeGitHubClient?.mergePullRequest) return JSON.stringify({ ok: false, error: "github_client_unavailable" }, null, 2);
14480
+ const result = await runtimeGitHubClient.mergePullRequest({
14481
+ pullNumber: args.pr_number,
14482
+ commitTitle: args.commit_title,
14483
+ commitMessage: args.commit_message,
14484
+ mergeMethod: args.merge_method || "squash"
14485
+ });
14486
+ return JSON.stringify({ ok: true, merge: result }, null, 2);
14487
+ } catch (error) {
14488
+ return JSON.stringify({ ok: false, error: error.message }, null, 2);
14489
+ }
14490
+ }
14491
+ }),
14492
+ optima_github_commit_worktree: tool({
14493
+ description: "Commit current worktree changes directly through the Optima GitHub App API so commits are authored by the App/bot and can be GitHub-verified",
14494
+ args: {
14495
+ directory: tool.schema.string().describe("Task worktree directory containing the file changes"),
14496
+ branch: tool.schema.string().describe("Target branch to update; defaults to the current local branch"),
14497
+ message: tool.schema.string().describe("Commit message"),
14498
+ sync_local: tool.schema.string().describe("Set to 'true' to fetch/reset the local worktree to the API-created commit after success")
14499
+ },
14500
+ async execute(args, context) {
14501
+ try {
14502
+ const auth = await requireGitHubAppClient();
14503
+ if (!auth.ok) return JSON.stringify(auth, null, 2);
14504
+ if (!runtimeGitHubClient?.commitWorktree) return JSON.stringify({ ok: false, error: "github_client_unavailable" }, null, 2);
14505
+ const directory = resolveSessionToolDirectory({ requestedDirectory: args.directory, context, pluginWorktree: worktree, clickUpWebhookValidation });
14506
+ if (!directory.ok) return JSON.stringify({ ok: false, error: directory.error }, null, 2);
14507
+ const result = await runtimeGitHubClient.commitWorktree({
14508
+ worktree: directory.directory,
14509
+ branch: args.branch,
14510
+ message: args.message,
14511
+ syncLocal: String(args.sync_local || "").toLowerCase() === "true"
14512
+ });
14513
+ return JSON.stringify(result, null, 2);
14514
+ } catch (error) {
14515
+ return JSON.stringify({ ok: false, error: error.message }, null, 2);
14516
+ }
14517
+ }
14518
+ }),
13886
14519
  optima_validate: tool({
13887
14520
  description: "Validate Optima workflow artifacts and CodeMap integrity",
13888
14521
  args: {},