@defend-tech/opencode-optima 0.1.75 → 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/tech_lead.md +2 -0
- package/assets/agents/workflow_product_manager.md +4 -3
- package/dist/index.js +212 -4
- package/dist/sanitize_cli.js +212 -4
- package/docs/core/agent_orchestration.md +3 -3
- package/docs/core/agent_orchestration.prompt.md +2 -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 +1 -0
- package/package.json +1 -1
|
@@ -7,6 +7,7 @@ tools:
|
|
|
7
7
|
optima_stop_discussion: true
|
|
8
8
|
optima_github_auth_mode: true
|
|
9
9
|
optima_github_commit_worktree: true
|
|
10
|
+
optima_github_verify_vercel_pr: true
|
|
10
11
|
---
|
|
11
12
|
You are the Tech Lead Agent: own technical quality, behavioral verification, technical guidance, and direct-path commit authority.
|
|
12
13
|
|
|
@@ -23,6 +24,7 @@ You are the Tech Lead Agent: own technical quality, behavioral verification, tec
|
|
|
23
24
|
- Review feasibility, scope, architecture fit, risk, and task complexity before implementation proceeds.
|
|
24
25
|
- In mini/direct paths, implement when assigned; in full mode, guide specialists instead of absorbing all work by default.
|
|
25
26
|
- For ClickUp/GitHub delivery, do not create local `git commit` commits. Use `optima_github_commit_worktree` so commits are created by the Optima GitHub App/bot and verify that the returned commit verification is `verified: true` before requesting approval or closure.
|
|
27
|
+
- For parent PR validation, call `optima_github_verify_vercel_pr` before approving technical handoff. A failing Vercel status, missing deployment, or non-functional deployment URL is implementation work to fix, not a CTO/PO handoff.
|
|
26
28
|
- Verify behavior against user stories and ACs through code inspection, builds, tests, and `optima_validate` where relevant.
|
|
27
29
|
- Review code quality, maintainability, architecture adherence, performance, security, and test coverage.
|
|
28
30
|
- Confirm technical/feature documentation closure before any final commit.
|
|
@@ -12,6 +12,7 @@ tools:
|
|
|
12
12
|
optima_github_comment_pr: true
|
|
13
13
|
optima_github_reply_review_comment: true
|
|
14
14
|
optima_github_review_pr: true
|
|
15
|
+
optima_github_verify_vercel_pr: true
|
|
15
16
|
optima_github_merge_pr: true
|
|
16
17
|
optima_github_commit_worktree: true
|
|
17
18
|
---
|
|
@@ -49,11 +50,11 @@ You are Workflow_Product_Manager, Optima's ClickUp-first delivery orchestrator.
|
|
|
49
50
|
## Status Actions
|
|
50
51
|
|
|
51
52
|
- Human registry: resolve `CTO` and `PO` from `docs/core/humans.md` when present; if missing in the task worktree, use the Optima-provided Human Role Fallback Registry and configured ClickUp IDs instead of blocking solely on the missing repo-local file.
|
|
52
|
-
- Human approval allowlist: never assign `CTO`/`PO` except for parent `plan` questions with clear ClickUp comments, real `in progress` blockers from missing credentials/permissions/tools/access, or parent `validation` with a functional
|
|
53
|
+
- Human approval allowlist: never assign `CTO`/`PO` except for parent `plan` questions with clear ClickUp comments, real `in progress` blockers from missing credentials/permissions/tools/access, or parent `validation` after `optima_github_verify_vercel_pr` returns `ready: true` with a functional Vercel URL. Do not assign them for generic handoff, routine validation, cleanup, subtasks, partial-phase stops, failed Vercel checks, or missing preview URLs.
|
|
53
54
|
- `backlog`: ignore until prioritized.
|
|
54
55
|
- `plan`: clarify AC/SCR/test strategy with Validator/QA; decompose; create/update Definition; estimate Story Points; remove PM assignee first; assign the next delivery owner. Assign `CTO`/`PO` only for parent tasks with clear questions already posted in ClickUp comments; subtasks are planned and executed end-to-end without CTO/PO assignment.
|
|
55
56
|
- `in progress`: execute through the assigned delivery agent or workflow runner. Treat blockers as work to solve first: spawn or resume Coder for code/build/dependency failures, QA for validation/test/evidence failures, Tech Lead for architecture/review/merge failures, and the relevant specialist for domain blockers. Escalate to `CTO`/`PO` only when genuinely blocked by missing credentials, permissions, external tools, or access after local/subagent resolution attempts; do not stop with phase language such as "I reached phase 1" or "no non-human assignee is available".
|
|
56
|
-
- `validation`: before moving any task/subtask into Validation, create or update the required GitHub PR through `optima_github_create_pr`, store its URL/number in ClickUp `agent_metadata`, and leave a concise model-owned ClickUp status comment with the PR link, source branch, target branch, and current validation owner. Subtasks PR from their subtask branch into the parent task branch. Parent tasks PR from the task branch into `dev`. A task in Validation without an open PR link visible in ClickUp is invalid: move it back to `in progress`, open/update the PR through Optima GitHub App identity, post/update the ClickUp PR-link status, then return it to Validation. Route Tech Lead for architecture/code/PR/standards/repo-skill review and Validator/QA for tests, Playwright/regression/coverage/evidence/final-doc checks.
|
|
57
|
+
- `validation`: before moving any task/subtask into Validation, create or update the required GitHub PR through `optima_github_create_pr`, store its URL/number in ClickUp `agent_metadata`, and leave a concise model-owned ClickUp status comment with the PR link, source branch, target branch, and current validation owner. Subtasks PR from their subtask branch into the parent task branch. Parent tasks PR from the task branch into `dev`. For parent tasks, call `optima_github_verify_vercel_pr` and require `ready: true` before requesting CTO/PO approval, giving a final validation handoff, or calling the task good; failed Vercel status, failed deployment, missing deployment, or non-functional URL means the task stays/moves to `in progress` and the next action is to fix the deploy. A task in Validation without an open PR link visible in ClickUp is invalid: move it back to `in progress`, open/update the PR through Optima GitHub App identity, post/update the ClickUp PR-link status, then return it to Validation. Route Tech Lead for architecture/code/PR/standards/repo-skill review and Validator/QA for tests, Playwright/regression/coverage/evidence/final-doc checks.
|
|
57
58
|
- GitHub review wakeups: keep listening for PR review/comment webhooks. Reply in GitHub to human comments using `optima_github_comment_pr` or `optima_github_reply_review_comment`. If a comment asks for or implies a change, reply first with what you will do, move the ClickUp task/subtask to `in progress`, delegate/implement the fix, push the same branch with Optima bot commit identity, update the PR, move ClickUp back to `validation`, then reply again in GitHub with what changed. Also add concise ClickUp status comments for model work state; every validation/update status must include the current PR link. Never post Optima runtime/process noise.
|
|
58
59
|
- `merge`: parent-only post-approval automation after the configured final approver/CTO approves the GitHub PR. The GitHub accepted/approved review is the merge trigger. Merge the parent PR into `dev`, verify the Vercel preproduction deployment updates automatically, run a small smoke/regression against preproduction, and only then clean workspaces/worktrees/branches and move ClickUp to `completed`. If merge, Vercel deployment, or regression fails, create Bug subtasks under the parent task, move the parent back to `in progress`, and keep the evidence/PR links in ClickUp.
|
|
59
60
|
- `completed` / `Closed`: no execution unless explicitly reopened.
|
|
@@ -70,7 +71,7 @@ You are Workflow_Product_Manager, Optima's ClickUp-first delivery orchestrator.
|
|
|
70
71
|
- Parent setup pulls remote once; after parent branch creation, subtasks can trust the parent local branch without continuous remote polling.
|
|
71
72
|
- Branches: parent `<clickup-task-type>/<parent-task-id>`; subtask `<clickup-task-type>/<parent-task-id>-subtask-<subtask-id>`; pending planned subtasks `<clickup-task-type>/<parent-task-id>-pending-<title-slug>`; PoC always `poc/<clickup-task-id>` and remains there unless productized later.
|
|
72
73
|
- PR targets/start points: subtask -> parent branch and starts from the parent branch; if parent branch/worktree is missing, bootstrap the parent from `dev`/`origin/dev` first. Parent task -> `dev` through a GitHub PR before entering Validation. Release -> `dev` to `main` only after explicit approval.
|
|
73
|
-
- Required PR gate: every transition into `validation` must have an open GitHub PR and a ClickUp-visible model status comment containing that PR link. Do not mark Validation with only local commits, evidence,
|
|
74
|
+
- Required PR gate: every transition into `validation` must have an open GitHub PR and a ClickUp-visible model status comment containing that PR link. Parent task validation also requires `optima_github_verify_vercel_pr` to return `ready: true` with the Vercel preproduction/preview deployment URL responding successfully before human approval or CTO/PO handoff. Do not mark Validation with only local commits, evidence, comments without the PR URL, or a failing/pending Vercel deploy.
|
|
74
75
|
- Identity gate: PRs/comments/reviews/merges must be authored by the Optima GitHub App/bot identity, not the human operator. Commits must be created through `optima_github_commit_worktree` using the GitHub App API; if the returned commit verification is not `verified: true` or GitHub does not show commits as Verified, do not request human approval or complete the task.
|
|
75
76
|
- Review/merge cleanup: after the configured final approver/CTO approves and the PR is merged, keep the OpenCode session ids in ClickUp `agent_metadata`; delete only the merged branch/worktree after Vercel preproduction and smoke regression pass. If the task is reopened later, recreate/register the worktree from metadata/branch context and resume the preserved sessions.
|
|
76
77
|
- Final completion gate: parent task completion requires merged PR, Vercel preproduction deployment verified, smoke/regression evidence captured, no open review-change requests, final ClickUp comment with result, and status `completed`.
|
package/dist/index.js
CHANGED
|
@@ -10296,10 +10296,88 @@ function treeEntryForWorktreePath(worktree, gitPath) {
|
|
|
10296
10296
|
encoding: "base64"
|
|
10297
10297
|
};
|
|
10298
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
|
+
}
|
|
10299
10375
|
function createGitHubApiClient(config = {}, fetchImpl = globalThis.fetch) {
|
|
10300
10376
|
const staticToken = resolveSecretReference(config?.apiToken);
|
|
10301
10377
|
const appConfig = isPlainObject(config?.app) ? config.app : {};
|
|
10302
|
-
const
|
|
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));
|
|
10303
10381
|
const privateKey = resolveGitHubAppPrivateKey(appConfig);
|
|
10304
10382
|
const owner = String(config?.owner || "").trim();
|
|
10305
10383
|
const repo = String(config?.repo || "").trim();
|
|
@@ -10324,8 +10402,8 @@ function createGitHubApiClient(config = {}, fetchImpl = globalThis.fetch) {
|
|
|
10324
10402
|
if (!appEnabled) return staticToken || "";
|
|
10325
10403
|
const nowMs = Date.now();
|
|
10326
10404
|
if (installationToken && installationTokenExpiresAt - 6e4 > nowMs) return installationToken;
|
|
10327
|
-
const jwt = createGitHubAppJwt({ appId
|
|
10328
|
-
const installation = await requestJson(`https://api.github.com/app/installations/${encodeURIComponent(
|
|
10405
|
+
const jwt = createGitHubAppJwt({ appId, privateKey });
|
|
10406
|
+
const installation = await requestJson(`https://api.github.com/app/installations/${encodeURIComponent(installationId)}/access_tokens`, {
|
|
10329
10407
|
method: "POST",
|
|
10330
10408
|
headers: { Authorization: `Bearer ${jwt}` }
|
|
10331
10409
|
});
|
|
@@ -10383,6 +10461,111 @@ function createGitHubApiClient(config = {}, fetchImpl = globalThis.fetch) {
|
|
|
10383
10461
|
if (commitMessage) body.commit_message = String(commitMessage);
|
|
10384
10462
|
return request(`/pulls/${encodeURIComponent(pullNumber)}/merge`, { method: "PUT", body });
|
|
10385
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
|
+
},
|
|
10386
10569
|
async commitWorktree({ worktree, branch = "", message = "", runGitFn = runGit, syncLocal = false } = {}) {
|
|
10387
10570
|
const directory = path6.resolve(String(worktree || ""));
|
|
10388
10571
|
if (!directory || !fs5.existsSync(directory)) throw new Error("worktree does not exist");
|
|
@@ -10432,7 +10615,7 @@ function createGitHubApiClient(config = {}, fetchImpl = globalThis.fetch) {
|
|
|
10432
10615
|
};
|
|
10433
10616
|
},
|
|
10434
10617
|
async authMode() {
|
|
10435
|
-
if (appEnabled) return { mode: "github_app", appId
|
|
10618
|
+
if (appEnabled) return { mode: "github_app", appId, installationId };
|
|
10436
10619
|
if (staticToken) return { mode: "token" };
|
|
10437
10620
|
return { mode: "anonymous" };
|
|
10438
10621
|
}
|
|
@@ -14256,6 +14439,31 @@ Restart or reload OpenCode manually if the newly scaffolded config or agents are
|
|
|
14256
14439
|
}
|
|
14257
14440
|
}
|
|
14258
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
|
+
}),
|
|
14259
14467
|
optima_github_merge_pr: tool({
|
|
14260
14468
|
description: "Merge a GitHub PR through the Optima GitHub App identity after required human approval gates pass",
|
|
14261
14469
|
args: {
|
package/dist/sanitize_cli.js
CHANGED
|
@@ -10303,10 +10303,88 @@ function treeEntryForWorktreePath(worktree, gitPath) {
|
|
|
10303
10303
|
encoding: "base64"
|
|
10304
10304
|
};
|
|
10305
10305
|
}
|
|
10306
|
+
function appendGitHubQuery(pathname, params = {}) {
|
|
10307
|
+
const query = new URLSearchParams();
|
|
10308
|
+
for (const [key, value] of Object.entries(params || {})) {
|
|
10309
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
10310
|
+
query.set(key, String(value));
|
|
10311
|
+
}
|
|
10312
|
+
const suffix = query.toString();
|
|
10313
|
+
return suffix ? `${pathname}?${suffix}` : pathname;
|
|
10314
|
+
}
|
|
10315
|
+
function timestampMs(value = "") {
|
|
10316
|
+
const parsed = Date.parse(String(value || ""));
|
|
10317
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
10318
|
+
}
|
|
10319
|
+
function sortNewestFirst(items = []) {
|
|
10320
|
+
return [...items].sort((a, b) => {
|
|
10321
|
+
const bTime = Math.max(timestampMs(b?.updated_at), timestampMs(b?.created_at));
|
|
10322
|
+
const aTime = Math.max(timestampMs(a?.updated_at), timestampMs(a?.created_at));
|
|
10323
|
+
return bTime - aTime;
|
|
10324
|
+
});
|
|
10325
|
+
}
|
|
10326
|
+
function matchesVercelStatusContext(context = "", expected = "") {
|
|
10327
|
+
const actual = String(context || "").trim();
|
|
10328
|
+
const wanted = String(expected || "").trim();
|
|
10329
|
+
if (wanted && actual === wanted) return true;
|
|
10330
|
+
const lower = actual.toLowerCase();
|
|
10331
|
+
if (!lower.includes("vercel")) return false;
|
|
10332
|
+
if (!wanted) return lower.includes("preproduction") || lower.includes("defend-preproduction");
|
|
10333
|
+
const wantedLower = wanted.toLowerCase();
|
|
10334
|
+
return wantedLower.split(/\s+|–|-/).filter((part) => part && part !== "vercel").every((part) => lower.includes(part));
|
|
10335
|
+
}
|
|
10336
|
+
function selectFunctionalDeploymentUrl(status = {}, deployment = {}) {
|
|
10337
|
+
for (const candidate of [
|
|
10338
|
+
status?.environment_url,
|
|
10339
|
+
deployment?.environment_url,
|
|
10340
|
+
status?.target_url,
|
|
10341
|
+
deployment?.target_url
|
|
10342
|
+
]) {
|
|
10343
|
+
const value = String(candidate || "").trim();
|
|
10344
|
+
if (!value) continue;
|
|
10345
|
+
try {
|
|
10346
|
+
const url = new URL(value);
|
|
10347
|
+
if (url.protocol === "http:" || url.protocol === "https:") return value;
|
|
10348
|
+
} catch {
|
|
10349
|
+
}
|
|
10350
|
+
}
|
|
10351
|
+
return "";
|
|
10352
|
+
}
|
|
10353
|
+
async function checkFunctionalDeploymentUrl(url, fetchImpl = globalThis.fetch) {
|
|
10354
|
+
const target = String(url || "").trim();
|
|
10355
|
+
if (!target) return { ok: false, reason: "url_missing" };
|
|
10356
|
+
if (typeof fetchImpl !== "function") return { ok: false, reason: "fetch_unavailable", url: target };
|
|
10357
|
+
const signal = typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function" ? AbortSignal.timeout(8e3) : void 0;
|
|
10358
|
+
const probe = async (method) => {
|
|
10359
|
+
const response = await fetchImpl(target, { method, redirect: "follow", signal });
|
|
10360
|
+
return {
|
|
10361
|
+
ok: response.status >= 200 && response.status < 400,
|
|
10362
|
+
status: response.status,
|
|
10363
|
+
statusText: response.statusText || "",
|
|
10364
|
+
url: response.url || target,
|
|
10365
|
+
method
|
|
10366
|
+
};
|
|
10367
|
+
};
|
|
10368
|
+
try {
|
|
10369
|
+
const head = await probe("HEAD");
|
|
10370
|
+
if (head.ok) return head;
|
|
10371
|
+
const get = await probe("GET");
|
|
10372
|
+
return get.ok ? get : { ...get, reason: "http_not_ok" };
|
|
10373
|
+
} catch (error) {
|
|
10374
|
+
try {
|
|
10375
|
+
const get = await probe("GET");
|
|
10376
|
+
return get.ok ? get : { ...get, reason: "http_not_ok" };
|
|
10377
|
+
} catch (secondError) {
|
|
10378
|
+
return { ok: false, reason: "request_failed", url: target, error: secondError.message || error.message };
|
|
10379
|
+
}
|
|
10380
|
+
}
|
|
10381
|
+
}
|
|
10306
10382
|
function createGitHubApiClient(config = {}, fetchImpl = globalThis.fetch) {
|
|
10307
10383
|
const staticToken = resolveSecretReference(config?.apiToken);
|
|
10308
10384
|
const appConfig = isPlainObject(config?.app) ? config.app : {};
|
|
10309
|
-
const
|
|
10385
|
+
const appId = resolveSecretReference(appConfig.appId || appConfig.app_id || "");
|
|
10386
|
+
const installationId = resolveSecretReference(appConfig.installationId || appConfig.installation_id || "");
|
|
10387
|
+
const appEnabled = appConfig.enabled === true || Boolean(appId && installationId && (appConfig.privateKey || appConfig.privateKeyFile));
|
|
10310
10388
|
const privateKey = resolveGitHubAppPrivateKey(appConfig);
|
|
10311
10389
|
const owner = String(config?.owner || "").trim();
|
|
10312
10390
|
const repo = String(config?.repo || "").trim();
|
|
@@ -10331,8 +10409,8 @@ function createGitHubApiClient(config = {}, fetchImpl = globalThis.fetch) {
|
|
|
10331
10409
|
if (!appEnabled) return staticToken || "";
|
|
10332
10410
|
const nowMs = Date.now();
|
|
10333
10411
|
if (installationToken && installationTokenExpiresAt - 6e4 > nowMs) return installationToken;
|
|
10334
|
-
const jwt = createGitHubAppJwt({ appId
|
|
10335
|
-
const installation = await requestJson(`https://api.github.com/app/installations/${encodeURIComponent(
|
|
10412
|
+
const jwt = createGitHubAppJwt({ appId, privateKey });
|
|
10413
|
+
const installation = await requestJson(`https://api.github.com/app/installations/${encodeURIComponent(installationId)}/access_tokens`, {
|
|
10336
10414
|
method: "POST",
|
|
10337
10415
|
headers: { Authorization: `Bearer ${jwt}` }
|
|
10338
10416
|
});
|
|
@@ -10390,6 +10468,111 @@ function createGitHubApiClient(config = {}, fetchImpl = globalThis.fetch) {
|
|
|
10390
10468
|
if (commitMessage) body.commit_message = String(commitMessage);
|
|
10391
10469
|
return request(`/pulls/${encodeURIComponent(pullNumber)}/merge`, { method: "PUT", body });
|
|
10392
10470
|
},
|
|
10471
|
+
async getCombinedStatus(ref) {
|
|
10472
|
+
return request(`/commits/${encodeURIComponent(String(ref || ""))}/status`);
|
|
10473
|
+
},
|
|
10474
|
+
async listDeployments({ sha = "", ref = "", environment = "", perPage = 30 } = {}) {
|
|
10475
|
+
return request(appendGitHubQuery("/deployments", {
|
|
10476
|
+
sha,
|
|
10477
|
+
ref,
|
|
10478
|
+
environment,
|
|
10479
|
+
per_page: perPage
|
|
10480
|
+
}));
|
|
10481
|
+
},
|
|
10482
|
+
async listDeploymentStatuses(deploymentId) {
|
|
10483
|
+
return request(`/deployments/${encodeURIComponent(deploymentId)}/statuses`);
|
|
10484
|
+
},
|
|
10485
|
+
async verifyVercelPullRequestDeployment({
|
|
10486
|
+
pullNumber,
|
|
10487
|
+
context = "Vercel \u2013 defend-preproduction",
|
|
10488
|
+
environment = "Preview \u2013 defend-preproduction",
|
|
10489
|
+
requireFunctionalUrl = true
|
|
10490
|
+
} = {}) {
|
|
10491
|
+
const pr = await this.getPullRequest(pullNumber);
|
|
10492
|
+
const headSha = String(pr?.head?.sha || "").trim();
|
|
10493
|
+
if (!headSha) return { ok: true, ready: false, reason: "pr_head_sha_missing", pull_request: { number: pullNumber } };
|
|
10494
|
+
const combined = await this.getCombinedStatus(headSha);
|
|
10495
|
+
const statuses = Array.isArray(combined?.statuses) ? combined.statuses : [];
|
|
10496
|
+
const matchingStatuses = statuses.filter((status) => matchesVercelStatusContext(status?.context, context));
|
|
10497
|
+
const selectedStatus = sortNewestFirst(matchingStatuses)[0] || null;
|
|
10498
|
+
if (!selectedStatus) {
|
|
10499
|
+
return {
|
|
10500
|
+
ok: true,
|
|
10501
|
+
ready: false,
|
|
10502
|
+
reason: "vercel_status_missing",
|
|
10503
|
+
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
10504
|
+
required_context: context,
|
|
10505
|
+
combined_state: combined?.state || null
|
|
10506
|
+
};
|
|
10507
|
+
}
|
|
10508
|
+
if (String(selectedStatus.state || "").toLowerCase() !== "success") {
|
|
10509
|
+
return {
|
|
10510
|
+
ok: true,
|
|
10511
|
+
ready: false,
|
|
10512
|
+
reason: "vercel_status_not_success",
|
|
10513
|
+
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
10514
|
+
required_context: context,
|
|
10515
|
+
status: selectedStatus,
|
|
10516
|
+
combined_state: combined?.state || null
|
|
10517
|
+
};
|
|
10518
|
+
}
|
|
10519
|
+
let deployments = await this.listDeployments({ sha: headSha, environment });
|
|
10520
|
+
if (!Array.isArray(deployments) || deployments.length === 0) deployments = await this.listDeployments({ sha: headSha });
|
|
10521
|
+
const selectedDeployment = sortNewestFirst(Array.isArray(deployments) ? deployments : []).find((deployment) => !environment || String(deployment?.environment || "") === environment) || sortNewestFirst(Array.isArray(deployments) ? deployments : [])[0] || null;
|
|
10522
|
+
if (!selectedDeployment?.id) {
|
|
10523
|
+
return {
|
|
10524
|
+
ok: true,
|
|
10525
|
+
ready: false,
|
|
10526
|
+
reason: "vercel_deployment_missing",
|
|
10527
|
+
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
10528
|
+
required_environment: environment,
|
|
10529
|
+
status: selectedStatus
|
|
10530
|
+
};
|
|
10531
|
+
}
|
|
10532
|
+
const deploymentStatuses = await this.listDeploymentStatuses(selectedDeployment.id);
|
|
10533
|
+
const selectedDeploymentStatus = sortNewestFirst(Array.isArray(deploymentStatuses) ? deploymentStatuses : [])[0] || null;
|
|
10534
|
+
if (!selectedDeploymentStatus || String(selectedDeploymentStatus.state || "").toLowerCase() !== "success") {
|
|
10535
|
+
return {
|
|
10536
|
+
ok: true,
|
|
10537
|
+
ready: false,
|
|
10538
|
+
reason: "vercel_deployment_not_success",
|
|
10539
|
+
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
10540
|
+
required_environment: environment,
|
|
10541
|
+
status: selectedStatus,
|
|
10542
|
+
deployment: selectedDeployment,
|
|
10543
|
+
deployment_status: selectedDeploymentStatus
|
|
10544
|
+
};
|
|
10545
|
+
}
|
|
10546
|
+
const url = selectFunctionalDeploymentUrl(selectedDeploymentStatus, selectedDeployment);
|
|
10547
|
+
const urlCheck = requireFunctionalUrl ? await checkFunctionalDeploymentUrl(url, fetchImpl) : { ok: Boolean(url), reason: url ? void 0 : "url_missing", url };
|
|
10548
|
+
if (requireFunctionalUrl && !urlCheck.ok) {
|
|
10549
|
+
return {
|
|
10550
|
+
ok: true,
|
|
10551
|
+
ready: false,
|
|
10552
|
+
reason: "vercel_url_not_functional",
|
|
10553
|
+
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
10554
|
+
required_environment: environment,
|
|
10555
|
+
status: selectedStatus,
|
|
10556
|
+
deployment: selectedDeployment,
|
|
10557
|
+
deployment_status: selectedDeploymentStatus,
|
|
10558
|
+
url,
|
|
10559
|
+
url_check: urlCheck
|
|
10560
|
+
};
|
|
10561
|
+
}
|
|
10562
|
+
return {
|
|
10563
|
+
ok: true,
|
|
10564
|
+
ready: true,
|
|
10565
|
+
reason: "vercel_pr_deployment_ready",
|
|
10566
|
+
pull_request: { number: pr.number, url: pr.html_url, head_sha: headSha },
|
|
10567
|
+
required_context: context,
|
|
10568
|
+
required_environment: environment,
|
|
10569
|
+
status: selectedStatus,
|
|
10570
|
+
deployment: selectedDeployment,
|
|
10571
|
+
deployment_status: selectedDeploymentStatus,
|
|
10572
|
+
url,
|
|
10573
|
+
url_check: urlCheck
|
|
10574
|
+
};
|
|
10575
|
+
},
|
|
10393
10576
|
async commitWorktree({ worktree, branch = "", message = "", runGitFn = runGit, syncLocal = false } = {}) {
|
|
10394
10577
|
const directory = path6.resolve(String(worktree || ""));
|
|
10395
10578
|
if (!directory || !fs5.existsSync(directory)) throw new Error("worktree does not exist");
|
|
@@ -10439,7 +10622,7 @@ function createGitHubApiClient(config = {}, fetchImpl = globalThis.fetch) {
|
|
|
10439
10622
|
};
|
|
10440
10623
|
},
|
|
10441
10624
|
async authMode() {
|
|
10442
|
-
if (appEnabled) return { mode: "github_app", appId
|
|
10625
|
+
if (appEnabled) return { mode: "github_app", appId, installationId };
|
|
10443
10626
|
if (staticToken) return { mode: "token" };
|
|
10444
10627
|
return { mode: "anonymous" };
|
|
10445
10628
|
}
|
|
@@ -14263,6 +14446,31 @@ Restart or reload OpenCode manually if the newly scaffolded config or agents are
|
|
|
14263
14446
|
}
|
|
14264
14447
|
}
|
|
14265
14448
|
}),
|
|
14449
|
+
optima_github_verify_vercel_pr: tool({
|
|
14450
|
+
description: "Verify that a PR head commit has a successful Vercel preproduction deployment and a functional deployment URL before validation/handoff",
|
|
14451
|
+
args: {
|
|
14452
|
+
pr_number: tool.schema.number().describe("Pull request number"),
|
|
14453
|
+
context: tool.schema.string().describe("Required GitHub status context; defaults to 'Vercel \u2013 defend-preproduction'"),
|
|
14454
|
+
environment: tool.schema.string().describe("Required GitHub deployment environment; defaults to 'Preview \u2013 defend-preproduction'"),
|
|
14455
|
+
require_functional_url: tool.schema.string().describe("Set to 'false' to skip HTTP probing of the deployment URL")
|
|
14456
|
+
},
|
|
14457
|
+
async execute(args) {
|
|
14458
|
+
try {
|
|
14459
|
+
const auth = await requireGitHubAppClient();
|
|
14460
|
+
if (!auth.ok) return JSON.stringify(auth, null, 2);
|
|
14461
|
+
if (!runtimeGitHubClient?.verifyVercelPullRequestDeployment) return JSON.stringify({ ok: false, error: "github_client_unavailable" }, null, 2);
|
|
14462
|
+
const result = await runtimeGitHubClient.verifyVercelPullRequestDeployment({
|
|
14463
|
+
pullNumber: args.pr_number,
|
|
14464
|
+
context: args.context || "Vercel \u2013 defend-preproduction",
|
|
14465
|
+
environment: args.environment || "Preview \u2013 defend-preproduction",
|
|
14466
|
+
requireFunctionalUrl: String(args.require_functional_url || "true").toLowerCase() !== "false"
|
|
14467
|
+
});
|
|
14468
|
+
return JSON.stringify(result, null, 2);
|
|
14469
|
+
} catch (error) {
|
|
14470
|
+
return JSON.stringify({ ok: false, error: error.message }, null, 2);
|
|
14471
|
+
}
|
|
14472
|
+
}
|
|
14473
|
+
}),
|
|
14266
14474
|
optima_github_merge_pr: tool({
|
|
14267
14475
|
description: "Merge a GitHub PR through the Optima GitHub App identity after required human approval gates pass",
|
|
14268
14476
|
args: {
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
- Supported delivery task types are `Tarea`, `Bug`, `Doc`, and `PoC`; ignore `Idea`, legacy `Backlog` alias, `Hito`, `Nota de reunión`, and `Respuesta del formulario` unless converted or linked to delivery work.
|
|
24
24
|
- Human role registry: resolve `CTO` and `PO` from `docs/core/humans.md` when present; if a task worktree lacks that file, use the Optima-provided human role fallback context and configured ClickUp IDs instead of blocking solely on the missing repo-local file. Use role identifiers in workflow text and automation config.
|
|
25
25
|
- Status actions are deterministic: `backlog` ignore, `plan` plan plus `Story Points`, test strategy, and `Definition`, `in progress` execute, `validation` split Tech Lead and Validator/QA gates, `merge` parent post-approval automation, and `completed`/`Closed` ignore unless reopened.
|
|
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
|
|
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` after `optima_github_verify_vercel_pr` returns `ready: true` for the current PR with a functional Vercel preproduction/preview URL. Do not assign `CTO`/`PO` for generic handoff, routine validation, cleanup, subtask planning/validation, partial-phase stops, failed Vercel checks, or missing preview URLs.
|
|
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
|
-
- Validation is not complete until the model leaves the current GitHub PR link visible in ClickUp with source branch, target branch, and
|
|
28
|
+
- Validation is not complete until the model leaves the current GitHub PR link visible in ClickUp with source branch, target branch, validation owner, and the `optima_github_verify_vercel_pr` result for parent PRs; Optima runtime must still limit its own ClickUp writes to metadata.
|
|
29
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.
|
|
30
30
|
- `workflow_product_manager` is registered only when explicit ClickUp webhook mode is configured and the local webhook subscription state is active/valid.
|
|
31
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.
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
- Parent branch format is `<clickup-task-type>/<parent-task-id>`; subtask branch format is the non-nested sibling ref `<clickup-task-type>/<parent-task-id>-subtask-<subtask-id>`; pending planned subtasks use `<clickup-task-type>/<parent-task-id>-pending-<title-slug>`; PoC branch format is always `poc/<clickup-task-id>` and stays there until a later productization task.
|
|
41
41
|
- Subtask worktrees start from the parent branch and PR to the parent branch; if the parent branch/worktree is missing, bootstrap the parent from `dev`/`origin/dev` first. Parent task PRs target `dev`, and release PRs target `main` from `dev` only after explicit approval.
|
|
42
42
|
- After successful subtask validation, Validator/QA merges the subtask PR into the parent branch/workspace without `CTO`/`PO` assignment or approval.
|
|
43
|
-
- After parent Tech Lead and Validator/QA validation passes, the parent task may assign `CTO`/`PO` only when a functional
|
|
43
|
+
- After parent Tech Lead and Validator/QA validation passes, the parent task may assign `CTO`/`PO` only when `optima_github_verify_vercel_pr` reports the PR deployment ready with a functional URL; after a human comments `Approved`, automation removes human assignees, assigns itself or the merge owner, merges the parent PR into `dev`, cleans workspaces/worktrees/branches, pushes to `dev`, and ensures the dev/preproduction environment contains the code.
|
|
44
44
|
- If any subtask or parent merge conflicts or fails, Validator/QA returns the affected ClickUp item to `in progress` and routes it to the coding owner.
|
|
45
45
|
- Never push directly to `main`.
|
|
46
46
|
- `investigation` and `spec` tasks may run in parallel only when they avoid conflicting delivery artifacts.
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
- `product_manager` may answer/investigate/dashboard/pre-estimate "a qué huele" plus rough story points; development asks become routed ClickUp tasks.
|
|
15
15
|
- ClickUp-first types: execute `Tarea`, `Bug`, `Doc`, `PoC`; ignore `Idea`, legacy `Backlog` alias, `Hito`, `Nota de reunión`, `Respuesta del formulario` unless converted/linked.
|
|
16
16
|
- Human role registry: resolve `CTO` and `PO` from `docs/core/humans.md` when present; if missing in a task worktree, use Optima-provided fallback role context/configured ClickUp IDs instead of blocking solely on the missing file.
|
|
17
|
-
- ClickUp-first statuses: `backlog` ignore, `plan` plan with `Story Points`, `Definition`, and test strategy; assign `CTO`/`PO` only for parent `plan` questions with clear ClickUp comments, real `in progress` blockers from missing credentials/tools/access, or parent `validation` with a functional
|
|
17
|
+
- ClickUp-first statuses: `backlog` ignore, `plan` plan with `Story Points`, `Definition`, and test strategy; assign `CTO`/`PO` only for parent `plan` questions with clear ClickUp comments, real `in progress` blockers from missing credentials/tools/access, or parent `validation` after `optima_github_verify_vercel_pr` returns `ready: true` with a functional Vercel URL. Subtasks merge directly into the parent branch after Validator/QA passes without CTO/PO assignment; parent `Approved` comments trigger automation to remove humans, assign merge owner/self, merge to `dev`, clean workspaces/worktrees/branches, push, and ensure dev/preproduction receives the code; `completed`/`Closed` ignore unless reopened.
|
|
18
18
|
- Shared-worktree rule: one active `implementation` task at a time; isolated `investigation`/`spec` may run in parallel if non-conflicting.
|
|
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
|
-
- Validation requires a model-owned ClickUp status comment with the current GitHub PR link, source branch, target branch,
|
|
21
|
+
- Validation requires a model-owned ClickUp status comment with the current GitHub PR link, source branch, target branch, validation owner, and parent PR Vercel gate result from `optima_github_verify_vercel_pr`; Optima runtime itself writes only metadata/logs.
|
|
22
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.
|
|
23
23
|
- `workflow_product_manager` is registered only when opt-in ClickUp webhook mode is complete and active/valid.
|
|
24
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.
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
- Raw logs stay in evidence storage; ClickUp receives concise summaries, paths/links, or relevant excerpts only, never wholesale raw logs.
|
|
22
22
|
- WPM owns ClickUp `Story Points` during `plan`, re-estimation on material plan changes, `agent_metadata` session JSON, `Definition` plan-contract linking, and parent approval routing after validation.
|
|
23
23
|
- Human role registry: resolve `CTO` and `PO` from `docs/core/humans.md` when present; if a task worktree lacks that file, use Optima-provided fallback role context and configured ClickUp IDs instead of blocking solely on the missing repo-local file. Use role identifiers in workflow text and automation config.
|
|
24
|
-
- Human approval assignment is prohibited except for three cases: parent `plan` with clear ClickUp-commented questions, `in progress` blockers caused by missing credentials/permissions/tools/access, or parent `validation` with a functional preview URL. Do not assign `CTO`/`PO` for generic handoff, routine validation, cleanup, subtask work,
|
|
24
|
+
- Human approval assignment is prohibited except for three cases: parent `plan` with clear ClickUp-commented questions, `in progress` blockers caused by missing credentials/permissions/tools/access, or parent `validation` after `optima_github_verify_vercel_pr` returns `ready: true` with a functional Vercel preproduction/preview URL. Do not assign `CTO`/`PO` for generic handoff, routine validation, cleanup, subtask work, partial-phase stops, failed Vercel checks, or missing preview URLs.
|
|
25
25
|
- Subtask merge authority belongs to Validator/QA after successful subtask validation: subtask PRs target and merge into the parent branch/workspace without `CTO`/`PO` assignment or approval.
|
|
26
|
-
- Parent merge authority is split: after Tech Lead and Validator/QA pass, WPM/Validator may assign `CTO`/`PO` only under the parent-validation allowlist; after a human comments `Approved`, automation removes human assignees, assigns itself or the merge owner, merges to `dev`, cleans workspaces/worktrees/branches, pushes to `dev`, and ensures the dev environment contains the code.
|
|
26
|
+
- Parent merge authority is split: after Tech Lead and Validator/QA pass plus a ready Vercel PR deployment gate, WPM/Validator may assign `CTO`/`PO` only under the parent-validation allowlist; after a human comments `Approved`, automation removes human assignees, assigns itself or the merge owner, merges to `dev`, cleans workspaces/worktrees/branches, pushes to `dev`, and ensures the dev/preproduction environment contains the code.
|
|
27
27
|
- If a subtask or parent merge conflicts or fails, Validator/QA returns the affected ClickUp task/subtask to `in progress` and routes it back to the coding owner.
|
|
28
28
|
- Git authority follows ClickUp-first rules: principal workspace on `dev`, no direct `main` push, parent task pulls remote once at start, subtask PRs to parent branch, parent PRs to `dev`, PoC branches stay `poc/<clickup-task-id>`, release PRs from `dev` to `main` only after approval.
|
|
29
29
|
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
- Keep raw logs in evidence storage; ClickUp receives concise summaries, paths/links, or relevant excerpts only, never wholesale raw logs.
|
|
16
16
|
- WPM owns `Story Points` during `plan`, re-estimation on material plan changes, `agent_metadata`, `Definition` plan-contract linking, and parent approval routing after validation.
|
|
17
17
|
- Human role registry: resolve `CTO` and `PO` from `docs/core/humans.md` when present; if missing in a task worktree, use Optima fallback role context/configured ClickUp IDs instead of blocking solely on the missing file.
|
|
18
|
-
- Human approval assignment is prohibited except for parent `plan` questions with clear ClickUp comments, `in progress` blockers from missing credentials/tools/access, or parent `validation` with a functional
|
|
18
|
+
- Human approval assignment is prohibited except for parent `plan` questions with clear ClickUp comments, `in progress` blockers from missing credentials/tools/access, or parent `validation` after `optima_github_verify_vercel_pr` returns `ready: true` with a functional Vercel URL; never use it for generic handoff, cleanup, subtasks, phase stops, failed Vercel checks, or missing preview URLs.
|
|
19
19
|
- Validator/QA may merge validated subtask PRs into the parent branch/workspace without `CTO`/`PO` assignment or approval.
|
|
20
|
-
- Parent merge authority uses the validation allowlist only: after a human comments `Approved`, automation removes human assignees, assigns merge owner/self, merges to `dev`, cleans workspaces/worktrees/branches, pushes, and ensures dev receives the code.
|
|
20
|
+
- Parent merge authority uses the validation allowlist only after a ready Vercel PR deployment gate: after a human comments `Approved`, automation removes human assignees, assigns merge owner/self, merges to `dev`, cleans workspaces/worktrees/branches, pushes, and ensures dev/preproduction receives the code.
|
|
21
21
|
- Failed or conflicted subtask/parent merges return the affected ClickUp item to `in progress` for the coding owner.
|
|
22
22
|
- ClickUp-first Git rules: principal workspace on `dev`, no direct `main` push, parent pulls remote once at start, subtask PRs to parent branch, parent PRs to `dev`, PoC branches stay `poc/<clickup-task-id>`, release PRs `dev` -> `main` only after approval.
|
|
23
23
|
- BA owns product truth and product-facing feature/domain docs.
|
package/docs/core/task_model.md
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
- Agents must not assign ClickUp tasks to `CTO` or `PO` except for the explicit cases below; generic handoff, routine validation, duplicate-assignee cleanup, or incomplete phase handoff are prohibited.
|
|
21
21
|
- **Parent planning questions:** only a parent task in `plan` may assign `CTO`/`PO`, and only after clear, concrete questions have been posted in ClickUp comments. Subtasks are planned and executed end-to-end without CTO/PO planning assignment.
|
|
22
22
|
- **Real in-progress blocker:** a task in `in progress` may assign/escalate to `CTO`/`PO` only when blocked by missing credentials, permissions, external tools, or access. Agents must not stop at informal phase boundaries such as "I reached phase 1"; phases should have been subtasks, otherwise finish the accepted task.
|
|
23
|
-
- **Parent validation approval:** only a parent task in `validation` may assign `CTO`/`PO` for final validation, and only
|
|
23
|
+
- **Parent validation approval:** only a parent task in `validation` may assign `CTO`/`PO` for final validation, and only after `optima_github_verify_vercel_pr` returns `ready: true` for the current parent PR with a functional Vercel preproduction/preview URL. After a human comments `Approved`, automation reassigns to itself or the merge owner, removes human assignees, merges, cleans workspaces/worktrees/branches, pushes to `dev`, and ensures the dev/preproduction environment contains the code.
|
|
24
24
|
|
|
25
25
|
## Routing Rules
|
|
26
26
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
- ClickUp-first delivery types: `Tarea`, `Bug`, `Doc`, `PoC`; ignored types: `Idea`, legacy `Backlog` alias, `Hito`, `Nota de reunión`, `Respuesta del formulario` unless converted/linked.
|
|
8
8
|
- Product Manager without workflow never develops; it may pre-estimate "a qué huele" small/medium/large plus rough story points and route development into ClickUp tasks.
|
|
9
9
|
- Human role registry: resolve `CTO` and `PO` from `docs/core/humans.md` when present; if missing in a task worktree, use Optima fallback role context/configured ClickUp IDs instead of blocking solely on the missing file.
|
|
10
|
-
- ClickUp-first actions: `backlog` ignore, `plan` plan with `Story Points`, `Definition`, and test strategy; assign `CTO`/`PO` only for parent planning questions with clear ClickUp comments, real `in progress` blockers caused by missing credentials/tools/access, or parent `validation` with a functional
|
|
10
|
+
- ClickUp-first actions: `backlog` ignore, `plan` plan with `Story Points`, `Definition`, and test strategy; assign `CTO`/`PO` only for parent planning questions with clear ClickUp comments, real `in progress` blockers caused by missing credentials/tools/access, or parent `validation` after `optima_github_verify_vercel_pr` returns `ready: true` with a functional Vercel URL. Subtasks execute end-to-end and merge to the parent branch after Validator/QA passes without CTO/PO approval; parent `Approved` comments trigger automation to remove humans, assign merge owner/self, merge to `dev`, clean workspaces/worktrees/branches, push, and ensure dev/preproduction receives the code; `completed`/`Closed` ignore unless reopened.
|
|
11
11
|
- Routing: keep `tiny` to one slice and usually one specialist; keep `standard` bounded; decompose `complex` into slice-based subtasks.
|
|
12
12
|
- `complex + implementation` normally uses `workflow_runner` in full mode.
|
|
13
13
|
- WPM stores `agent_metadata`, re-estimates `Story Points` on material plan changes, and keeps `Definition` plan contract separate from final Documentation.
|
|
@@ -19,7 +19,7 @@ We adhere to a strict test pyramid strategy to ensure 100% reliability.
|
|
|
19
19
|
- **Regression:** A full regression suite must be run by the Developer before handing over for technical review.
|
|
20
20
|
- **Split Validation:** Tech Lead reviews architecture, code, PR readiness, standards, and repo-skill use; Validator/QA verifies tests, Playwright flows, regression, required coverage, evidence, and final documentation freshness.
|
|
21
21
|
- **Human Role Registry:** Resolve `CTO` and `PO` from `docs/core/humans.md` when present; if a task worktree lacks that file, use Optima fallback role context and configured ClickUp IDs instead of blocking solely on the missing repo-local file. Use role identifiers in workflow text and automation config.
|
|
22
|
-
- **Human Approval Allowlist:** Assign `CTO`/`PO` only for parent `plan` questions with clear ClickUp comments, real `in progress` blockers caused by missing credentials/permissions/tools/access, or parent `validation` with a functional
|
|
22
|
+
- **Human Approval Allowlist:** Assign `CTO`/`PO` only for parent `plan` questions with clear ClickUp comments, real `in progress` blockers caused by missing credentials/permissions/tools/access, or parent `validation` after `optima_github_verify_vercel_pr` returns `ready: true` with a functional Vercel URL. Never assign them for generic handoff, routine validation, cleanup, subtasks, partial-phase stops, failed Vercel checks, or missing preview URLs.
|
|
23
23
|
- **Merge Execution Gate:** Validator/QA may merge validated subtask PRs into the parent branch/workspace without human approval. Parent PRs to `dev` require Tech Lead and Validator/QA pass plus the parent-validation allowlist; after a human comments `Approved`, automation removes human assignees, assigns itself or the merge owner, merges, cleans workspaces/worktrees/branches, pushes to `dev`, and ensures the dev environment contains the code. Any conflicted or failed merge returns the affected task/subtask to `in progress` for the coding owner.
|
|
24
24
|
- **Documentation Gate:** Validator/QA must fail validation when final documentation is missing or outdated; `Definition` is only the plan contract, not the delivered documentation.
|
|
25
25
|
- **QA Output Contract:** QA handoffs should state the test strategy used, the results observed, the AC coverage achieved, any documentation impact, open risks, and the recommended next step.
|
package/docs/guides/AGENTS.md
CHANGED
|
@@ -70,6 +70,7 @@ For ClickUp-first delivery, Validation is a GitHub PR state, not a comment-only
|
|
|
70
70
|
- Parent tasks open/update a PR from the task branch into `dev` before entering Validation.
|
|
71
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.
|
|
72
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.
|
|
73
|
+
- Parent PR validation must call `optima_github_verify_vercel_pr`; failed Vercel status, missing deployment, or non-functional URL keeps/returns the task to `in progress` and must be fixed before CTO/PO approval or handoff.
|
|
73
74
|
- 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.
|
|
74
75
|
- 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`.
|
|
75
76
|
- If merge, Vercel deployment, or regression fails, create Bug subtasks under the parent and return the parent to `in progress`.
|