@defend-tech/opencode-optima 0.1.74 → 0.1.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/agents/developer.md +1 -0
- package/assets/agents/tech_lead.md +3 -0
- package/assets/agents/workflow_product_manager.md +11 -2
- package/dist/index.js +441 -16
- package/dist/sanitize_cli.js +441 -16
- package/docs/core/agent_orchestration.md +1 -0
- package/docs/core/agent_orchestration.prompt.md +1 -0
- package/docs/guides/AGENTS.md +1 -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,209 @@ 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
|
+
}
|
|
10169
10306
|
function createGitHubApiClient(config = {}, fetchImpl = globalThis.fetch) {
|
|
10170
|
-
const
|
|
10307
|
+
const staticToken = resolveSecretReference(config?.apiToken);
|
|
10308
|
+
const appConfig = isPlainObject(config?.app) ? config.app : {};
|
|
10309
|
+
const appEnabled = appConfig.enabled === true || Boolean(appConfig.appId && appConfig.installationId && (appConfig.privateKey || appConfig.privateKeyFile));
|
|
10310
|
+
const privateKey = resolveGitHubAppPrivateKey(appConfig);
|
|
10171
10311
|
const owner = String(config?.owner || "").trim();
|
|
10172
10312
|
const repo = String(config?.repo || "").trim();
|
|
10173
|
-
|
|
10313
|
+
let installationToken = null;
|
|
10314
|
+
let installationTokenExpiresAt = 0;
|
|
10315
|
+
const requestJson = async (url, { method = "GET", headers = {}, body = void 0 } = {}) => {
|
|
10174
10316
|
if (typeof fetchImpl !== "function") throw new Error("fetch is unavailable; inject a GitHub client for live PR lookup");
|
|
10175
|
-
|
|
10176
|
-
|
|
10317
|
+
const response = await fetchImpl(url, {
|
|
10318
|
+
method,
|
|
10177
10319
|
headers: {
|
|
10178
10320
|
Accept: "application/vnd.github+json",
|
|
10179
10321
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
10180
|
-
...
|
|
10181
|
-
|
|
10322
|
+
...body === void 0 ? {} : { "Content-Type": "application/json" },
|
|
10323
|
+
...headers
|
|
10324
|
+
},
|
|
10325
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
10182
10326
|
});
|
|
10183
10327
|
if (!response.ok) throw new Error(`GitHub API request failed: ${response.status}`);
|
|
10184
|
-
return response.json();
|
|
10328
|
+
return response.status === 204 ? null : response.json();
|
|
10329
|
+
};
|
|
10330
|
+
const getAuthToken = async () => {
|
|
10331
|
+
if (!appEnabled) return staticToken || "";
|
|
10332
|
+
const nowMs = Date.now();
|
|
10333
|
+
if (installationToken && installationTokenExpiresAt - 6e4 > nowMs) return installationToken;
|
|
10334
|
+
const jwt = createGitHubAppJwt({ appId: appConfig.appId, privateKey });
|
|
10335
|
+
const installation = await requestJson(`https://api.github.com/app/installations/${encodeURIComponent(appConfig.installationId)}/access_tokens`, {
|
|
10336
|
+
method: "POST",
|
|
10337
|
+
headers: { Authorization: `Bearer ${jwt}` }
|
|
10338
|
+
});
|
|
10339
|
+
installationToken = String(installation?.token || "").trim();
|
|
10340
|
+
installationTokenExpiresAt = Date.parse(installation?.expires_at || "") || nowMs + 3e6;
|
|
10341
|
+
if (!installationToken) throw new Error("GitHub App installation token response did not include a token");
|
|
10342
|
+
return installationToken;
|
|
10185
10343
|
};
|
|
10344
|
+
const request = async (pathname, { method = "GET", body = void 0 } = {}) => {
|
|
10345
|
+
if (!owner || !repo) throw new Error("GitHub repository owner/repo is not configured");
|
|
10346
|
+
const token = await getAuthToken();
|
|
10347
|
+
return requestJson(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}${pathname}`, {
|
|
10348
|
+
method,
|
|
10349
|
+
body,
|
|
10350
|
+
headers: token ? { Authorization: `Bearer ${token}` } : {}
|
|
10351
|
+
});
|
|
10352
|
+
};
|
|
10353
|
+
const getRef = async (branch) => request(`/git/ref/heads/${encodeGitHubBranchRef(branch)}`);
|
|
10354
|
+
const getCommitObject = async (sha) => request(`/git/commits/${encodeURIComponent(sha)}`);
|
|
10355
|
+
const createBlob = async ({ content, encoding }) => request("/git/blobs", { method: "POST", body: { content, encoding } });
|
|
10356
|
+
const createTree = async ({ baseTree, tree }) => request("/git/trees", { method: "POST", body: { base_tree: baseTree, tree } });
|
|
10357
|
+
const createCommit = async ({ message, treeSha, parents }) => request("/git/commits", { method: "POST", body: { message, tree: treeSha, parents } });
|
|
10358
|
+
const updateRef = async ({ branch, sha, force = false }) => request(`/git/refs/heads/${encodeGitHubBranchRef(branch)}`, { method: "PATCH", body: { sha, force } });
|
|
10186
10359
|
return {
|
|
10187
10360
|
async getPullRequest(number) {
|
|
10188
10361
|
return request(`/pulls/${encodeURIComponent(number)}`);
|
|
10362
|
+
},
|
|
10363
|
+
async createPullRequest({ title, head, base, body = "", draft = false, maintainerCanModify = true }) {
|
|
10364
|
+
return request("/pulls", {
|
|
10365
|
+
method: "POST",
|
|
10366
|
+
body: {
|
|
10367
|
+
title: String(title || ""),
|
|
10368
|
+
head: String(head || ""),
|
|
10369
|
+
base: String(base || ""),
|
|
10370
|
+
body: String(body || ""),
|
|
10371
|
+
draft: draft === true,
|
|
10372
|
+
maintainer_can_modify: maintainerCanModify !== false
|
|
10373
|
+
}
|
|
10374
|
+
});
|
|
10375
|
+
},
|
|
10376
|
+
async createIssueComment({ issueNumber, body }) {
|
|
10377
|
+
return request(`/issues/${encodeURIComponent(issueNumber)}/comments`, { method: "POST", body: { body: String(body || "") } });
|
|
10378
|
+
},
|
|
10379
|
+
async replyToReviewComment({ commentId, body }) {
|
|
10380
|
+
return request(`/pulls/comments/${encodeURIComponent(commentId)}/replies`, { method: "POST", body: { body: String(body || "") } });
|
|
10381
|
+
},
|
|
10382
|
+
async createPullRequestReview({ pullNumber, body, event = "COMMENT" }) {
|
|
10383
|
+
return request(`/pulls/${encodeURIComponent(pullNumber)}/reviews`, { method: "POST", body: { body: String(body || ""), event: String(event || "COMMENT").toUpperCase() } });
|
|
10384
|
+
},
|
|
10385
|
+
async mergePullRequest({ pullNumber, commitTitle = "", commitMessage = "", mergeMethod = "squash" }) {
|
|
10386
|
+
const body = {
|
|
10387
|
+
merge_method: String(mergeMethod || "squash")
|
|
10388
|
+
};
|
|
10389
|
+
if (commitTitle) body.commit_title = String(commitTitle);
|
|
10390
|
+
if (commitMessage) body.commit_message = String(commitMessage);
|
|
10391
|
+
return request(`/pulls/${encodeURIComponent(pullNumber)}/merge`, { method: "PUT", body });
|
|
10392
|
+
},
|
|
10393
|
+
async commitWorktree({ worktree, branch = "", message = "", runGitFn = runGit, syncLocal = false } = {}) {
|
|
10394
|
+
const directory = path6.resolve(String(worktree || ""));
|
|
10395
|
+
if (!directory || !fs5.existsSync(directory)) throw new Error("worktree does not exist");
|
|
10396
|
+
const targetBranch = String(branch || currentGitBranch(directory, runGitFn)).trim();
|
|
10397
|
+
if (!targetBranch || targetBranch === "HEAD") throw new Error("target branch is required for GitHub API commit");
|
|
10398
|
+
const commitMessage = String(message || "").trim();
|
|
10399
|
+
if (!commitMessage) throw new Error("commit message is required");
|
|
10400
|
+
const changedPaths = listWorktreeChangedPaths(directory, runGitFn);
|
|
10401
|
+
if (changedPaths.length === 0) return { ok: true, action: "no_changes", branch: targetBranch, changedPaths: [] };
|
|
10402
|
+
const ref = await getRef(targetBranch);
|
|
10403
|
+
const headSha = ref?.object?.sha;
|
|
10404
|
+
if (!headSha) throw new Error(`GitHub ref for ${targetBranch} did not include a head sha`);
|
|
10405
|
+
const headCommit = await getCommitObject(headSha);
|
|
10406
|
+
const baseTree = headCommit?.tree?.sha;
|
|
10407
|
+
if (!baseTree) throw new Error(`GitHub commit ${headSha} did not include a tree sha`);
|
|
10408
|
+
const tree = [];
|
|
10409
|
+
for (const gitPath of changedPaths) {
|
|
10410
|
+
const entry = treeEntryForWorktreePath(directory, gitPath);
|
|
10411
|
+
if (!entry) continue;
|
|
10412
|
+
if (entry.deleted) {
|
|
10413
|
+
tree.push({ path: entry.path, mode: entry.mode, type: entry.type, sha: null });
|
|
10414
|
+
continue;
|
|
10415
|
+
}
|
|
10416
|
+
const blob = await createBlob({ content: entry.content, encoding: entry.encoding });
|
|
10417
|
+
if (!blob?.sha) throw new Error(`GitHub blob creation failed for ${gitPath}`);
|
|
10418
|
+
tree.push({ path: entry.path, mode: entry.mode, type: entry.type, sha: blob.sha });
|
|
10419
|
+
}
|
|
10420
|
+
if (tree.length === 0) return { ok: true, action: "no_changes", branch: targetBranch, changedPaths: [] };
|
|
10421
|
+
const nextTree = await createTree({ baseTree, tree });
|
|
10422
|
+
const nextCommit = await createCommit({ message: commitMessage, treeSha: nextTree.sha, parents: [headSha] });
|
|
10423
|
+
if (!nextCommit?.sha) throw new Error("GitHub commit creation did not return a sha");
|
|
10424
|
+
const updatedRef = await updateRef({ branch: targetBranch, sha: nextCommit.sha, force: false });
|
|
10425
|
+
if (syncLocal) {
|
|
10426
|
+
runGitFn(directory, ["fetch", "origin", targetBranch]);
|
|
10427
|
+
runGitFn(directory, ["reset", "--hard", "FETCH_HEAD"]);
|
|
10428
|
+
}
|
|
10429
|
+
return {
|
|
10430
|
+
ok: true,
|
|
10431
|
+
action: "committed",
|
|
10432
|
+
branch: targetBranch,
|
|
10433
|
+
before: headSha,
|
|
10434
|
+
after: nextCommit.sha,
|
|
10435
|
+
changedPaths,
|
|
10436
|
+
treeEntries: tree.length,
|
|
10437
|
+
verification: nextCommit.verification || null,
|
|
10438
|
+
ref: updatedRef
|
|
10439
|
+
};
|
|
10440
|
+
},
|
|
10441
|
+
async authMode() {
|
|
10442
|
+
if (appEnabled) return { mode: "github_app", appId: String(appConfig.appId || ""), installationId: String(appConfig.installationId || "") };
|
|
10443
|
+
if (staticToken) return { mode: "token" };
|
|
10444
|
+
return { mode: "anonymous" };
|
|
10189
10445
|
}
|
|
10190
10446
|
};
|
|
10191
10447
|
}
|
|
@@ -11765,7 +12021,22 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
11765
12021
|
const subtaskId = parentTaskId && parentTaskId !== taskId ? taskId : "";
|
|
11766
12022
|
let taskRoute;
|
|
11767
12023
|
try {
|
|
11768
|
-
taskRoute = await ensureTaskWorktree({
|
|
12024
|
+
taskRoute = await ensureTaskWorktree({
|
|
12025
|
+
baseWorktree: config.basePath,
|
|
12026
|
+
taskId,
|
|
12027
|
+
taskType,
|
|
12028
|
+
parentTaskId,
|
|
12029
|
+
subtaskId,
|
|
12030
|
+
existingMetadata: metadata,
|
|
12031
|
+
allowNonGitFallback: process.env.NODE_ENV === "test" || config.test === true,
|
|
12032
|
+
opencodeBaseUrl: config.opencode?.baseUrl,
|
|
12033
|
+
opencodeBaseUrlConfigured: config.opencode?.baseUrlConfigured === true,
|
|
12034
|
+
openchamberBaseUrl: config.openchamber?.baseUrl,
|
|
12035
|
+
openchamberBaseUrlConfigured: config.openchamber?.baseUrlConfigured === true,
|
|
12036
|
+
clickupClient,
|
|
12037
|
+
webhookWorktree: worktree,
|
|
12038
|
+
gitIdentity: config.github?.committer
|
|
12039
|
+
});
|
|
11769
12040
|
} catch (error) {
|
|
11770
12041
|
const message = `Optima webhook could not create or reuse a task worktree for ${taskId}: ${error.message}`;
|
|
11771
12042
|
appendClickUpWebhookLocalLog(worktree, { type: "task_worktree_failed", taskId, message });
|
|
@@ -13637,6 +13908,14 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
|
|
|
13637
13908
|
}
|
|
13638
13909
|
});
|
|
13639
13910
|
};
|
|
13911
|
+
const requireGitHubAppClient = async () => {
|
|
13912
|
+
if (!runtimeGitHubClient?.authMode) return { ok: false, error: "github_client_unavailable" };
|
|
13913
|
+
const auth = await runtimeGitHubClient.authMode();
|
|
13914
|
+
if (auth.mode !== "github_app") {
|
|
13915
|
+
return { ok: false, error: "github_app_auth_required", auth };
|
|
13916
|
+
}
|
|
13917
|
+
return { ok: true, auth };
|
|
13918
|
+
};
|
|
13640
13919
|
const tools = {
|
|
13641
13920
|
optima_init: tool({
|
|
13642
13921
|
description: "Initialize the Optima workflow and CodeMap in the current repository",
|
|
@@ -13890,6 +14169,152 @@ Restart or reload OpenCode manually if the newly scaffolded config or agents are
|
|
|
13890
14169
|
return JSON.stringify(result, null, 2);
|
|
13891
14170
|
}
|
|
13892
14171
|
}),
|
|
14172
|
+
optima_github_auth_mode: tool({
|
|
14173
|
+
description: "Report whether Optima GitHub operations use anonymous, user token, or GitHub App installation authentication",
|
|
14174
|
+
args: {},
|
|
14175
|
+
async execute() {
|
|
14176
|
+
try {
|
|
14177
|
+
if (!runtimeGitHubClient?.authMode) return JSON.stringify({ ok: false, error: "github_client_unavailable" }, null, 2);
|
|
14178
|
+
return JSON.stringify({ ok: true, ...await runtimeGitHubClient.authMode() }, null, 2);
|
|
14179
|
+
} catch (error) {
|
|
14180
|
+
return JSON.stringify({ ok: false, error: error.message }, null, 2);
|
|
14181
|
+
}
|
|
14182
|
+
}
|
|
14183
|
+
}),
|
|
14184
|
+
optima_github_create_pr: tool({
|
|
14185
|
+
description: "Create a GitHub PR through the Optima GitHub App identity so human owners can approve it",
|
|
14186
|
+
args: {
|
|
14187
|
+
title: tool.schema.string().describe("Pull request title"),
|
|
14188
|
+
head: tool.schema.string().describe("Source branch"),
|
|
14189
|
+
base: tool.schema.string().describe("Target branch"),
|
|
14190
|
+
body: tool.schema.string().describe("Pull request body"),
|
|
14191
|
+
draft: tool.schema.string().describe("Set to 'true' to create a draft PR")
|
|
14192
|
+
},
|
|
14193
|
+
async execute(args) {
|
|
14194
|
+
try {
|
|
14195
|
+
const auth = await requireGitHubAppClient();
|
|
14196
|
+
if (!auth.ok) return JSON.stringify(auth, null, 2);
|
|
14197
|
+
if (!runtimeGitHubClient?.createPullRequest) return JSON.stringify({ ok: false, error: "github_client_unavailable" }, null, 2);
|
|
14198
|
+
const result = await runtimeGitHubClient.createPullRequest({
|
|
14199
|
+
title: args.title,
|
|
14200
|
+
head: args.head,
|
|
14201
|
+
base: args.base,
|
|
14202
|
+
body: args.body,
|
|
14203
|
+
draft: String(args.draft || "").toLowerCase() === "true"
|
|
14204
|
+
});
|
|
14205
|
+
return JSON.stringify({ ok: true, pull_request: result }, null, 2);
|
|
14206
|
+
} catch (error) {
|
|
14207
|
+
return JSON.stringify({ ok: false, error: error.message }, null, 2);
|
|
14208
|
+
}
|
|
14209
|
+
}
|
|
14210
|
+
}),
|
|
14211
|
+
optima_github_comment_pr: tool({
|
|
14212
|
+
description: "Post a PR conversation comment through the Optima GitHub App identity",
|
|
14213
|
+
args: {
|
|
14214
|
+
pr_number: tool.schema.number().describe("Pull request number"),
|
|
14215
|
+
body: tool.schema.string().describe("Markdown comment body")
|
|
14216
|
+
},
|
|
14217
|
+
async execute(args) {
|
|
14218
|
+
try {
|
|
14219
|
+
const auth = await requireGitHubAppClient();
|
|
14220
|
+
if (!auth.ok) return JSON.stringify(auth, null, 2);
|
|
14221
|
+
if (!runtimeGitHubClient?.createIssueComment) return JSON.stringify({ ok: false, error: "github_client_unavailable" }, null, 2);
|
|
14222
|
+
const result = await runtimeGitHubClient.createIssueComment({ issueNumber: args.pr_number, body: args.body });
|
|
14223
|
+
return JSON.stringify({ ok: true, comment: result }, null, 2);
|
|
14224
|
+
} catch (error) {
|
|
14225
|
+
return JSON.stringify({ ok: false, error: error.message }, null, 2);
|
|
14226
|
+
}
|
|
14227
|
+
}
|
|
14228
|
+
}),
|
|
14229
|
+
optima_github_reply_review_comment: tool({
|
|
14230
|
+
description: "Reply to a GitHub PR review comment through the Optima GitHub App identity",
|
|
14231
|
+
args: {
|
|
14232
|
+
comment_id: tool.schema.number().describe("GitHub review comment id to reply to"),
|
|
14233
|
+
body: tool.schema.string().describe("Markdown reply body")
|
|
14234
|
+
},
|
|
14235
|
+
async execute(args) {
|
|
14236
|
+
try {
|
|
14237
|
+
const auth = await requireGitHubAppClient();
|
|
14238
|
+
if (!auth.ok) return JSON.stringify(auth, null, 2);
|
|
14239
|
+
if (!runtimeGitHubClient?.replyToReviewComment) return JSON.stringify({ ok: false, error: "github_client_unavailable" }, null, 2);
|
|
14240
|
+
const result = await runtimeGitHubClient.replyToReviewComment({ commentId: args.comment_id, body: args.body });
|
|
14241
|
+
return JSON.stringify({ ok: true, comment: result }, null, 2);
|
|
14242
|
+
} catch (error) {
|
|
14243
|
+
return JSON.stringify({ ok: false, error: error.message }, null, 2);
|
|
14244
|
+
}
|
|
14245
|
+
}
|
|
14246
|
+
}),
|
|
14247
|
+
optima_github_review_pr: tool({
|
|
14248
|
+
description: "Create a GitHub PR review through the Optima GitHub App identity",
|
|
14249
|
+
args: {
|
|
14250
|
+
pr_number: tool.schema.number().describe("Pull request number"),
|
|
14251
|
+
body: tool.schema.string().describe("Markdown review body"),
|
|
14252
|
+
event: tool.schema.string().describe("Review event: COMMENT, APPROVE, or REQUEST_CHANGES")
|
|
14253
|
+
},
|
|
14254
|
+
async execute(args) {
|
|
14255
|
+
try {
|
|
14256
|
+
const auth = await requireGitHubAppClient();
|
|
14257
|
+
if (!auth.ok) return JSON.stringify(auth, null, 2);
|
|
14258
|
+
if (!runtimeGitHubClient?.createPullRequestReview) return JSON.stringify({ ok: false, error: "github_client_unavailable" }, null, 2);
|
|
14259
|
+
const result = await runtimeGitHubClient.createPullRequestReview({ pullNumber: args.pr_number, body: args.body, event: args.event || "COMMENT" });
|
|
14260
|
+
return JSON.stringify({ ok: true, review: result }, null, 2);
|
|
14261
|
+
} catch (error) {
|
|
14262
|
+
return JSON.stringify({ ok: false, error: error.message }, null, 2);
|
|
14263
|
+
}
|
|
14264
|
+
}
|
|
14265
|
+
}),
|
|
14266
|
+
optima_github_merge_pr: tool({
|
|
14267
|
+
description: "Merge a GitHub PR through the Optima GitHub App identity after required human approval gates pass",
|
|
14268
|
+
args: {
|
|
14269
|
+
pr_number: tool.schema.number().describe("Pull request number"),
|
|
14270
|
+
commit_title: tool.schema.string().describe("Optional merge commit title"),
|
|
14271
|
+
commit_message: tool.schema.string().describe("Optional merge commit message"),
|
|
14272
|
+
merge_method: tool.schema.string().describe("merge, squash, or rebase; defaults to squash")
|
|
14273
|
+
},
|
|
14274
|
+
async execute(args) {
|
|
14275
|
+
try {
|
|
14276
|
+
const auth = await requireGitHubAppClient();
|
|
14277
|
+
if (!auth.ok) return JSON.stringify(auth, null, 2);
|
|
14278
|
+
if (!runtimeGitHubClient?.mergePullRequest) return JSON.stringify({ ok: false, error: "github_client_unavailable" }, null, 2);
|
|
14279
|
+
const result = await runtimeGitHubClient.mergePullRequest({
|
|
14280
|
+
pullNumber: args.pr_number,
|
|
14281
|
+
commitTitle: args.commit_title,
|
|
14282
|
+
commitMessage: args.commit_message,
|
|
14283
|
+
mergeMethod: args.merge_method || "squash"
|
|
14284
|
+
});
|
|
14285
|
+
return JSON.stringify({ ok: true, merge: result }, null, 2);
|
|
14286
|
+
} catch (error) {
|
|
14287
|
+
return JSON.stringify({ ok: false, error: error.message }, null, 2);
|
|
14288
|
+
}
|
|
14289
|
+
}
|
|
14290
|
+
}),
|
|
14291
|
+
optima_github_commit_worktree: tool({
|
|
14292
|
+
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",
|
|
14293
|
+
args: {
|
|
14294
|
+
directory: tool.schema.string().describe("Task worktree directory containing the file changes"),
|
|
14295
|
+
branch: tool.schema.string().describe("Target branch to update; defaults to the current local branch"),
|
|
14296
|
+
message: tool.schema.string().describe("Commit message"),
|
|
14297
|
+
sync_local: tool.schema.string().describe("Set to 'true' to fetch/reset the local worktree to the API-created commit after success")
|
|
14298
|
+
},
|
|
14299
|
+
async execute(args, context) {
|
|
14300
|
+
try {
|
|
14301
|
+
const auth = await requireGitHubAppClient();
|
|
14302
|
+
if (!auth.ok) return JSON.stringify(auth, null, 2);
|
|
14303
|
+
if (!runtimeGitHubClient?.commitWorktree) return JSON.stringify({ ok: false, error: "github_client_unavailable" }, null, 2);
|
|
14304
|
+
const directory = resolveSessionToolDirectory({ requestedDirectory: args.directory, context, pluginWorktree: worktree, clickUpWebhookValidation });
|
|
14305
|
+
if (!directory.ok) return JSON.stringify({ ok: false, error: directory.error }, null, 2);
|
|
14306
|
+
const result = await runtimeGitHubClient.commitWorktree({
|
|
14307
|
+
worktree: directory.directory,
|
|
14308
|
+
branch: args.branch,
|
|
14309
|
+
message: args.message,
|
|
14310
|
+
syncLocal: String(args.sync_local || "").toLowerCase() === "true"
|
|
14311
|
+
});
|
|
14312
|
+
return JSON.stringify(result, null, 2);
|
|
14313
|
+
} catch (error) {
|
|
14314
|
+
return JSON.stringify({ ok: false, error: error.message }, null, 2);
|
|
14315
|
+
}
|
|
14316
|
+
}
|
|
14317
|
+
}),
|
|
13893
14318
|
optima_validate: tool({
|
|
13894
14319
|
description: "Validate Optima workflow artifacts and CodeMap integrity",
|
|
13895
14320
|
args: {},
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
- Human approval assignment is prohibited except for the strict allowlist: parent `plan` with clear questions already posted in ClickUp comments; `in progress` blocked by missing credentials, permissions, external tools, or access; or parent `validation` with a functional preview URL such as `https://<taskid>-preview.defend.tech`. Do not assign `CTO`/`PO` for generic handoff, routine validation, cleanup, subtask planning/validation, or partial-phase stops.
|
|
27
27
|
- Store ClickUp `agent_metadata` JSON with session IDs per agent/type/task/subtask; keep `Definition` as the plan contract and final Documentation as delivered behavior docs.
|
|
28
28
|
- Validation is not complete until the model leaves the current GitHub PR link visible in ClickUp with source branch, target branch, and validation owner; Optima runtime must still limit its own ClickUp writes to metadata.
|
|
29
|
+
- PR creation, PR comments, reviews, merges, and commits must use the Optima GitHub App/API identity, not a human token. Final commits for ClickUp/GitHub delivery are created with `optima_github_commit_worktree`; human approval must not be requested until GitHub reports those commits as Verified.
|
|
29
30
|
- `workflow_product_manager` is registered only when explicit ClickUp webhook mode is configured and the local webhook subscription state is active/valid.
|
|
30
31
|
- Webhook mode is opt-in: Optima validates signed `X-Signature` HMAC SHA-256 ClickUp requests, routes status/assignee events only for Product Manager-assigned non-terminal tasks, routes comments only when they mention `@Defend Tech Product Manager`, and stores new `ses_...` ids in ClickUp `agent_metadata`. Runtime/process failures stay in local logs and must not create ClickUp comments or tags.
|
|
31
32
|
- The same gated in-process listener can also accept signed GitHub `X-Hub-Signature-256` PR/review/comment events at `/optima/github/webhook` when `clickup.github.webhook` is enabled. GitHub events resolve the ClickUp task from the PR source branch, update only `agent_metadata.task.github`, and steer the existing `workflow_product_manager` session; they do not create replacement sessions or ClickUp runtime comments.
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
- Git rules: principal workspace stays on `dev`, never `main`; parent branches use `<type>/<parent-id>`; subtask branches use non-nested `<type>/<parent-id>-subtask-<subtask-id>` and pending subtasks use `<type>/<parent-id>-pending-<title-slug>`; parent task pulls remote once at start; subtasks start from and PR to the parent local branch, bootstrapping the parent from `dev`/`origin/dev` first when missing; PoC branches stay `poc/<clickup-task-id>`; parents PR to `dev`, releases PR `dev` -> `main`; failed/conflicted subtask or parent merges return the affected item to `in progress` for the coding owner; no direct `main` pushes.
|
|
20
20
|
- Store `agent_metadata` session JSON; `Definition` is the plan contract, final Documentation is delivered behavior docs.
|
|
21
21
|
- Validation requires a model-owned ClickUp status comment with the current GitHub PR link, source branch, target branch, and validation owner; Optima runtime itself writes only metadata/logs.
|
|
22
|
+
- PRs, GitHub comments/reviews/merges, and final commits use Optima GitHub App/API identity; create final commits with `optima_github_commit_worktree` and require GitHub `Verified` before human approval.
|
|
22
23
|
- `workflow_product_manager` is registered only when opt-in ClickUp webhook mode is complete and active/valid.
|
|
23
24
|
- Webhook mode validates ClickUp `X-Signature` HMAC SHA-256, routes only PM-assigned non-terminal status/assignee events, routes comments only on `@Defend Tech Product Manager`, writes new `ses_...` ids to `agent_metadata`, and keeps runtime/process failures in local logs, never ClickUp comments/tags.
|
|
24
25
|
- The listener is gated in-process local runtime; production needs a stable public URL/tunnel and ignored `.optima/.config/runtime/` webhook state. It can also accept GitHub `X-Hub-Signature-256` PR/review/comment webhooks at `/optima/github/webhook`; these update only `agent_metadata.task.github` and steer the existing WPM session resolved from the PR source branch.
|
package/docs/guides/AGENTS.md
CHANGED
|
@@ -68,6 +68,7 @@ For ClickUp-first delivery, Validation is a GitHub PR state, not a comment-only
|
|
|
68
68
|
|
|
69
69
|
- Subtasks open/update a PR from the subtask branch into the parent task branch before entering Validation.
|
|
70
70
|
- Parent tasks open/update a PR from the task branch into `dev` before entering Validation.
|
|
71
|
+
- Final commits must be created with `optima_github_commit_worktree`, not local `git commit`, so GitHub attributes them to the Optima App/bot and can mark them Verified. If GitHub does not show the commits as Verified, do not request human approval.
|
|
71
72
|
- The model must leave the current PR link visible in ClickUp when it moves a task/subtask to Validation, including source/target branch and validation owner. Optima runtime still writes only metadata/logs; the ClickUp comment is model-owned work status.
|
|
72
73
|
- GitHub review/comment webhooks wake the workflow owner. The agent replies in GitHub; if a comment requires a change, it moves ClickUp back to `in progress`, fixes/pushes the same branch, returns ClickUp to `validation`, updates the PR, and replies again with the result.
|
|
73
74
|
- The configured final approver/CTO approving the parent PR is the merge trigger. After merge to `dev`, Vercel preproduction must deploy automatically and pass a small smoke/regression check before cleanup and ClickUp `completed`.
|