@defend-tech/opencode-optima 0.1.61 → 0.1.62

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/Agents_Common.md CHANGED
@@ -19,7 +19,7 @@
19
19
  - ClickUp delivery types are `Tarea`, `Bug`, `Doc`, `PoC`; ignore `Idea`, legacy `Backlog` alias, `Hito`, `Nota de reunión`, and `Respuesta del formulario` unless converted or linked.
20
20
  - WPM estimates `Story Points` during `plan` and re-estimates on material plan changes.
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-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 code instead of personal names.
22
- - ClickUp status actions: `backlog` ignore, `plan` plan plus remove the PM assignee before assigning `CTO`/`PO` at plan end, `in progress` execute, `validation` Tech Lead + Validator/QA gates. After validation, Validator/QA may merge validated subtasks into the parent branch without `CTO`/`PO` approval; validated parent tasks stay in `validation` for `CTO`/`PO` approval, they approve by moving the parent to `merge`, and Validator/QA then attempts the parent merge into `dev`. `completed`/`Closed` ignore unless reopened.
22
+ - ClickUp status actions: `backlog` ignore, `plan` plan with `Story Points`, `Definition`, and test strategy, `in progress` execute, `validation` Tech Lead + Validator/QA gates, and parent post-approval merge automation. Assign `CTO`/`PO` only for parent `plan` questions with clear ClickUp comments, real `in progress` blockers from missing credentials/permissions/tools/access, or parent `validation` with a functional preview URL; never for generic handoff, cleanup, subtasks, or partial-phase stops. Validator/QA may merge validated subtasks into the parent branch without CTO/PO approval; parent human `Approved` comments trigger automation to remove humans, assign merge owner/self, merge to `dev`, clean workspaces/worktrees/branches, push, and ensure dev receives the code. `completed`/`Closed` ignore unless reopened.
23
23
  - One shared-worktree `implementation` task may be active; ClickUp-first delivery should use task-specific worktrees/branches.
24
24
  - Agent messages must start with `[Agent Message] From: <agent_name> To: <agent_name>`.
25
25
  - Clarifications, blockers, dependencies, and reviews go through PMA.
@@ -21,7 +21,7 @@
21
21
  - ClickUp-first delivery types: `Tarea`, `Bug`, `Doc`, `PoC`; ignore `Idea`, legacy `Backlog` alias, `Hito`, `Nota de reunión`, `Respuesta del formulario` unless converted or linked.
22
22
  - WPM estimates `Story Points` during `plan` and re-estimates on material plan changes.
23
23
  - 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.
24
- - ClickUp-first statuses: `backlog` ignore, `plan` plan plus remove the PM assignee before assigning `CTO`/`PO` at plan end, `in progress` execute, `validation` Tech Lead + Validator/QA gates. Validator/QA may merge validated subtasks into the parent branch without `CTO`/`PO` approval; validated parent tasks stay in `validation` for `CTO`/`PO` approval, they approve by moving the parent to `merge`, and Validator/QA then attempts the parent merge into `dev`. `completed`/`Closed` ignore unless reopened.
24
+ - ClickUp-first statuses: `backlog` ignore, `plan` plan with `Story Points`, `Definition`, and test strategy, `in progress` execute, `validation` Tech Lead + Validator/QA gates, and parent post-approval merge automation. Assign `CTO`/`PO` only for parent `plan` questions with clear ClickUp comments, real `in progress` blockers from missing credentials/permissions/tools/access, or parent `validation` with a functional preview URL; never for generic handoff, cleanup, subtasks, or partial-phase stops. Validator/QA may merge validated subtasks into the parent branch without CTO/PO approval; parent human `Approved` comments trigger automation to remove humans, assign merge owner/self, merge to `dev`, clean workspaces/worktrees/branches, push, and ensure dev receives the code. `completed`/`Closed` ignore unless reopened.
25
25
  - Signed agent-to-agent messages must start exactly: `[Agent Message] From: <agent_name> To: <agent_name>`.
26
26
  - Direct all clarifications, blockers, and specialist questions through PMA unless explicitly in a direct discussion-capable role.
27
27
  - Read relevant docs/tasks fully when they govern the current work. Prefer targeted CodeMap navigation before broad source search.
@@ -15,7 +15,7 @@ You are Workflow_Product_Manager, Optima's ClickUp-first delivery orchestrator.
15
15
  - `workflow_product_manager` owns delivery ops: ClickUp status, routing/handoffs, decomposition, validation gates, Git worktree/branch/PR flow, evidence, and closure.
16
16
  - `product_manager` remains compatibility/product/planning PMA for requirements, SCRs, product truth, rough pre-estimation, and default/legacy orchestration when ClickUp-first is not opted in. Do not remove, shadow, or break `product_manager`.
17
17
  - ClickUp Docs/tasks are source of truth for intent, state, comments, assignment, validation, and closure. Use the ClickUp skill plus ClickUp MCP/tools for every read/write/comment/field/status/assignment/dashboard action.
18
- - RULE NUMBER ONE: your operating objective is zero ClickUp tasks assigned to Workflow/Product Manager. If a task is PM-assigned, do not stop until you have posted a human-visible ClickUp task comment, removed yourself, and assigned `CTO` plus `PO` or the next owner.
18
+ - RULE NUMBER ONE: your operating objective is zero ClickUp tasks assigned to Workflow/Product Manager. If a task is PM-assigned, do not stop until you have posted a human-visible ClickUp task comment, removed yourself, and assigned the next non-human owner; assign `CTO`/`PO` only under the explicit human approval allowlist.
19
19
  - OpenCode session output is not visible to humans unless you post it to ClickUp. Before any stop, blocker, error, clarification, missing tool, or handoff pause, post a task comment. If ClickUp writes are unavailable, record the blocker/manual-sync payload in task/evidence and still report that blocker before stopping.
20
20
  - Keep raw logs in evidence; ClickUp gets summaries, paths/links, or excerpts only.
21
21
 
@@ -40,11 +40,12 @@ You are Workflow_Product_Manager, Optima's ClickUp-first delivery orchestrator.
40
40
  ## Status Actions
41
41
 
42
42
  - 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.
43
+ - 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 preview URL such as `https://<taskid>-preview.defend.tech`. Do not assign them for generic handoff, routine validation, cleanup, subtasks, or partial-phase stops.
43
44
  - `backlog`: ignore until prioritized.
44
- - `plan`: clarify AC/SCR/test strategy with Validator/QA; decompose; create/update Definition; estimate Story Points; remove PM assignee first; assign `CTO` + `PO` or next owner; target zero PM-assigned tasks.
45
- - `in progress`: execute through the assigned delivery agent or workflow runner.
46
- - `validation`: route Tech Lead for architecture/code/PR/standards/repo-skill review and Validator/QA for tests, Playwright/regression/coverage/evidence/final-doc checks. Validator/QA may merge validated subtasks into parent branch without `CTO`/`PO`; validated parents stay in `validation`, assigned to `CTO`/`PO`, ready for approval.
47
- - `merge`: only after `CTO`/`PO` move parent from `validation` to `merge`; Validator/QA then merges parent PR into `dev`. Conflicts or merge failures return the affected item to `in progress`.
45
+ - `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.
46
+ - `in progress`: execute through the assigned delivery agent or workflow runner. Escalate to `CTO`/`PO` only when genuinely blocked by missing credentials, permissions, external tools, or access; do not stop with phase language such as "I reached phase 1".
47
+ - `validation`: route Tech Lead for architecture/code/PR/standards/repo-skill review and Validator/QA for tests, Playwright/regression/coverage/evidence/final-doc checks. Validator/QA may merge validated subtasks into parent branch without `CTO`/`PO`; validated parent tasks may assign `CTO`/`PO` only when a functional preview URL is provided.
48
+ - `merge`: parent-only post-approval automation after a human comments `Approved`; remove human assignees, assign yourself or the merge owner, merge parent PR into `dev`, clean workspaces/worktrees/branches, push to `dev`, and ensure the dev environment contains the code. Conflicts or merge failures return the affected item to `in progress`.
48
49
  - `completed` / `Closed`: no execution unless explicitly reopened.
49
50
 
50
51
  ## Git, Worktree, PR
@@ -58,18 +59,18 @@ You are Workflow_Product_Manager, Optima's ClickUp-first delivery orchestrator.
58
59
  - Unrelated active tasks in `.optima/tasks/current.md` must not block planning; move to/create the correct task worktree instead.
59
60
  - Parent setup pulls remote once; after parent branch creation, subtasks can trust the parent local branch without continuous remote polling.
60
61
  - 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.
61
- - 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 -> `dev` only after Tech Lead + Validator/QA pass and `CTO`/`PO` move to `merge`; release -> `dev` to `main` only after explicit approval.
62
+ - 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 -> `dev` only after Tech Lead + Validator/QA pass and a parent-validation human `Approved` comment triggers merge automation; release -> `dev` to `main` only after explicit approval.
62
63
  - Preserve user work and unrelated dirty files. Stop and ask if unexpected changes appear.
63
64
 
64
65
  ## Operating Style
65
66
 
66
67
  - Orchestrate; do not silently implement specialist work yourself.
67
68
  - Delegate through ClickUp task/subtask assignment or task-specific specialist sessions with ClickUp context, AC, evidence, sync duties, branch target, Story Points, Definition link, final Documentation needs, and validation requirements.
68
- - Never abandon a PM-assigned task: keep working until unblocked and handed off, or post the stop/blocker/clarification/error as a ClickUp comment, remove Workflow/Product Manager, and assign `CTO` plus `PO` or next owner.
69
+ - Never abandon a PM-assigned task: keep working until unblocked and handed off, or post the stop/blocker/clarification/error as a ClickUp comment, remove Workflow/Product Manager, and assign the next delivery owner; assign `CTO`/`PO` only under the human approval allowlist.
69
70
  - On pickup, rewrite the ClickUp task description with the complete current description of what must be done; do not rely on status comments.
70
71
  - At plan completion, rewrite the ClickUp task description again with the complete final plan/Definition, distinct from the plan comment.
71
72
  - Estimate Story Points during `plan`, write them to ClickUp `Story Points`, and re-estimate when material plan changes alter scope/risk.
72
- - Before assigning `CTO`/`PO` or any next owner, remove the PM assignee and verify no task remains assigned to Workflow/Product Manager unless explicitly re-queued.
73
+ - Before assigning any next owner, remove the PM assignee and verify no task remains assigned to Workflow/Product Manager unless explicitly re-queued; when the next owner is `CTO`/`PO`, verify the allowlist condition and comment evidence first.
73
74
  - Final handoffs must include Summary, Work Performed, AC Coverage, Documentation Impact, Open Risks, Recommended Next Step, verification results, and commit/PR status.
74
75
 
75
76
  <include:plugin:Agents_Common.md>
package/dist/index.js CHANGED
@@ -8845,9 +8845,9 @@ var CLICKUP_REQUIRED_SUMMARY_SECTIONS = [
8845
8845
  ];
8846
8846
  var CLICKUP_RAW_LOG_SECTION_NAMES = /* @__PURE__ */ new Set(["Raw Logs", "Logs", "Full Logs", "Command Output", "Transcript"]);
8847
8847
  var CLICKUP_TRANSITIONS = /* @__PURE__ */ new Map([
8848
- ["plan->in progress", { status: "in progress", assignFinalApprovers: true, comment: "Plan complete; assigning CTO+PO for visibility and moving to implementation." }],
8848
+ ["plan->in progress", { status: "in progress", comment: "Plan complete; moving to implementation without generic CTO/PO assignment." }],
8849
8849
  ["in progress->validation", { status: "validation", comment: "Implementation complete; ready for validation." }],
8850
- ["validation->merge", { status: "merge", assignFinalApprovers: true, comment: "Validation passed; parent task is ready for CTO+PO merge approval." }],
8850
+ ["validation->merge", { status: "merge", assignFinalApprovers: true, parentOnlyFinalApproval: true, comment: "Parent validation passed with a functional preview URL; ready for CTO/PO approval flow." }],
8851
8851
  ["validation->in progress", { status: "in progress", comment: "Validation failed; returning to implementation." }],
8852
8852
  ["merge->completed", { status: "completed", comment: "Merge complete; closing delivery task." }],
8853
8853
  ["completed->in progress", { status: "in progress", comment: "Task reopened; returning to implementation." }],
@@ -9586,11 +9586,14 @@ function buildClickUpTransitionPayload({ fromStatus, toStatus, validationPassed
9586
9586
  const key = `${from}->${to}`;
9587
9587
  const rule = CLICKUP_TRANSITIONS.get(key);
9588
9588
  if (!rule) return { ok: false, dryRun: true, message: `Transition not allowed: ${key}` };
9589
- const assignsFinalApprovers = rule.assignFinalApprovers === true;
9590
- const requiresPlanCompletionContract = from === "plan" && assignsFinalApprovers;
9589
+ const assignsFinalApprovers = rule.assignFinalApprovers === true && !(rule.parentOnlyFinalApproval && isSubtask);
9590
+ const requiresPlanCompletionContract = from === "plan" && to === "in progress";
9591
9591
  const definitionContent = compactMarkdownValue(definition);
9592
9592
  const description = compactMarkdownValue(planDescription);
9593
9593
  const validationErrors = [];
9594
+ if (rule.parentOnlyFinalApproval && isSubtask) {
9595
+ validationErrors.push("Subtasks must not use the parent final-approval transition; merge validated subtasks directly into the parent branch/workspace.");
9596
+ }
9594
9597
  if (assignsFinalApprovers && !isRealClickUpAssigneeId(productManagerAssignee) && requireProductManagerAssignee !== false) {
9595
9598
  validationErrors.push("productManagerAssignee must be the configured ClickUp PM assignee ID before assigning final approvers.");
9596
9599
  }
@@ -9599,10 +9602,10 @@ function buildClickUpTransitionPayload({ fromStatus, toStatus, validationPassed
9599
9602
  if (!definitionContent) validationErrors.push("definition is required and must contain the complete plan/Definition content at plan completion.");
9600
9603
  }
9601
9604
  if (validationErrors.length > 0) return clickUpPayloadValidationError(validationErrors);
9602
- const finalApprovers = rule.assignFinalApprovers ? finalApprovalAssignees(CLICKUP_FINAL_APPROVER_ROLES, humansRegistry) : [];
9605
+ const finalApprovers = assignsFinalApprovers ? finalApprovalAssignees(CLICKUP_FINAL_APPROVER_ROLES, humansRegistry) : [];
9603
9606
  const explicitRemovals = (removeAssignees || []).filter(Boolean);
9604
9607
  const normalizedProductManagerAssignee = isRealClickUpAssigneeId(productManagerAssignee) ? String(productManagerAssignee).trim() : "";
9605
- const removalTargets = rule.assignFinalApprovers ? [...new Set([normalizedProductManagerAssignee, ...explicitRemovals].filter(Boolean))] : explicitRemovals;
9608
+ const removalTargets = assignsFinalApprovers ? [...new Set([normalizedProductManagerAssignee, ...explicitRemovals].filter(Boolean))] : explicitRemovals;
9606
9609
  const assigned = [...new Set([...assignees || [], ...finalApprovers].filter(Boolean))].filter((assignee) => !removalTargets.includes(assignee));
9607
9610
  const authority = from === "validation" || to === "merge" ? determineClickUpMergeAuthority({ isSubtask, clickupStatus: to, validationPassed: validationPassed || to === "merge", humansRegistry }) : null;
9608
9611
  const fields = authority ? { merge_authority: JSON.stringify(authority) } : {};
@@ -9617,7 +9620,7 @@ function buildClickUpTransitionPayload({ fromStatus, toStatus, validationPassed
9617
9620
  assignment_delta: {
9618
9621
  add: assigned,
9619
9622
  remove: removalTargets,
9620
- objective: rule.assignFinalApprovers ? "zero_product_manager_assigned_tasks" : "preserve_existing_owner_policy"
9623
+ objective: assignsFinalApprovers ? "zero_product_manager_assigned_tasks" : "preserve_existing_owner_policy"
9621
9624
  },
9622
9625
  comment: rule.comment,
9623
9626
  description,
@@ -10075,11 +10078,17 @@ function createTestClickUpApiClient(config) {
10075
10078
  __metadata: metadata
10076
10079
  };
10077
10080
  }
10078
- function normalizeClickUpWebhookApiResponse(response = {}, fallbackConfig = null) {
10079
- const webhook = response?.webhook || response?.data || response || {};
10081
+ function clickUpWebhookRemoteInactiveReason(webhook = {}) {
10080
10082
  const healthStatus = String(webhook.health?.status || webhook.health_status || "").trim().toLowerCase();
10081
10083
  const status = String(webhook.status || webhook.state || "").trim().toLowerCase();
10082
- const active = webhook.active !== false && !["inactive", "disabled", "suspended", "failed", "failing", "error", "errored", "paused"].includes(status) && !["failed", "failing", "unhealthy", "error", "errored", "suspended", "paused"].includes(healthStatus);
10084
+ if (["suspended", "paused", "failed", "failing", "unhealthy", "error", "errored"].includes(healthStatus)) return `remote_health_${healthStatus}`;
10085
+ if (["inactive", "disabled", "suspended", "failed", "failing", "error", "errored", "paused"].includes(status)) return `remote_status_${status}`;
10086
+ if (webhook.active === false) return "remote_inactive";
10087
+ return "";
10088
+ }
10089
+ function normalizeClickUpWebhookApiResponse(response = {}, fallbackConfig = null) {
10090
+ const webhook = response?.webhook || response?.data || response || {};
10091
+ const active = !clickUpWebhookRemoteInactiveReason(webhook);
10083
10092
  return sanitizeClickUpWebhookState({
10084
10093
  active,
10085
10094
  webhookId: webhook.id || webhook.webhook_id || webhook.webhookId,
@@ -10105,20 +10114,44 @@ function clickUpWebhookLocationCompatible(webhook = {}, config = {}) {
10105
10114
  }
10106
10115
  return true;
10107
10116
  }
10117
+ function clickUpWebhookEventsCompatible(webhook = {}, config = {}) {
10118
+ if (!Array.isArray(webhook.events) || webhook.events.length === 0) return false;
10119
+ const actualEvents = new Set(webhook.events);
10120
+ return [...new Set(config?.webhook?.events || [])].every((event) => actualEvents.has(event));
10121
+ }
10122
+ function clickUpWebhookRemoteCompatibilityReason(webhook = {}, config = {}) {
10123
+ if (!clickUpWebhookLocationCompatible(webhook, config)) return "remote_location_mismatch";
10124
+ if (!clickUpWebhookEventsCompatible(webhook, config)) return "remote_events_mismatch";
10125
+ return "";
10126
+ }
10127
+ function isClickUpWebhookRemoteSelfHealableReason(reason = "") {
10128
+ const normalized = String(reason || "");
10129
+ return normalized === "remote_inactive" || normalized.startsWith("remote_health_") || normalized.startsWith("remote_status_");
10130
+ }
10108
10131
  async function findReusableClickUpWebhook(config, clickupClient = null, existingState = null) {
10109
10132
  if (!clickupClient?.listWebhooks) return null;
10110
10133
  const listed = await clickupClient.listWebhooks({ teamId: config.teamId });
10111
- let secretlessMatch = null;
10134
+ let incompatibleMatch = null;
10112
10135
  for (const webhook of clickUpWebhookListItems(listed)) {
10113
10136
  const remote = normalizeClickUpWebhookApiResponse(webhook, config);
10114
10137
  if (remote.publicUrl !== config.webhook.publicUrl) continue;
10115
- if (!clickUpWebhookLocationCompatible(webhook, config)) continue;
10138
+ const compatibilityReason = clickUpWebhookRemoteCompatibilityReason(webhook, config);
10139
+ if (compatibilityReason) {
10140
+ if (compatibilityReason === "remote_events_mismatch" && remote.webhookId && !incompatibleMatch) incompatibleMatch = { ...remote, active: false, reason: compatibilityReason };
10141
+ continue;
10142
+ }
10116
10143
  const existingSecret = existingState?.webhookId === remote.webhookId ? existingState.secret : "";
10117
10144
  const reusable = remote.secret ? remote : { ...remote, secret: existingSecret };
10118
10145
  if (isClickUpWebhookStateActive(reusable, config)) return reusable;
10119
- if (remote.webhookId && !secretlessMatch) secretlessMatch = { ...remote, active: false, reason: "remote_secret_unavailable" };
10146
+ if (remote.webhookId && !incompatibleMatch) {
10147
+ incompatibleMatch = {
10148
+ ...remote,
10149
+ active: false,
10150
+ reason: remote.active === false ? clickUpWebhookRemoteInactiveReason(webhook) || "remote_inactive" : "remote_secret_unavailable"
10151
+ };
10152
+ }
10120
10153
  }
10121
- return secretlessMatch;
10154
+ return incompatibleMatch;
10122
10155
  }
10123
10156
  async function validateClickUpWebhookState(state, config, clickupClient = null, { allowRemoteUnhealthyLocalRecovery = false } = {}) {
10124
10157
  if (!isClickUpWebhookStateActive(state, config)) return { valid: false, reason: "state_incomplete" };
@@ -10133,7 +10166,7 @@ async function validateClickUpWebhookState(state, config, clickupClient = null,
10133
10166
  }
10134
10167
  const localStateValidation = await validateClickUpWebhookState(state, config, null);
10135
10168
  if (allowRemoteUnhealthyLocalRecovery && localStateValidation.valid && remote.webhookId === state.webhookId) {
10136
- const remoteConfigMatches = remote.publicUrl === config.webhook.publicUrl && [...new Set(config.webhook.events || [])].every((event) => new Set(remote.events || []).has(event));
10169
+ const remoteConfigMatches = remote.publicUrl === config.webhook.publicUrl && !clickUpWebhookRemoteCompatibilityReason(match, config);
10137
10170
  if (remoteConfigMatches) {
10138
10171
  return {
10139
10172
  ...localStateValidation,
@@ -10158,12 +10191,18 @@ async function ensureClickUpWebhookSubscription({ validation, worktree, clickupC
10158
10191
  const { config } = validation;
10159
10192
  const existing = existingState ? sanitizeClickUpWebhookState(existingState, config) : readClickUpWebhookState(worktree, config);
10160
10193
  const existingValidation = await validateClickUpWebhookState(existing, config, clickupClient, { allowRemoteUnhealthyLocalRecovery: true });
10161
- if (existingValidation.valid) {
10194
+ const canReplaceUnhealthyExisting = existingValidation.mode === "local_state_remote_unhealthy" && existingValidation.remote?.webhookId && clickupClient?.deleteWebhook && clickupClient?.createWebhook;
10195
+ if (existingValidation.valid && !canReplaceUnhealthyExisting) {
10162
10196
  const state = writeClickUpWebhookState(worktree, { ...existingValidation.state, recentEventKeys: existing.recentEventKeys || [] }, config);
10163
10197
  clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_reused", webhookId: state.webhookId, mode: existingValidation.mode });
10164
10198
  return { active: true, valid: true, mode: existingValidation.mode, limitation: existingValidation.limitation, state };
10165
10199
  }
10166
- const reusableRemote = await findReusableClickUpWebhook(config, clickupClient, existing);
10200
+ const reusableRemote = canReplaceUnhealthyExisting ? {
10201
+ ...existingValidation.remote,
10202
+ active: false,
10203
+ secret: existingValidation.remote.secret || existing.secret,
10204
+ reason: clickUpWebhookRemoteInactiveReason(existingValidation.remote) || existingValidation.reason || "remote_unhealthy_self_heal"
10205
+ } : await findReusableClickUpWebhook(config, clickupClient, existing);
10167
10206
  if (reusableRemote) {
10168
10207
  if (isClickUpWebhookStateActive(reusableRemote, config)) {
10169
10208
  const state = writeClickUpWebhookState(worktree, { ...reusableRemote, recentEventKeys: existing.recentEventKeys || [] }, config);
@@ -10171,10 +10210,31 @@ async function ensureClickUpWebhookSubscription({ validation, worktree, clickupC
10171
10210
  clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_reused", webhookId: state.webhookId, mode: mode2 });
10172
10211
  return { active: true, valid: true, mode: mode2, state };
10173
10212
  }
10174
- clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_preserved", webhookId: reusableRemote.webhookId, reason: reusableRemote.reason || "remote_webhook_exists_without_secret" });
10175
- return { active: false, valid: false, reason: reusableRemote.reason || "remote_webhook_exists_without_secret", state: existing, remote: reusableRemote };
10213
+ if (reusableRemote.active === false && reusableRemote.reason !== "remote_secret_unavailable") {
10214
+ if (!isClickUpWebhookRemoteSelfHealableReason(reusableRemote.reason)) {
10215
+ clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_preserved", webhookId: reusableRemote.webhookId, reason: reusableRemote.reason || "remote_incompatible" });
10216
+ return { active: false, valid: false, reason: reusableRemote.reason || "remote_incompatible", state: existing, remote: reusableRemote };
10217
+ }
10218
+ if (clickupClient?.deleteWebhook && clickupClient?.createWebhook) {
10219
+ const deleteResult = await deleteClickUpWebhookBestEffort({ webhookId: reusableRemote.webhookId, clickupClient, worktree, reason: reusableRemote.reason || "remote_unhealthy_self_heal" });
10220
+ if (deleteResult.ok) {
10221
+ if (existing.webhookId === reusableRemote.webhookId) markClickUpWebhookInactive(worktree, existing, config);
10222
+ } else {
10223
+ const reason = deleteResult.reason || "remote_unhealthy_delete_failed";
10224
+ clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_preserved", webhookId: reusableRemote.webhookId, reason });
10225
+ return { active: false, valid: false, reason, state: existing, remote: reusableRemote, deleteResult };
10226
+ }
10227
+ } else {
10228
+ const reason = clickupClient?.deleteWebhook ? "remote_unhealthy_create_unavailable" : "remote_unhealthy_delete_unavailable";
10229
+ clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_preserved", webhookId: reusableRemote.webhookId, reason });
10230
+ return { active: false, valid: false, reason, state: existing, remote: reusableRemote };
10231
+ }
10232
+ } else {
10233
+ clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_preserved", webhookId: reusableRemote.webhookId, reason: reusableRemote.reason || "remote_webhook_exists_without_secret" });
10234
+ return { active: false, valid: false, reason: reusableRemote.reason || "remote_webhook_exists_without_secret", state: existing, remote: reusableRemote };
10235
+ }
10176
10236
  }
10177
- if (existing.webhookId && clickupClient?.deleteWebhook) {
10237
+ if (existing.webhookId && existing.webhookId !== reusableRemote?.webhookId && clickupClient?.deleteWebhook) {
10178
10238
  await deleteClickUpWebhookBestEffort({ webhookId: existing.webhookId, clickupClient, worktree, reason: existingValidation.reason || "startup_self_heal" });
10179
10239
  markClickUpWebhookInactive(worktree, existing, config);
10180
10240
  }
@@ -8852,9 +8852,9 @@ var CLICKUP_REQUIRED_SUMMARY_SECTIONS = [
8852
8852
  ];
8853
8853
  var CLICKUP_RAW_LOG_SECTION_NAMES = /* @__PURE__ */ new Set(["Raw Logs", "Logs", "Full Logs", "Command Output", "Transcript"]);
8854
8854
  var CLICKUP_TRANSITIONS = /* @__PURE__ */ new Map([
8855
- ["plan->in progress", { status: "in progress", assignFinalApprovers: true, comment: "Plan complete; assigning CTO+PO for visibility and moving to implementation." }],
8855
+ ["plan->in progress", { status: "in progress", comment: "Plan complete; moving to implementation without generic CTO/PO assignment." }],
8856
8856
  ["in progress->validation", { status: "validation", comment: "Implementation complete; ready for validation." }],
8857
- ["validation->merge", { status: "merge", assignFinalApprovers: true, comment: "Validation passed; parent task is ready for CTO+PO merge approval." }],
8857
+ ["validation->merge", { status: "merge", assignFinalApprovers: true, parentOnlyFinalApproval: true, comment: "Parent validation passed with a functional preview URL; ready for CTO/PO approval flow." }],
8858
8858
  ["validation->in progress", { status: "in progress", comment: "Validation failed; returning to implementation." }],
8859
8859
  ["merge->completed", { status: "completed", comment: "Merge complete; closing delivery task." }],
8860
8860
  ["completed->in progress", { status: "in progress", comment: "Task reopened; returning to implementation." }],
@@ -9593,11 +9593,14 @@ function buildClickUpTransitionPayload({ fromStatus, toStatus, validationPassed
9593
9593
  const key = `${from}->${to}`;
9594
9594
  const rule = CLICKUP_TRANSITIONS.get(key);
9595
9595
  if (!rule) return { ok: false, dryRun: true, message: `Transition not allowed: ${key}` };
9596
- const assignsFinalApprovers = rule.assignFinalApprovers === true;
9597
- const requiresPlanCompletionContract = from === "plan" && assignsFinalApprovers;
9596
+ const assignsFinalApprovers = rule.assignFinalApprovers === true && !(rule.parentOnlyFinalApproval && isSubtask);
9597
+ const requiresPlanCompletionContract = from === "plan" && to === "in progress";
9598
9598
  const definitionContent = compactMarkdownValue(definition);
9599
9599
  const description = compactMarkdownValue(planDescription);
9600
9600
  const validationErrors = [];
9601
+ if (rule.parentOnlyFinalApproval && isSubtask) {
9602
+ validationErrors.push("Subtasks must not use the parent final-approval transition; merge validated subtasks directly into the parent branch/workspace.");
9603
+ }
9601
9604
  if (assignsFinalApprovers && !isRealClickUpAssigneeId(productManagerAssignee) && requireProductManagerAssignee !== false) {
9602
9605
  validationErrors.push("productManagerAssignee must be the configured ClickUp PM assignee ID before assigning final approvers.");
9603
9606
  }
@@ -9606,10 +9609,10 @@ function buildClickUpTransitionPayload({ fromStatus, toStatus, validationPassed
9606
9609
  if (!definitionContent) validationErrors.push("definition is required and must contain the complete plan/Definition content at plan completion.");
9607
9610
  }
9608
9611
  if (validationErrors.length > 0) return clickUpPayloadValidationError(validationErrors);
9609
- const finalApprovers = rule.assignFinalApprovers ? finalApprovalAssignees(CLICKUP_FINAL_APPROVER_ROLES, humansRegistry) : [];
9612
+ const finalApprovers = assignsFinalApprovers ? finalApprovalAssignees(CLICKUP_FINAL_APPROVER_ROLES, humansRegistry) : [];
9610
9613
  const explicitRemovals = (removeAssignees || []).filter(Boolean);
9611
9614
  const normalizedProductManagerAssignee = isRealClickUpAssigneeId(productManagerAssignee) ? String(productManagerAssignee).trim() : "";
9612
- const removalTargets = rule.assignFinalApprovers ? [...new Set([normalizedProductManagerAssignee, ...explicitRemovals].filter(Boolean))] : explicitRemovals;
9615
+ const removalTargets = assignsFinalApprovers ? [...new Set([normalizedProductManagerAssignee, ...explicitRemovals].filter(Boolean))] : explicitRemovals;
9613
9616
  const assigned = [...new Set([...assignees || [], ...finalApprovers].filter(Boolean))].filter((assignee) => !removalTargets.includes(assignee));
9614
9617
  const authority = from === "validation" || to === "merge" ? determineClickUpMergeAuthority({ isSubtask, clickupStatus: to, validationPassed: validationPassed || to === "merge", humansRegistry }) : null;
9615
9618
  const fields = authority ? { merge_authority: JSON.stringify(authority) } : {};
@@ -9624,7 +9627,7 @@ function buildClickUpTransitionPayload({ fromStatus, toStatus, validationPassed
9624
9627
  assignment_delta: {
9625
9628
  add: assigned,
9626
9629
  remove: removalTargets,
9627
- objective: rule.assignFinalApprovers ? "zero_product_manager_assigned_tasks" : "preserve_existing_owner_policy"
9630
+ objective: assignsFinalApprovers ? "zero_product_manager_assigned_tasks" : "preserve_existing_owner_policy"
9628
9631
  },
9629
9632
  comment: rule.comment,
9630
9633
  description,
@@ -10082,11 +10085,17 @@ function createTestClickUpApiClient(config) {
10082
10085
  __metadata: metadata
10083
10086
  };
10084
10087
  }
10085
- function normalizeClickUpWebhookApiResponse(response = {}, fallbackConfig = null) {
10086
- const webhook = response?.webhook || response?.data || response || {};
10088
+ function clickUpWebhookRemoteInactiveReason(webhook = {}) {
10087
10089
  const healthStatus = String(webhook.health?.status || webhook.health_status || "").trim().toLowerCase();
10088
10090
  const status = String(webhook.status || webhook.state || "").trim().toLowerCase();
10089
- const active = webhook.active !== false && !["inactive", "disabled", "suspended", "failed", "failing", "error", "errored", "paused"].includes(status) && !["failed", "failing", "unhealthy", "error", "errored", "suspended", "paused"].includes(healthStatus);
10091
+ if (["suspended", "paused", "failed", "failing", "unhealthy", "error", "errored"].includes(healthStatus)) return `remote_health_${healthStatus}`;
10092
+ if (["inactive", "disabled", "suspended", "failed", "failing", "error", "errored", "paused"].includes(status)) return `remote_status_${status}`;
10093
+ if (webhook.active === false) return "remote_inactive";
10094
+ return "";
10095
+ }
10096
+ function normalizeClickUpWebhookApiResponse(response = {}, fallbackConfig = null) {
10097
+ const webhook = response?.webhook || response?.data || response || {};
10098
+ const active = !clickUpWebhookRemoteInactiveReason(webhook);
10090
10099
  return sanitizeClickUpWebhookState({
10091
10100
  active,
10092
10101
  webhookId: webhook.id || webhook.webhook_id || webhook.webhookId,
@@ -10112,20 +10121,44 @@ function clickUpWebhookLocationCompatible(webhook = {}, config = {}) {
10112
10121
  }
10113
10122
  return true;
10114
10123
  }
10124
+ function clickUpWebhookEventsCompatible(webhook = {}, config = {}) {
10125
+ if (!Array.isArray(webhook.events) || webhook.events.length === 0) return false;
10126
+ const actualEvents = new Set(webhook.events);
10127
+ return [...new Set(config?.webhook?.events || [])].every((event) => actualEvents.has(event));
10128
+ }
10129
+ function clickUpWebhookRemoteCompatibilityReason(webhook = {}, config = {}) {
10130
+ if (!clickUpWebhookLocationCompatible(webhook, config)) return "remote_location_mismatch";
10131
+ if (!clickUpWebhookEventsCompatible(webhook, config)) return "remote_events_mismatch";
10132
+ return "";
10133
+ }
10134
+ function isClickUpWebhookRemoteSelfHealableReason(reason = "") {
10135
+ const normalized = String(reason || "");
10136
+ return normalized === "remote_inactive" || normalized.startsWith("remote_health_") || normalized.startsWith("remote_status_");
10137
+ }
10115
10138
  async function findReusableClickUpWebhook(config, clickupClient = null, existingState = null) {
10116
10139
  if (!clickupClient?.listWebhooks) return null;
10117
10140
  const listed = await clickupClient.listWebhooks({ teamId: config.teamId });
10118
- let secretlessMatch = null;
10141
+ let incompatibleMatch = null;
10119
10142
  for (const webhook of clickUpWebhookListItems(listed)) {
10120
10143
  const remote = normalizeClickUpWebhookApiResponse(webhook, config);
10121
10144
  if (remote.publicUrl !== config.webhook.publicUrl) continue;
10122
- if (!clickUpWebhookLocationCompatible(webhook, config)) continue;
10145
+ const compatibilityReason = clickUpWebhookRemoteCompatibilityReason(webhook, config);
10146
+ if (compatibilityReason) {
10147
+ if (compatibilityReason === "remote_events_mismatch" && remote.webhookId && !incompatibleMatch) incompatibleMatch = { ...remote, active: false, reason: compatibilityReason };
10148
+ continue;
10149
+ }
10123
10150
  const existingSecret = existingState?.webhookId === remote.webhookId ? existingState.secret : "";
10124
10151
  const reusable = remote.secret ? remote : { ...remote, secret: existingSecret };
10125
10152
  if (isClickUpWebhookStateActive(reusable, config)) return reusable;
10126
- if (remote.webhookId && !secretlessMatch) secretlessMatch = { ...remote, active: false, reason: "remote_secret_unavailable" };
10153
+ if (remote.webhookId && !incompatibleMatch) {
10154
+ incompatibleMatch = {
10155
+ ...remote,
10156
+ active: false,
10157
+ reason: remote.active === false ? clickUpWebhookRemoteInactiveReason(webhook) || "remote_inactive" : "remote_secret_unavailable"
10158
+ };
10159
+ }
10127
10160
  }
10128
- return secretlessMatch;
10161
+ return incompatibleMatch;
10129
10162
  }
10130
10163
  async function validateClickUpWebhookState(state, config, clickupClient = null, { allowRemoteUnhealthyLocalRecovery = false } = {}) {
10131
10164
  if (!isClickUpWebhookStateActive(state, config)) return { valid: false, reason: "state_incomplete" };
@@ -10140,7 +10173,7 @@ async function validateClickUpWebhookState(state, config, clickupClient = null,
10140
10173
  }
10141
10174
  const localStateValidation = await validateClickUpWebhookState(state, config, null);
10142
10175
  if (allowRemoteUnhealthyLocalRecovery && localStateValidation.valid && remote.webhookId === state.webhookId) {
10143
- const remoteConfigMatches = remote.publicUrl === config.webhook.publicUrl && [...new Set(config.webhook.events || [])].every((event) => new Set(remote.events || []).has(event));
10176
+ const remoteConfigMatches = remote.publicUrl === config.webhook.publicUrl && !clickUpWebhookRemoteCompatibilityReason(match, config);
10144
10177
  if (remoteConfigMatches) {
10145
10178
  return {
10146
10179
  ...localStateValidation,
@@ -10165,12 +10198,18 @@ async function ensureClickUpWebhookSubscription({ validation, worktree, clickupC
10165
10198
  const { config } = validation;
10166
10199
  const existing = existingState ? sanitizeClickUpWebhookState(existingState, config) : readClickUpWebhookState(worktree, config);
10167
10200
  const existingValidation = await validateClickUpWebhookState(existing, config, clickupClient, { allowRemoteUnhealthyLocalRecovery: true });
10168
- if (existingValidation.valid) {
10201
+ const canReplaceUnhealthyExisting = existingValidation.mode === "local_state_remote_unhealthy" && existingValidation.remote?.webhookId && clickupClient?.deleteWebhook && clickupClient?.createWebhook;
10202
+ if (existingValidation.valid && !canReplaceUnhealthyExisting) {
10169
10203
  const state = writeClickUpWebhookState(worktree, { ...existingValidation.state, recentEventKeys: existing.recentEventKeys || [] }, config);
10170
10204
  clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_reused", webhookId: state.webhookId, mode: existingValidation.mode });
10171
10205
  return { active: true, valid: true, mode: existingValidation.mode, limitation: existingValidation.limitation, state };
10172
10206
  }
10173
- const reusableRemote = await findReusableClickUpWebhook(config, clickupClient, existing);
10207
+ const reusableRemote = canReplaceUnhealthyExisting ? {
10208
+ ...existingValidation.remote,
10209
+ active: false,
10210
+ secret: existingValidation.remote.secret || existing.secret,
10211
+ reason: clickUpWebhookRemoteInactiveReason(existingValidation.remote) || existingValidation.reason || "remote_unhealthy_self_heal"
10212
+ } : await findReusableClickUpWebhook(config, clickupClient, existing);
10174
10213
  if (reusableRemote) {
10175
10214
  if (isClickUpWebhookStateActive(reusableRemote, config)) {
10176
10215
  const state = writeClickUpWebhookState(worktree, { ...reusableRemote, recentEventKeys: existing.recentEventKeys || [] }, config);
@@ -10178,10 +10217,31 @@ async function ensureClickUpWebhookSubscription({ validation, worktree, clickupC
10178
10217
  clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_reused", webhookId: state.webhookId, mode: mode2 });
10179
10218
  return { active: true, valid: true, mode: mode2, state };
10180
10219
  }
10181
- clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_preserved", webhookId: reusableRemote.webhookId, reason: reusableRemote.reason || "remote_webhook_exists_without_secret" });
10182
- return { active: false, valid: false, reason: reusableRemote.reason || "remote_webhook_exists_without_secret", state: existing, remote: reusableRemote };
10220
+ if (reusableRemote.active === false && reusableRemote.reason !== "remote_secret_unavailable") {
10221
+ if (!isClickUpWebhookRemoteSelfHealableReason(reusableRemote.reason)) {
10222
+ clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_preserved", webhookId: reusableRemote.webhookId, reason: reusableRemote.reason || "remote_incompatible" });
10223
+ return { active: false, valid: false, reason: reusableRemote.reason || "remote_incompatible", state: existing, remote: reusableRemote };
10224
+ }
10225
+ if (clickupClient?.deleteWebhook && clickupClient?.createWebhook) {
10226
+ const deleteResult = await deleteClickUpWebhookBestEffort({ webhookId: reusableRemote.webhookId, clickupClient, worktree, reason: reusableRemote.reason || "remote_unhealthy_self_heal" });
10227
+ if (deleteResult.ok) {
10228
+ if (existing.webhookId === reusableRemote.webhookId) markClickUpWebhookInactive(worktree, existing, config);
10229
+ } else {
10230
+ const reason = deleteResult.reason || "remote_unhealthy_delete_failed";
10231
+ clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_preserved", webhookId: reusableRemote.webhookId, reason });
10232
+ return { active: false, valid: false, reason, state: existing, remote: reusableRemote, deleteResult };
10233
+ }
10234
+ } else {
10235
+ const reason = clickupClient?.deleteWebhook ? "remote_unhealthy_create_unavailable" : "remote_unhealthy_delete_unavailable";
10236
+ clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_preserved", webhookId: reusableRemote.webhookId, reason });
10237
+ return { active: false, valid: false, reason, state: existing, remote: reusableRemote };
10238
+ }
10239
+ } else {
10240
+ clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_preserved", webhookId: reusableRemote.webhookId, reason: reusableRemote.reason || "remote_webhook_exists_without_secret" });
10241
+ return { active: false, valid: false, reason: reusableRemote.reason || "remote_webhook_exists_without_secret", state: existing, remote: reusableRemote };
10242
+ }
10183
10243
  }
10184
- if (existing.webhookId && clickupClient?.deleteWebhook) {
10244
+ if (existing.webhookId && existing.webhookId !== reusableRemote?.webhookId && clickupClient?.deleteWebhook) {
10185
10245
  await deleteClickUpWebhookBestEffort({ webhookId: existing.webhookId, clickupClient, worktree, reason: existingValidation.reason || "startup_self_heal" });
10186
10246
  markClickUpWebhookInactive(worktree, existing, config);
10187
10247
  }
@@ -22,7 +22,8 @@
22
22
  - `product_manager` may answer, investigate, operate dashboards, and pre-estimate "a qué huele" small/medium/large plus rough story points; WPM owns delivery routing.
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
- - Status actions are deterministic: `backlog` ignore, `plan` plan plus `Story Points`, test strategy, `Definition`, and `CTO`/`PO` assignment, `in progress` execute, `validation` split Tech Lead and Validator/QA gates, `merge` parent approval state after `CTO`/`PO` move a validated parent task there, and `completed`/`Closed` ignore unless reopened.
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 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.
26
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.
27
28
  - `workflow_product_manager` is registered only when explicit ClickUp webhook mode is configured and the local webhook subscription state is active/valid.
28
29
  - Webhook mode is opt-in: Optima validates signed `X-Signature` HMAC SHA-256 requests, routes status/assignee events only for Product Manager-assigned non-terminal tasks, routes comments only when they mention `@Defend Tech Product Manager`, stores new `ses_...` ids in ClickUp `agent_metadata`, and reports stale/missing sessions to ClickUp without creating replacements.
@@ -35,8 +36,8 @@
35
36
  - Parent task start pulls remote once; after branch creation, subtasks trust the parent local branch instead of continuous remote polling.
36
37
  - 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.
37
38
  - 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.
38
- - After successful subtask validation, Validator/QA merges the subtask PR into the parent branch/workspace without `CTO`/`PO` approval.
39
- - After parent Tech Lead and Validator/QA validation passes, the parent task stays in `validation`, `CTO`/`PO` are assigned and marked as approval owners, and they approve by moving it to `merge`; only then does Validator/QA attempt the parent PR merge into `dev`.
39
+ - After successful subtask validation, Validator/QA merges the subtask PR into the parent branch/workspace without `CTO`/`PO` assignment or approval.
40
+ - After parent Tech Lead and Validator/QA validation passes, the parent task may assign `CTO`/`PO` only when a functional validation URL is provided; 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 environment contains the code.
40
41
  - 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.
41
42
  - Never push directly to `main`.
42
43
  - `investigation` and `spec` tasks may run in parallel only when they avoid conflicting delivery artifacts.
@@ -14,7 +14,7 @@
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`, test strategy, and `CTO`/`PO` assignment, `in progress` execute, `validation` split Tech Lead + Validator/QA gates; Validator/QA may merge validated subtasks directly into the parent branch; validated parents stay in `validation` for `CTO`/`PO` approval; `merge` means `CTO`/`PO` approved the parent and Validator/QA may attempt parent merge into `dev`; `completed`/`Closed` ignore unless reopened.
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 preview 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 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,8 +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
- - Subtask merge authority belongs to Validator/QA after successful subtask validation: subtask PRs target and merge into the parent branch/workspace without `CTO`/`PO` approval.
25
- - Parent merge authority is split: after Tech Lead and Validator/QA pass, WPM/Validator assigns `CTO` and `PO` while the parent task remains in `validation`; `CTO`/`PO` approve by moving the parent task to `merge`; Validator/QA then attempts the parent PR merge into `dev`.
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, or partial-phase stops.
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
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.
27
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.
28
29
 
@@ -15,8 +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
- - Validator/QA may merge validated subtask PRs into the parent branch/workspace without `CTO`/`PO` approval.
19
- - Parent merge authority requires `CTO`/`PO` approval: after Tech Lead + Validator/QA pass, assign both roles while the parent stays in `validation`; they approve by moving it to `merge`; Validator/QA then attempts the parent PR merge into `dev`.
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 preview URL; never use it for generic handoff, cleanup, subtasks, or phase stops.
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
21
  - Failed or conflicted subtask/parent merges return the affected ClickUp item to `in progress` for the coding owner.
21
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.
22
23
  - BA owns product truth and product-facing feature/domain docs.
@@ -12,9 +12,16 @@
12
12
  - Ignored task types: `Idea`, legacy `Backlog` alias, `Hito`, `Nota de reunión`, `Respuesta del formulario` unless converted or linked to delivery work; `Idea` is non-delivery.
13
13
  - `product_manager` may pre-estimate "a qué huele" small/medium/large plus rough story points, but development requests must be converted to routed ClickUp work.
14
14
  - 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.
15
- - Status-to-action mapping: `backlog` -> `ignore`, `plan` -> `plan`, `in progress` -> `execute`, `validation` -> `validate`, `merge` -> `merge`, `completed`/`Closed` -> `ignore` unless reopened. For parent tasks, `merge` means `CTO`/`PO` have approved by moving the task out of `validation`; for subtasks, Validator/QA may merge after successful validation without waiting for `merge` human approval.
15
+ - Status-to-action mapping: `backlog` -> `ignore`, `plan` -> `plan`, `in progress` -> `execute`, `validation` -> `validate`, `merge` -> `merge`, `completed`/`Closed` -> `ignore` unless reopened. `plan` does not imply generic human approval assignment; `merge` is parent-only post-approval automation, while subtasks merge after successful Validator/QA validation without waiting for a human approval status.
16
16
  - Branch-safe type slugs are lowercase ASCII: `tarea`, `bug`, `doc`, `poc`.
17
17
 
18
+ ## Human Approval Allowlist
19
+
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
+ - **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
+ - **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 with a functional preview URL such as `https://<taskid>-preview.defend.tech` or an equivalent working validation 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 environment contains the code.
24
+
18
25
  ## Routing Rules
19
26
 
20
27
  - `tiny` stays within one slice and usually one specialist handoff.
@@ -24,7 +31,7 @@
24
31
  - While one shared-worktree implementation task is active, parallel work is limited to non-conflicting `investigation` or `spec`.
25
32
  - WPM estimates `Story Points` during `plan`, re-estimates on material plan changes, links the `Definition` plan contract when needed, and records `agent_metadata` session IDs.
26
33
  - In ClickUp-first mode, work should be decomposed into parent/subtask branches: parent tasks pull remote once at start, parent branches use `<type>/<parent-id>` and merge to `dev`, subtasks use non-nested `<type>/<parent-id>-subtask-<subtask-id>` branches that start from/trust the parent local branch and merge to parent branches, missing parent branches/worktrees are bootstrapped from `dev`/`origin/dev` before subtask worktree creation, PoC branches stay `poc/<clickup-task-id>`, and release branches merge `dev` to `main` only after approval.
27
- - Validator/QA owns merge execution after the correct gate: validated subtask PRs merge directly into the parent branch/workspace, while parent PRs merge into `dev` only after `CTO`/`PO` move the parent task to `merge`. Merge conflicts or failed attempts return the affected task/subtask to `in progress` for the coding owner.
34
+ - Validator/QA owns merge execution after the correct gate: validated subtask PRs merge directly into the parent branch/workspace without CTO/PO assignment, while parent PRs merge into `dev` only after the parent validation approval allowlist is satisfied and a human `Approved` comment triggers merge automation. Merge conflicts or failed attempts return the affected task/subtask to `in progress` for the coding owner.
28
35
 
29
36
  ## Pre-Sync Defaults
30
37
 
@@ -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`, test strategy, and `CTO`/`PO` assignment, `in progress` execute, `validation` split Tech Lead + Validator/QA; validated subtasks may be merged by Validator/QA into the parent branch without human approval; validated parent tasks assign `CTO`/`PO` and wait for them to move the task to `merge`; `merge` lets Validator/QA attempt parent PR merge into `dev`; `completed`/`Closed` ignore unless reopened.
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 preview 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 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,8 @@ 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
- - **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 `CTO`/`PO` approval by moving the parent task to `merge` before Validator/QA attempts the merge. Any conflicted or failed merge returns the affected task/subtask to `in progress` for the coding owner.
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 preview URL. Never assign them for generic handoff, routine validation, cleanup, subtasks, or partial-phase stops.
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.
23
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.
24
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.
25
26
  - **Investigation Outputs:** `investigation` tasks should produce findings, reproduction notes, logs when useful, and a recommended next step rather than pretending to be implementation tests.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defend-tech/opencode-optima",
3
- "version": "0.1.61",
3
+ "version": "0.1.62",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+ssh://git@github.com/defend-tech/opencode-optima.git"