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