@defend-tech/opencode-optima 0.1.73 → 0.1.75

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,209 @@ 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
+ }
10162
10299
  function createGitHubApiClient(config = {}, fetchImpl = globalThis.fetch) {
10163
- const token = resolveSecretReference(config?.apiToken);
10300
+ const staticToken = resolveSecretReference(config?.apiToken);
10301
+ const appConfig = isPlainObject(config?.app) ? config.app : {};
10302
+ const appEnabled = appConfig.enabled === true || Boolean(appConfig.appId && appConfig.installationId && (appConfig.privateKey || appConfig.privateKeyFile));
10303
+ const privateKey = resolveGitHubAppPrivateKey(appConfig);
10164
10304
  const owner = String(config?.owner || "").trim();
10165
10305
  const repo = String(config?.repo || "").trim();
10166
- const request = async (pathname) => {
10306
+ let installationToken = null;
10307
+ let installationTokenExpiresAt = 0;
10308
+ const requestJson = async (url, { method = "GET", headers = {}, body = void 0 } = {}) => {
10167
10309
  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}`, {
10310
+ const response = await fetchImpl(url, {
10311
+ method,
10170
10312
  headers: {
10171
10313
  Accept: "application/vnd.github+json",
10172
10314
  "X-GitHub-Api-Version": "2022-11-28",
10173
- ...token ? { Authorization: `Bearer ${token}` } : {}
10174
- }
10315
+ ...body === void 0 ? {} : { "Content-Type": "application/json" },
10316
+ ...headers
10317
+ },
10318
+ ...body === void 0 ? {} : { body: JSON.stringify(body) }
10175
10319
  });
10176
10320
  if (!response.ok) throw new Error(`GitHub API request failed: ${response.status}`);
10177
- return response.json();
10321
+ return response.status === 204 ? null : response.json();
10322
+ };
10323
+ const getAuthToken = async () => {
10324
+ if (!appEnabled) return staticToken || "";
10325
+ const nowMs = Date.now();
10326
+ if (installationToken && installationTokenExpiresAt - 6e4 > nowMs) return installationToken;
10327
+ const jwt = createGitHubAppJwt({ appId: appConfig.appId, privateKey });
10328
+ const installation = await requestJson(`https://api.github.com/app/installations/${encodeURIComponent(appConfig.installationId)}/access_tokens`, {
10329
+ method: "POST",
10330
+ headers: { Authorization: `Bearer ${jwt}` }
10331
+ });
10332
+ installationToken = String(installation?.token || "").trim();
10333
+ installationTokenExpiresAt = Date.parse(installation?.expires_at || "") || nowMs + 3e6;
10334
+ if (!installationToken) throw new Error("GitHub App installation token response did not include a token");
10335
+ return installationToken;
10178
10336
  };
10337
+ const request = async (pathname, { method = "GET", body = void 0 } = {}) => {
10338
+ if (!owner || !repo) throw new Error("GitHub repository owner/repo is not configured");
10339
+ const token = await getAuthToken();
10340
+ return requestJson(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}${pathname}`, {
10341
+ method,
10342
+ body,
10343
+ headers: token ? { Authorization: `Bearer ${token}` } : {}
10344
+ });
10345
+ };
10346
+ const getRef = async (branch) => request(`/git/ref/heads/${encodeGitHubBranchRef(branch)}`);
10347
+ const getCommitObject = async (sha) => request(`/git/commits/${encodeURIComponent(sha)}`);
10348
+ const createBlob = async ({ content, encoding }) => request("/git/blobs", { method: "POST", body: { content, encoding } });
10349
+ const createTree = async ({ baseTree, tree }) => request("/git/trees", { method: "POST", body: { base_tree: baseTree, tree } });
10350
+ const createCommit = async ({ message, treeSha, parents }) => request("/git/commits", { method: "POST", body: { message, tree: treeSha, parents } });
10351
+ const updateRef = async ({ branch, sha, force = false }) => request(`/git/refs/heads/${encodeGitHubBranchRef(branch)}`, { method: "PATCH", body: { sha, force } });
10179
10352
  return {
10180
10353
  async getPullRequest(number) {
10181
10354
  return request(`/pulls/${encodeURIComponent(number)}`);
10355
+ },
10356
+ async createPullRequest({ title, head, base, body = "", draft = false, maintainerCanModify = true }) {
10357
+ return request("/pulls", {
10358
+ method: "POST",
10359
+ body: {
10360
+ title: String(title || ""),
10361
+ head: String(head || ""),
10362
+ base: String(base || ""),
10363
+ body: String(body || ""),
10364
+ draft: draft === true,
10365
+ maintainer_can_modify: maintainerCanModify !== false
10366
+ }
10367
+ });
10368
+ },
10369
+ async createIssueComment({ issueNumber, body }) {
10370
+ return request(`/issues/${encodeURIComponent(issueNumber)}/comments`, { method: "POST", body: { body: String(body || "") } });
10371
+ },
10372
+ async replyToReviewComment({ commentId, body }) {
10373
+ return request(`/pulls/comments/${encodeURIComponent(commentId)}/replies`, { method: "POST", body: { body: String(body || "") } });
10374
+ },
10375
+ async createPullRequestReview({ pullNumber, body, event = "COMMENT" }) {
10376
+ return request(`/pulls/${encodeURIComponent(pullNumber)}/reviews`, { method: "POST", body: { body: String(body || ""), event: String(event || "COMMENT").toUpperCase() } });
10377
+ },
10378
+ async mergePullRequest({ pullNumber, commitTitle = "", commitMessage = "", mergeMethod = "squash" }) {
10379
+ const body = {
10380
+ merge_method: String(mergeMethod || "squash")
10381
+ };
10382
+ if (commitTitle) body.commit_title = String(commitTitle);
10383
+ if (commitMessage) body.commit_message = String(commitMessage);
10384
+ return request(`/pulls/${encodeURIComponent(pullNumber)}/merge`, { method: "PUT", body });
10385
+ },
10386
+ async commitWorktree({ worktree, branch = "", message = "", runGitFn = runGit, syncLocal = false } = {}) {
10387
+ const directory = path6.resolve(String(worktree || ""));
10388
+ if (!directory || !fs5.existsSync(directory)) throw new Error("worktree does not exist");
10389
+ const targetBranch = String(branch || currentGitBranch(directory, runGitFn)).trim();
10390
+ if (!targetBranch || targetBranch === "HEAD") throw new Error("target branch is required for GitHub API commit");
10391
+ const commitMessage = String(message || "").trim();
10392
+ if (!commitMessage) throw new Error("commit message is required");
10393
+ const changedPaths = listWorktreeChangedPaths(directory, runGitFn);
10394
+ if (changedPaths.length === 0) return { ok: true, action: "no_changes", branch: targetBranch, changedPaths: [] };
10395
+ const ref = await getRef(targetBranch);
10396
+ const headSha = ref?.object?.sha;
10397
+ if (!headSha) throw new Error(`GitHub ref for ${targetBranch} did not include a head sha`);
10398
+ const headCommit = await getCommitObject(headSha);
10399
+ const baseTree = headCommit?.tree?.sha;
10400
+ if (!baseTree) throw new Error(`GitHub commit ${headSha} did not include a tree sha`);
10401
+ const tree = [];
10402
+ for (const gitPath of changedPaths) {
10403
+ const entry = treeEntryForWorktreePath(directory, gitPath);
10404
+ if (!entry) continue;
10405
+ if (entry.deleted) {
10406
+ tree.push({ path: entry.path, mode: entry.mode, type: entry.type, sha: null });
10407
+ continue;
10408
+ }
10409
+ const blob = await createBlob({ content: entry.content, encoding: entry.encoding });
10410
+ if (!blob?.sha) throw new Error(`GitHub blob creation failed for ${gitPath}`);
10411
+ tree.push({ path: entry.path, mode: entry.mode, type: entry.type, sha: blob.sha });
10412
+ }
10413
+ if (tree.length === 0) return { ok: true, action: "no_changes", branch: targetBranch, changedPaths: [] };
10414
+ const nextTree = await createTree({ baseTree, tree });
10415
+ const nextCommit = await createCommit({ message: commitMessage, treeSha: nextTree.sha, parents: [headSha] });
10416
+ if (!nextCommit?.sha) throw new Error("GitHub commit creation did not return a sha");
10417
+ const updatedRef = await updateRef({ branch: targetBranch, sha: nextCommit.sha, force: false });
10418
+ if (syncLocal) {
10419
+ runGitFn(directory, ["fetch", "origin", targetBranch]);
10420
+ runGitFn(directory, ["reset", "--hard", "FETCH_HEAD"]);
10421
+ }
10422
+ return {
10423
+ ok: true,
10424
+ action: "committed",
10425
+ branch: targetBranch,
10426
+ before: headSha,
10427
+ after: nextCommit.sha,
10428
+ changedPaths,
10429
+ treeEntries: tree.length,
10430
+ verification: nextCommit.verification || null,
10431
+ ref: updatedRef
10432
+ };
10433
+ },
10434
+ async authMode() {
10435
+ if (appEnabled) return { mode: "github_app", appId: String(appConfig.appId || ""), installationId: String(appConfig.installationId || "") };
10436
+ if (staticToken) return { mode: "token" };
10437
+ return { mode: "anonymous" };
10182
10438
  }
10183
10439
  };
10184
10440
  }
@@ -11758,7 +12014,22 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
11758
12014
  const subtaskId = parentTaskId && parentTaskId !== taskId ? taskId : "";
11759
12015
  let taskRoute;
11760
12016
  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 });
12017
+ taskRoute = await ensureTaskWorktree({
12018
+ baseWorktree: config.basePath,
12019
+ taskId,
12020
+ taskType,
12021
+ parentTaskId,
12022
+ subtaskId,
12023
+ existingMetadata: metadata,
12024
+ allowNonGitFallback: process.env.NODE_ENV === "test" || config.test === true,
12025
+ opencodeBaseUrl: config.opencode?.baseUrl,
12026
+ opencodeBaseUrlConfigured: config.opencode?.baseUrlConfigured === true,
12027
+ openchamberBaseUrl: config.openchamber?.baseUrl,
12028
+ openchamberBaseUrlConfigured: config.openchamber?.baseUrlConfigured === true,
12029
+ clickupClient,
12030
+ webhookWorktree: worktree,
12031
+ gitIdentity: config.github?.committer
12032
+ });
11762
12033
  } catch (error) {
11763
12034
  const message = `Optima webhook could not create or reuse a task worktree for ${taskId}: ${error.message}`;
11764
12035
  appendClickUpWebhookLocalLog(worktree, { type: "task_worktree_failed", taskId, message });
@@ -13630,6 +13901,14 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
13630
13901
  }
13631
13902
  });
13632
13903
  };
13904
+ const requireGitHubAppClient = async () => {
13905
+ if (!runtimeGitHubClient?.authMode) return { ok: false, error: "github_client_unavailable" };
13906
+ const auth = await runtimeGitHubClient.authMode();
13907
+ if (auth.mode !== "github_app") {
13908
+ return { ok: false, error: "github_app_auth_required", auth };
13909
+ }
13910
+ return { ok: true, auth };
13911
+ };
13633
13912
  const tools = {
13634
13913
  optima_init: tool({
13635
13914
  description: "Initialize the Optima workflow and CodeMap in the current repository",
@@ -13883,6 +14162,152 @@ Restart or reload OpenCode manually if the newly scaffolded config or agents are
13883
14162
  return JSON.stringify(result, null, 2);
13884
14163
  }
13885
14164
  }),
14165
+ optima_github_auth_mode: tool({
14166
+ description: "Report whether Optima GitHub operations use anonymous, user token, or GitHub App installation authentication",
14167
+ args: {},
14168
+ async execute() {
14169
+ try {
14170
+ if (!runtimeGitHubClient?.authMode) return JSON.stringify({ ok: false, error: "github_client_unavailable" }, null, 2);
14171
+ return JSON.stringify({ ok: true, ...await runtimeGitHubClient.authMode() }, null, 2);
14172
+ } catch (error) {
14173
+ return JSON.stringify({ ok: false, error: error.message }, null, 2);
14174
+ }
14175
+ }
14176
+ }),
14177
+ optima_github_create_pr: tool({
14178
+ description: "Create a GitHub PR through the Optima GitHub App identity so human owners can approve it",
14179
+ args: {
14180
+ title: tool.schema.string().describe("Pull request title"),
14181
+ head: tool.schema.string().describe("Source branch"),
14182
+ base: tool.schema.string().describe("Target branch"),
14183
+ body: tool.schema.string().describe("Pull request body"),
14184
+ draft: tool.schema.string().describe("Set to 'true' to create a draft PR")
14185
+ },
14186
+ async execute(args) {
14187
+ try {
14188
+ const auth = await requireGitHubAppClient();
14189
+ if (!auth.ok) return JSON.stringify(auth, null, 2);
14190
+ if (!runtimeGitHubClient?.createPullRequest) return JSON.stringify({ ok: false, error: "github_client_unavailable" }, null, 2);
14191
+ const result = await runtimeGitHubClient.createPullRequest({
14192
+ title: args.title,
14193
+ head: args.head,
14194
+ base: args.base,
14195
+ body: args.body,
14196
+ draft: String(args.draft || "").toLowerCase() === "true"
14197
+ });
14198
+ return JSON.stringify({ ok: true, pull_request: result }, null, 2);
14199
+ } catch (error) {
14200
+ return JSON.stringify({ ok: false, error: error.message }, null, 2);
14201
+ }
14202
+ }
14203
+ }),
14204
+ optima_github_comment_pr: tool({
14205
+ description: "Post a PR conversation comment through the Optima GitHub App identity",
14206
+ args: {
14207
+ pr_number: tool.schema.number().describe("Pull request number"),
14208
+ body: tool.schema.string().describe("Markdown comment body")
14209
+ },
14210
+ async execute(args) {
14211
+ try {
14212
+ const auth = await requireGitHubAppClient();
14213
+ if (!auth.ok) return JSON.stringify(auth, null, 2);
14214
+ if (!runtimeGitHubClient?.createIssueComment) return JSON.stringify({ ok: false, error: "github_client_unavailable" }, null, 2);
14215
+ const result = await runtimeGitHubClient.createIssueComment({ issueNumber: args.pr_number, body: args.body });
14216
+ return JSON.stringify({ ok: true, comment: result }, null, 2);
14217
+ } catch (error) {
14218
+ return JSON.stringify({ ok: false, error: error.message }, null, 2);
14219
+ }
14220
+ }
14221
+ }),
14222
+ optima_github_reply_review_comment: tool({
14223
+ description: "Reply to a GitHub PR review comment through the Optima GitHub App identity",
14224
+ args: {
14225
+ comment_id: tool.schema.number().describe("GitHub review comment id to reply to"),
14226
+ body: tool.schema.string().describe("Markdown reply body")
14227
+ },
14228
+ async execute(args) {
14229
+ try {
14230
+ const auth = await requireGitHubAppClient();
14231
+ if (!auth.ok) return JSON.stringify(auth, null, 2);
14232
+ if (!runtimeGitHubClient?.replyToReviewComment) return JSON.stringify({ ok: false, error: "github_client_unavailable" }, null, 2);
14233
+ const result = await runtimeGitHubClient.replyToReviewComment({ commentId: args.comment_id, body: args.body });
14234
+ return JSON.stringify({ ok: true, comment: result }, null, 2);
14235
+ } catch (error) {
14236
+ return JSON.stringify({ ok: false, error: error.message }, null, 2);
14237
+ }
14238
+ }
14239
+ }),
14240
+ optima_github_review_pr: tool({
14241
+ description: "Create a GitHub PR review through the Optima GitHub App identity",
14242
+ args: {
14243
+ pr_number: tool.schema.number().describe("Pull request number"),
14244
+ body: tool.schema.string().describe("Markdown review body"),
14245
+ event: tool.schema.string().describe("Review event: COMMENT, APPROVE, or REQUEST_CHANGES")
14246
+ },
14247
+ async execute(args) {
14248
+ try {
14249
+ const auth = await requireGitHubAppClient();
14250
+ if (!auth.ok) return JSON.stringify(auth, null, 2);
14251
+ if (!runtimeGitHubClient?.createPullRequestReview) return JSON.stringify({ ok: false, error: "github_client_unavailable" }, null, 2);
14252
+ const result = await runtimeGitHubClient.createPullRequestReview({ pullNumber: args.pr_number, body: args.body, event: args.event || "COMMENT" });
14253
+ return JSON.stringify({ ok: true, review: result }, null, 2);
14254
+ } catch (error) {
14255
+ return JSON.stringify({ ok: false, error: error.message }, null, 2);
14256
+ }
14257
+ }
14258
+ }),
14259
+ optima_github_merge_pr: tool({
14260
+ description: "Merge a GitHub PR through the Optima GitHub App identity after required human approval gates pass",
14261
+ args: {
14262
+ pr_number: tool.schema.number().describe("Pull request number"),
14263
+ commit_title: tool.schema.string().describe("Optional merge commit title"),
14264
+ commit_message: tool.schema.string().describe("Optional merge commit message"),
14265
+ merge_method: tool.schema.string().describe("merge, squash, or rebase; defaults to squash")
14266
+ },
14267
+ async execute(args) {
14268
+ try {
14269
+ const auth = await requireGitHubAppClient();
14270
+ if (!auth.ok) return JSON.stringify(auth, null, 2);
14271
+ if (!runtimeGitHubClient?.mergePullRequest) return JSON.stringify({ ok: false, error: "github_client_unavailable" }, null, 2);
14272
+ const result = await runtimeGitHubClient.mergePullRequest({
14273
+ pullNumber: args.pr_number,
14274
+ commitTitle: args.commit_title,
14275
+ commitMessage: args.commit_message,
14276
+ mergeMethod: args.merge_method || "squash"
14277
+ });
14278
+ return JSON.stringify({ ok: true, merge: result }, null, 2);
14279
+ } catch (error) {
14280
+ return JSON.stringify({ ok: false, error: error.message }, null, 2);
14281
+ }
14282
+ }
14283
+ }),
14284
+ optima_github_commit_worktree: tool({
14285
+ 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",
14286
+ args: {
14287
+ directory: tool.schema.string().describe("Task worktree directory containing the file changes"),
14288
+ branch: tool.schema.string().describe("Target branch to update; defaults to the current local branch"),
14289
+ message: tool.schema.string().describe("Commit message"),
14290
+ sync_local: tool.schema.string().describe("Set to 'true' to fetch/reset the local worktree to the API-created commit after success")
14291
+ },
14292
+ async execute(args, context) {
14293
+ try {
14294
+ const auth = await requireGitHubAppClient();
14295
+ if (!auth.ok) return JSON.stringify(auth, null, 2);
14296
+ if (!runtimeGitHubClient?.commitWorktree) return JSON.stringify({ ok: false, error: "github_client_unavailable" }, null, 2);
14297
+ const directory = resolveSessionToolDirectory({ requestedDirectory: args.directory, context, pluginWorktree: worktree, clickUpWebhookValidation });
14298
+ if (!directory.ok) return JSON.stringify({ ok: false, error: directory.error }, null, 2);
14299
+ const result = await runtimeGitHubClient.commitWorktree({
14300
+ worktree: directory.directory,
14301
+ branch: args.branch,
14302
+ message: args.message,
14303
+ syncLocal: String(args.sync_local || "").toLowerCase() === "true"
14304
+ });
14305
+ return JSON.stringify(result, null, 2);
14306
+ } catch (error) {
14307
+ return JSON.stringify({ ok: false, error: error.message }, null, 2);
14308
+ }
14309
+ }
14310
+ }),
13886
14311
  optima_validate: tool({
13887
14312
  description: "Validate Optima workflow artifacts and CodeMap integrity",
13888
14313
  args: {},