@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 +1 -1
- package/Agents_Common.prompt.md +1 -1
- package/assets/agents/workflow_product_manager.md +9 -8
- package/dist/index.js +80 -20
- package/dist/sanitize_cli.js +80 -20
- package/docs/core/agent_orchestration.md +4 -3
- package/docs/core/agent_orchestration.prompt.md +1 -1
- package/docs/core/role_contracts.md +3 -2
- package/docs/core/role_contracts.prompt.md +3 -2
- package/docs/core/task_model.md +9 -2
- package/docs/core/task_model.prompt.md +1 -1
- package/docs/core/testing_strategy.md +2 -1
- package/package.json +1 -1
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
|
|
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.
|
package/Agents_Common.prompt.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
47
|
-
- `merge`: only after `
|
|
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 `
|
|
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
|
|
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
|
|
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",
|
|
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: "
|
|
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" &&
|
|
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 =
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 && !
|
|
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
|
|
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 &&
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
10175
|
-
|
|
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
|
}
|
package/dist/sanitize_cli.js
CHANGED
|
@@ -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",
|
|
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: "
|
|
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" &&
|
|
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 =
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 && !
|
|
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
|
|
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 &&
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
10182
|
-
|
|
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`,
|
|
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
|
|
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
|
|
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
|
-
-
|
|
25
|
-
-
|
|
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
|
-
-
|
|
19
|
-
-
|
|
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.
|
package/docs/core/task_model.md
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
-
- **
|
|
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.
|