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