@defend-tech/opencode-optima 0.1.47 → 0.1.48
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 +1 -1
- package/dist/index.js +89 -7
- package/dist/sanitize_cli.js +89 -7
- package/docs/core/agent_orchestration.md +1 -1
- package/docs/core/agent_orchestration.prompt.md +1 -1
- package/docs/core/role_contracts.md +1 -1
- package/docs/core/role_contracts.prompt.md +1 -1
- package/docs/core/task_model.md +1 -1
- package/docs/core/task_model.prompt.md +1 -1
- package/docs/core/testing_strategy.md +1 -1
- package/package.json +1 -1
package/Agents_Common.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
- `product_manager` may investigate, answer, pre-estimate "a qué huele" small/medium/large plus rough story points, and operate ClickUp dashboards; development requests must be converted into properly routed ClickUp tasks.
|
|
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
|
-
- Human role registry: resolve `CTO` and `PO` from `docs/core/humans.md
|
|
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
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.
|
|
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>`.
|
package/Agents_Common.prompt.md
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
- `product_manager` may investigate, answer, pre-estimate "a qué huele" small/medium/large plus rough story points, and operate ClickUp dashboards; development requests must become routed ClickUp tasks.
|
|
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
|
-
- Human role registry: resolve `CTO` and `PO` from `docs/core/humans.md
|
|
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
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.
|
|
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.
|
|
@@ -41,7 +41,7 @@ You are Workflow_Product_Manager, Optima's ClickUp-first delivery orchestrator.
|
|
|
41
41
|
## Status Actions
|
|
42
42
|
|
|
43
43
|
- `backlog`: ignore until prioritized.
|
|
44
|
-
- Human registry: resolve `CTO` and `PO` from `docs/core/humans.md
|
|
44
|
+
- Human registry: resolve `CTO` and `PO` from `docs/core/humans.md` when present; if the task worktree lacks that file, use the Optima-provided Human Role Fallback Registry and configured ClickUp IDs instead of blocking solely on the missing repo-local file. Use role IDs in workflow text, ClickUp comments, and automation config.
|
|
45
45
|
- `plan`: clarify AC/SCR/test strategy with Validator/QA, decompose, create/update Definition, estimate Story Points, hand off implementation, remove PM assignee first, then assign `CTO` + `PO` or next owner; target zero PM-assigned tasks.
|
|
46
46
|
- `in progress`: execute through assigned delivery agent or workflow runner.
|
|
47
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 parents stay in `validation`, assigned to `CTO`/`PO`, ready for approval.
|
package/dist/index.js
CHANGED
|
@@ -8130,6 +8130,41 @@ function resolveHumanRoles(roles = CLICKUP_FINAL_APPROVER_ROLES, registry = load
|
|
|
8130
8130
|
function finalApprovalAssignees(roles = CLICKUP_FINAL_APPROVER_ROLES, registry = loadHumansRegistry()) {
|
|
8131
8131
|
return resolveHumanRoles(roles, registry);
|
|
8132
8132
|
}
|
|
8133
|
+
function normalizedHumanRoleRegistry(registry = loadHumansRegistry()) {
|
|
8134
|
+
return Object.fromEntries(
|
|
8135
|
+
Object.entries(registry || {}).map(([role, human]) => [String(role || "").trim(), String(human || "").trim()]).filter(([role, human]) => role && human)
|
|
8136
|
+
);
|
|
8137
|
+
}
|
|
8138
|
+
function normalizeClickUpRoleIds(raw = {}) {
|
|
8139
|
+
const source = isPlainObject(raw) ? raw : {};
|
|
8140
|
+
const entries = Object.entries(source).map(([role, id]) => [String(role || "").trim(), String(id || "").trim()]);
|
|
8141
|
+
return Object.fromEntries(entries.filter(([role, id]) => role && id));
|
|
8142
|
+
}
|
|
8143
|
+
function buildHumanRoleContext({ roles = CLICKUP_FINAL_APPROVER_ROLES, registry = loadHumansRegistry(), roleIds = {} } = {}) {
|
|
8144
|
+
const normalizedRegistry = normalizedHumanRoleRegistry(registry);
|
|
8145
|
+
const normalizedIds = normalizeClickUpRoleIds(roleIds);
|
|
8146
|
+
return [...new Set(roles.map((role) => String(role || "").trim()).filter(Boolean))].map((role) => ({
|
|
8147
|
+
role,
|
|
8148
|
+
human: normalizedRegistry[role] || "",
|
|
8149
|
+
clickupUserId: normalizedIds[role] || ""
|
|
8150
|
+
}));
|
|
8151
|
+
}
|
|
8152
|
+
function formatHumanRoleContext(context = []) {
|
|
8153
|
+
const entries = Array.isArray(context) ? context : [];
|
|
8154
|
+
if (!entries.length) return "";
|
|
8155
|
+
return entries.map((entry) => {
|
|
8156
|
+
const human = entry.human ? ` = ${entry.human}` : "";
|
|
8157
|
+
const id = entry.clickupUserId ? ` (ClickUp ID: ${entry.clickupUserId})` : "";
|
|
8158
|
+
return `- ${entry.role}${human}${id}`;
|
|
8159
|
+
}).join("\n");
|
|
8160
|
+
}
|
|
8161
|
+
function hasActionableClickUpRoute(result = {}) {
|
|
8162
|
+
if (!result?.ok || result.action === "ignored") return false;
|
|
8163
|
+
if (!result.sessionId) return false;
|
|
8164
|
+
if (result.action === "message_delivery_failed" || result.action === "error") return false;
|
|
8165
|
+
if (result.deliveryVerification?.ok === false) return false;
|
|
8166
|
+
return true;
|
|
8167
|
+
}
|
|
8133
8168
|
function determineClickUpMergeAuthority({ isSubtask = false, clickupStatus = "", validationPassed = false, mergeFailed = false, finalApprovalRoles = CLICKUP_FINAL_APPROVER_ROLES, humansRegistry } = {}) {
|
|
8134
8169
|
if (mergeFailed) {
|
|
8135
8170
|
return {
|
|
@@ -9015,7 +9050,8 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
9015
9050
|
ignoredStatuses,
|
|
9016
9051
|
metadataFieldId: String(routing.metadata_field_id || routing.metadataFieldId || "").trim(),
|
|
9017
9052
|
metadataKey: String(routing.metadata_key || routing.metadataKey || CLICKUP_PM_METADATA_KEY).trim(),
|
|
9018
|
-
ignoredCommentAuthorId: String(routing.ignored_comment_author_id || routing.ignoredCommentAuthorId || routing.product_manager_mention_user_id || routing.productManagerMentionUserId || "").trim()
|
|
9053
|
+
ignoredCommentAuthorId: String(routing.ignored_comment_author_id || routing.ignoredCommentAuthorId || routing.product_manager_mention_user_id || routing.productManagerMentionUserId || "").trim(),
|
|
9054
|
+
humanRoleClickUpIds: normalizeClickUpRoleIds(routing.human_role_clickup_ids || routing.humanRoleClickUpIds || routing.role_ids || routing.roleIds)
|
|
9019
9055
|
}
|
|
9020
9056
|
};
|
|
9021
9057
|
const errors = [];
|
|
@@ -9882,9 +9918,10 @@ async function recoverClickUpPmSession({ openCodeClient, sendSessionEvent, click
|
|
|
9882
9918
|
appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_succeeded", taskId, staleSessionId, replacementSessionId });
|
|
9883
9919
|
return { ok: true, action: "sent_to_replacement_session", taskId, sessionId: replacementSessionId, staleSessionId, replacementAttempted: true, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath, deliveryVerification: replacementDelivery.verification, deliveryFallback: replacementDelivery.fallback };
|
|
9884
9920
|
}
|
|
9885
|
-
function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", worktree = "", deliveryEvidencePath = "" }) {
|
|
9921
|
+
function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", worktree = "", deliveryEvidencePath = "", humanRoleContext = [] }) {
|
|
9886
9922
|
const comment = clickUpCommentFromPayload(payload);
|
|
9887
9923
|
const commentText = clickUpCommentText(comment).trim();
|
|
9924
|
+
const humanRoleLines = formatHumanRoleContext(humanRoleContext);
|
|
9888
9925
|
return [
|
|
9889
9926
|
"[ClickUp Webhook Event]",
|
|
9890
9927
|
`Event: ${eventType}`,
|
|
@@ -9892,9 +9929,12 @@ function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", w
|
|
|
9892
9929
|
branch ? `Branch: ${branch}` : null,
|
|
9893
9930
|
worktree ? `Worktree: ${worktree}` : null,
|
|
9894
9931
|
deliveryEvidencePath ? `Delivery evidence path: ${deliveryEvidencePath}` : null,
|
|
9932
|
+
humanRoleLines ? "Human role fallback registry (Optima-provided; non-blocking if repo docs/core/humans.md is missing):" : null,
|
|
9933
|
+
humanRoleLines || null,
|
|
9895
9934
|
commentText ? `Comment: ${commentText}` : null,
|
|
9896
9935
|
"",
|
|
9897
9936
|
"Handle this ClickUp Product Manager event using the ClickUp-first workflow rules. Do not assume broad chat routing; this event passed Optima webhook gates.",
|
|
9937
|
+
"If the task worktree lacks docs/core/humans.md, use the Optima-provided human role fallback registry above instead of blocking solely on that missing file.",
|
|
9898
9938
|
deliveryEvidencePath ? `Final merge-trackable evidence must be written under ${deliveryEvidencePath}; use .optima only as local mirror/staging.` : null
|
|
9899
9939
|
].filter((part) => part !== null).join("\n");
|
|
9900
9940
|
}
|
|
@@ -10145,7 +10185,8 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
10145
10185
|
evidence_path: `.optima/evidences/${branchSafeClickUpId(taskId)}/SUMMARY.md`
|
|
10146
10186
|
};
|
|
10147
10187
|
const metadataWithRouting = setClickUpTaskRoutingMetadata(existingMetadata, routingMetadata);
|
|
10148
|
-
const
|
|
10188
|
+
const humanRoleContext = buildHumanRoleContext({ roleIds: config.routing?.humanRoleClickUpIds });
|
|
10189
|
+
const prompt = formatClickUpWebhookPrompt({ eventType, taskId, payload, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, humanRoleContext });
|
|
10149
10190
|
if (!existingSessionId) {
|
|
10150
10191
|
let pendingSessionId = getNestedMetadataValue(metadata, clickUpPendingSessionKey(config.routing.metadataKey)) || state.pendingSessions?.[taskId];
|
|
10151
10192
|
if (!pendingSessionId) {
|
|
@@ -10192,7 +10233,7 @@ async function routeClickUpWebhookEvent(options = {}) {
|
|
|
10192
10233
|
const taskId = clickUpTaskIdFromPayload(options.payload || {});
|
|
10193
10234
|
return withClickUpTaskRouteLock(taskId, () => routeClickUpWebhookEventUnlocked(options));
|
|
10194
10235
|
}
|
|
10195
|
-
async function reconcileClickUpStartup({ config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, saveState = null, now = () => /* @__PURE__ */ new Date(), limit = CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT } = {}) {
|
|
10236
|
+
async function reconcileClickUpStartup({ config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, saveState = null, now = () => /* @__PURE__ */ new Date(), limit = CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT } = {}) {
|
|
10196
10237
|
if (!config || !clickupClient?.listAssignedTasks) return { ok: true, skipped: true, reason: "clickup_task_listing_unavailable", assigned: 0, comments: 0 };
|
|
10197
10238
|
let authorizedUserId = "";
|
|
10198
10239
|
if (clickupClient?.getAuthorizedUser) {
|
|
@@ -10210,7 +10251,7 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
|
|
|
10210
10251
|
};
|
|
10211
10252
|
const lastWebhookMs = clickUpTimestampMs(mutableState.lastWebhookAt);
|
|
10212
10253
|
const ignored = new Set((config.routing?.ignoredStatuses || CLICKUP_WEBHOOK_TERMINAL_STATUSES).map(normalizeClickUpStatus));
|
|
10213
|
-
const routed = { assigned: 0, comments: 0, ignored: 0, errors: 0 };
|
|
10254
|
+
const routed = { assigned: 0, comments: 0, ignored: 0, errors: 0, undelivered: 0, tasks: [] };
|
|
10214
10255
|
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_started", lastWebhookAt: state.lastWebhookAt || null });
|
|
10215
10256
|
const listed = await clickupClient.listAssignedTasks({ assigneeId: config.routing.productManagerAssigneeId, limit });
|
|
10216
10257
|
const tasks = clickUpTaskListItems(listed).slice(0, limit);
|
|
@@ -10239,18 +10280,41 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
|
|
|
10239
10280
|
sessionExists,
|
|
10240
10281
|
createSession,
|
|
10241
10282
|
sendSessionEvent,
|
|
10283
|
+
verifySessionEventDelivery,
|
|
10242
10284
|
saveState: persistState,
|
|
10243
10285
|
now
|
|
10244
10286
|
});
|
|
10245
|
-
|
|
10287
|
+
const routeSummary = {
|
|
10288
|
+
taskId,
|
|
10289
|
+
action: result?.action || "unknown",
|
|
10290
|
+
ok: result?.ok === true,
|
|
10291
|
+
sessionId: result?.sessionId || result?.replacementSessionId || null,
|
|
10292
|
+
branch: result?.branch || null,
|
|
10293
|
+
worktree: result?.worktree || null,
|
|
10294
|
+
verification: result?.deliveryVerification?.method || result?.deliveryVerification?.reason || null,
|
|
10295
|
+
delivered: hasActionableClickUpRoute(result)
|
|
10296
|
+
};
|
|
10297
|
+
routed.tasks.push(routeSummary);
|
|
10298
|
+
appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_routed", ...routeSummary });
|
|
10299
|
+
if (routeSummary.delivered) {
|
|
10246
10300
|
routed.assigned += 1;
|
|
10301
|
+
} else if (result?.ok && result.action === "ignored") {
|
|
10302
|
+
routed.ignored += 1;
|
|
10247
10303
|
} else if (result?.ok === false) {
|
|
10248
10304
|
routed.errors += 1;
|
|
10305
|
+
routed.undelivered += 1;
|
|
10306
|
+
appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_undelivered", taskId, action: result?.action || "error", sessionId: routeSummary.sessionId, reason: result.reason || "startup_reconciliation_route_failed" });
|
|
10249
10307
|
if (!result.blockerTag) await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: result.reason || "startup_reconciliation_route_failed", source: "startup_reconciliation_task" });
|
|
10308
|
+
} else {
|
|
10309
|
+
routed.undelivered += 1;
|
|
10310
|
+
appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_undelivered", taskId, action: routeSummary.action, sessionId: routeSummary.sessionId, reason: "route_not_actionable" });
|
|
10250
10311
|
}
|
|
10251
10312
|
} catch (error) {
|
|
10252
10313
|
routed.errors += 1;
|
|
10314
|
+
routed.undelivered += 1;
|
|
10315
|
+
routed.tasks.push({ taskId, action: "error", ok: false, sessionId: null, branch: null, worktree: null, verification: error.message, delivered: false });
|
|
10253
10316
|
appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_failed", taskId, message: error.message });
|
|
10317
|
+
appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_undelivered", taskId, action: "error", sessionId: null, reason: "startup_reconciliation_task_failed" });
|
|
10254
10318
|
await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "startup_reconciliation_task_failed", source: "startup_reconciliation_task" });
|
|
10255
10319
|
}
|
|
10256
10320
|
if (!lastWebhookMs || !clickupClient?.getTaskComments) continue;
|
|
@@ -10273,6 +10337,7 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
|
|
|
10273
10337
|
sessionExists,
|
|
10274
10338
|
createSession,
|
|
10275
10339
|
sendSessionEvent,
|
|
10340
|
+
verifySessionEventDelivery,
|
|
10276
10341
|
saveState: persistState,
|
|
10277
10342
|
now
|
|
10278
10343
|
});
|
|
@@ -10284,8 +10349,12 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
|
|
|
10284
10349
|
await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "startup_reconciliation_comments_failed", source: "startup_reconciliation_comments" });
|
|
10285
10350
|
}
|
|
10286
10351
|
}
|
|
10352
|
+
const emptyPromptSessions = routed.tasks.filter((task) => task.delivered === false && task.sessionId);
|
|
10353
|
+
if (emptyPromptSessions.length > 0) {
|
|
10354
|
+
appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_validation_failed", tasks: emptyPromptSessions });
|
|
10355
|
+
}
|
|
10287
10356
|
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_finished", ...routed });
|
|
10288
|
-
return { ok: routed.errors === 0, ...routed };
|
|
10357
|
+
return { ok: routed.errors === 0 && routed.undelivered === 0, ...routed, validation: { undelivered: routed.undelivered, emptyPromptSessions } };
|
|
10289
10358
|
}
|
|
10290
10359
|
function clickUpWebhookExpectedPath(config) {
|
|
10291
10360
|
try {
|
|
@@ -11403,6 +11472,19 @@ ${additionFragment}`;
|
|
|
11403
11472
|
delete agentConfig.tools.optima_prompt_workflow;
|
|
11404
11473
|
}
|
|
11405
11474
|
}
|
|
11475
|
+
if (id === "workflow_product_manager") {
|
|
11476
|
+
const humanRoleContext = buildHumanRoleContext({ roleIds: options.clickUpWebhookValidation?.config?.routing?.humanRoleClickUpIds });
|
|
11477
|
+
const humanRoleLines = formatHumanRoleContext(humanRoleContext);
|
|
11478
|
+
if (humanRoleLines) {
|
|
11479
|
+
agentConfig.prompt = `${agentConfig.prompt}
|
|
11480
|
+
|
|
11481
|
+
## Optima Human Role Fallback Registry
|
|
11482
|
+
|
|
11483
|
+
${humanRoleLines}
|
|
11484
|
+
|
|
11485
|
+
Use this Optima-provided fallback when the current task worktree lacks docs/core/humans.md; missing repo-local humans.md is not a hard blocker when this context or configured ClickUp IDs are present.`;
|
|
11486
|
+
}
|
|
11487
|
+
}
|
|
11406
11488
|
ourAgents[id] = agentConfig;
|
|
11407
11489
|
if (repoCfg.features?.debug_dumps !== false) {
|
|
11408
11490
|
const debugPath = path2.join(debugDir, `${id}.md`);
|
package/dist/sanitize_cli.js
CHANGED
|
@@ -8137,6 +8137,41 @@ function resolveHumanRoles(roles = CLICKUP_FINAL_APPROVER_ROLES, registry = load
|
|
|
8137
8137
|
function finalApprovalAssignees(roles = CLICKUP_FINAL_APPROVER_ROLES, registry = loadHumansRegistry()) {
|
|
8138
8138
|
return resolveHumanRoles(roles, registry);
|
|
8139
8139
|
}
|
|
8140
|
+
function normalizedHumanRoleRegistry(registry = loadHumansRegistry()) {
|
|
8141
|
+
return Object.fromEntries(
|
|
8142
|
+
Object.entries(registry || {}).map(([role, human]) => [String(role || "").trim(), String(human || "").trim()]).filter(([role, human]) => role && human)
|
|
8143
|
+
);
|
|
8144
|
+
}
|
|
8145
|
+
function normalizeClickUpRoleIds(raw = {}) {
|
|
8146
|
+
const source = isPlainObject(raw) ? raw : {};
|
|
8147
|
+
const entries = Object.entries(source).map(([role, id]) => [String(role || "").trim(), String(id || "").trim()]);
|
|
8148
|
+
return Object.fromEntries(entries.filter(([role, id]) => role && id));
|
|
8149
|
+
}
|
|
8150
|
+
function buildHumanRoleContext({ roles = CLICKUP_FINAL_APPROVER_ROLES, registry = loadHumansRegistry(), roleIds = {} } = {}) {
|
|
8151
|
+
const normalizedRegistry = normalizedHumanRoleRegistry(registry);
|
|
8152
|
+
const normalizedIds = normalizeClickUpRoleIds(roleIds);
|
|
8153
|
+
return [...new Set(roles.map((role) => String(role || "").trim()).filter(Boolean))].map((role) => ({
|
|
8154
|
+
role,
|
|
8155
|
+
human: normalizedRegistry[role] || "",
|
|
8156
|
+
clickupUserId: normalizedIds[role] || ""
|
|
8157
|
+
}));
|
|
8158
|
+
}
|
|
8159
|
+
function formatHumanRoleContext(context = []) {
|
|
8160
|
+
const entries = Array.isArray(context) ? context : [];
|
|
8161
|
+
if (!entries.length) return "";
|
|
8162
|
+
return entries.map((entry) => {
|
|
8163
|
+
const human = entry.human ? ` = ${entry.human}` : "";
|
|
8164
|
+
const id = entry.clickupUserId ? ` (ClickUp ID: ${entry.clickupUserId})` : "";
|
|
8165
|
+
return `- ${entry.role}${human}${id}`;
|
|
8166
|
+
}).join("\n");
|
|
8167
|
+
}
|
|
8168
|
+
function hasActionableClickUpRoute(result = {}) {
|
|
8169
|
+
if (!result?.ok || result.action === "ignored") return false;
|
|
8170
|
+
if (!result.sessionId) return false;
|
|
8171
|
+
if (result.action === "message_delivery_failed" || result.action === "error") return false;
|
|
8172
|
+
if (result.deliveryVerification?.ok === false) return false;
|
|
8173
|
+
return true;
|
|
8174
|
+
}
|
|
8140
8175
|
function determineClickUpMergeAuthority({ isSubtask = false, clickupStatus = "", validationPassed = false, mergeFailed = false, finalApprovalRoles = CLICKUP_FINAL_APPROVER_ROLES, humansRegistry } = {}) {
|
|
8141
8176
|
if (mergeFailed) {
|
|
8142
8177
|
return {
|
|
@@ -9022,7 +9057,8 @@ function normalizeClickUpWebhookConfig(rawClickUp = null, worktree = process.cwd
|
|
|
9022
9057
|
ignoredStatuses,
|
|
9023
9058
|
metadataFieldId: String(routing.metadata_field_id || routing.metadataFieldId || "").trim(),
|
|
9024
9059
|
metadataKey: String(routing.metadata_key || routing.metadataKey || CLICKUP_PM_METADATA_KEY).trim(),
|
|
9025
|
-
ignoredCommentAuthorId: String(routing.ignored_comment_author_id || routing.ignoredCommentAuthorId || routing.product_manager_mention_user_id || routing.productManagerMentionUserId || "").trim()
|
|
9060
|
+
ignoredCommentAuthorId: String(routing.ignored_comment_author_id || routing.ignoredCommentAuthorId || routing.product_manager_mention_user_id || routing.productManagerMentionUserId || "").trim(),
|
|
9061
|
+
humanRoleClickUpIds: normalizeClickUpRoleIds(routing.human_role_clickup_ids || routing.humanRoleClickUpIds || routing.role_ids || routing.roleIds)
|
|
9026
9062
|
}
|
|
9027
9063
|
};
|
|
9028
9064
|
const errors = [];
|
|
@@ -9889,9 +9925,10 @@ async function recoverClickUpPmSession({ openCodeClient, sendSessionEvent, click
|
|
|
9889
9925
|
appendClickUpWebhookLocalLog(worktree, { type: "pm_session_recovery_succeeded", taskId, staleSessionId, replacementSessionId });
|
|
9890
9926
|
return { ok: true, action: "sent_to_replacement_session", taskId, sessionId: replacementSessionId, staleSessionId, replacementAttempted: true, eventKey, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, evidencePath, deliveryVerification: replacementDelivery.verification, deliveryFallback: replacementDelivery.fallback };
|
|
9891
9927
|
}
|
|
9892
|
-
function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", worktree = "", deliveryEvidencePath = "" }) {
|
|
9928
|
+
function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", worktree = "", deliveryEvidencePath = "", humanRoleContext = [] }) {
|
|
9893
9929
|
const comment = clickUpCommentFromPayload(payload);
|
|
9894
9930
|
const commentText = clickUpCommentText(comment).trim();
|
|
9931
|
+
const humanRoleLines = formatHumanRoleContext(humanRoleContext);
|
|
9895
9932
|
return [
|
|
9896
9933
|
"[ClickUp Webhook Event]",
|
|
9897
9934
|
`Event: ${eventType}`,
|
|
@@ -9899,9 +9936,12 @@ function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", w
|
|
|
9899
9936
|
branch ? `Branch: ${branch}` : null,
|
|
9900
9937
|
worktree ? `Worktree: ${worktree}` : null,
|
|
9901
9938
|
deliveryEvidencePath ? `Delivery evidence path: ${deliveryEvidencePath}` : null,
|
|
9939
|
+
humanRoleLines ? "Human role fallback registry (Optima-provided; non-blocking if repo docs/core/humans.md is missing):" : null,
|
|
9940
|
+
humanRoleLines || null,
|
|
9902
9941
|
commentText ? `Comment: ${commentText}` : null,
|
|
9903
9942
|
"",
|
|
9904
9943
|
"Handle this ClickUp Product Manager event using the ClickUp-first workflow rules. Do not assume broad chat routing; this event passed Optima webhook gates.",
|
|
9944
|
+
"If the task worktree lacks docs/core/humans.md, use the Optima-provided human role fallback registry above instead of blocking solely on that missing file.",
|
|
9905
9945
|
deliveryEvidencePath ? `Final merge-trackable evidence must be written under ${deliveryEvidencePath}; use .optima only as local mirror/staging.` : null
|
|
9906
9946
|
].filter((part) => part !== null).join("\n");
|
|
9907
9947
|
}
|
|
@@ -10152,7 +10192,8 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
10152
10192
|
evidence_path: `.optima/evidences/${branchSafeClickUpId(taskId)}/SUMMARY.md`
|
|
10153
10193
|
};
|
|
10154
10194
|
const metadataWithRouting = setClickUpTaskRoutingMetadata(existingMetadata, routingMetadata);
|
|
10155
|
-
const
|
|
10195
|
+
const humanRoleContext = buildHumanRoleContext({ roleIds: config.routing?.humanRoleClickUpIds });
|
|
10196
|
+
const prompt = formatClickUpWebhookPrompt({ eventType, taskId, payload, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, humanRoleContext });
|
|
10156
10197
|
if (!existingSessionId) {
|
|
10157
10198
|
let pendingSessionId = getNestedMetadataValue(metadata, clickUpPendingSessionKey(config.routing.metadataKey)) || state.pendingSessions?.[taskId];
|
|
10158
10199
|
if (!pendingSessionId) {
|
|
@@ -10199,7 +10240,7 @@ async function routeClickUpWebhookEvent(options = {}) {
|
|
|
10199
10240
|
const taskId = clickUpTaskIdFromPayload(options.payload || {});
|
|
10200
10241
|
return withClickUpTaskRouteLock(taskId, () => routeClickUpWebhookEventUnlocked(options));
|
|
10201
10242
|
}
|
|
10202
|
-
async function reconcileClickUpStartup({ config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, saveState = null, now = () => /* @__PURE__ */ new Date(), limit = CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT } = {}) {
|
|
10243
|
+
async function reconcileClickUpStartup({ config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, saveState = null, now = () => /* @__PURE__ */ new Date(), limit = CLICKUP_WEBHOOK_STARTUP_TASK_LIMIT } = {}) {
|
|
10203
10244
|
if (!config || !clickupClient?.listAssignedTasks) return { ok: true, skipped: true, reason: "clickup_task_listing_unavailable", assigned: 0, comments: 0 };
|
|
10204
10245
|
let authorizedUserId = "";
|
|
10205
10246
|
if (clickupClient?.getAuthorizedUser) {
|
|
@@ -10217,7 +10258,7 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
|
|
|
10217
10258
|
};
|
|
10218
10259
|
const lastWebhookMs = clickUpTimestampMs(mutableState.lastWebhookAt);
|
|
10219
10260
|
const ignored = new Set((config.routing?.ignoredStatuses || CLICKUP_WEBHOOK_TERMINAL_STATUSES).map(normalizeClickUpStatus));
|
|
10220
|
-
const routed = { assigned: 0, comments: 0, ignored: 0, errors: 0 };
|
|
10261
|
+
const routed = { assigned: 0, comments: 0, ignored: 0, errors: 0, undelivered: 0, tasks: [] };
|
|
10221
10262
|
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_started", lastWebhookAt: state.lastWebhookAt || null });
|
|
10222
10263
|
const listed = await clickupClient.listAssignedTasks({ assigneeId: config.routing.productManagerAssigneeId, limit });
|
|
10223
10264
|
const tasks = clickUpTaskListItems(listed).slice(0, limit);
|
|
@@ -10246,18 +10287,41 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
|
|
|
10246
10287
|
sessionExists,
|
|
10247
10288
|
createSession,
|
|
10248
10289
|
sendSessionEvent,
|
|
10290
|
+
verifySessionEventDelivery,
|
|
10249
10291
|
saveState: persistState,
|
|
10250
10292
|
now
|
|
10251
10293
|
});
|
|
10252
|
-
|
|
10294
|
+
const routeSummary = {
|
|
10295
|
+
taskId,
|
|
10296
|
+
action: result?.action || "unknown",
|
|
10297
|
+
ok: result?.ok === true,
|
|
10298
|
+
sessionId: result?.sessionId || result?.replacementSessionId || null,
|
|
10299
|
+
branch: result?.branch || null,
|
|
10300
|
+
worktree: result?.worktree || null,
|
|
10301
|
+
verification: result?.deliveryVerification?.method || result?.deliveryVerification?.reason || null,
|
|
10302
|
+
delivered: hasActionableClickUpRoute(result)
|
|
10303
|
+
};
|
|
10304
|
+
routed.tasks.push(routeSummary);
|
|
10305
|
+
appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_routed", ...routeSummary });
|
|
10306
|
+
if (routeSummary.delivered) {
|
|
10253
10307
|
routed.assigned += 1;
|
|
10308
|
+
} else if (result?.ok && result.action === "ignored") {
|
|
10309
|
+
routed.ignored += 1;
|
|
10254
10310
|
} else if (result?.ok === false) {
|
|
10255
10311
|
routed.errors += 1;
|
|
10312
|
+
routed.undelivered += 1;
|
|
10313
|
+
appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_undelivered", taskId, action: result?.action || "error", sessionId: routeSummary.sessionId, reason: result.reason || "startup_reconciliation_route_failed" });
|
|
10256
10314
|
if (!result.blockerTag) await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: result.reason || "startup_reconciliation_route_failed", source: "startup_reconciliation_task" });
|
|
10315
|
+
} else {
|
|
10316
|
+
routed.undelivered += 1;
|
|
10317
|
+
appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_undelivered", taskId, action: routeSummary.action, sessionId: routeSummary.sessionId, reason: "route_not_actionable" });
|
|
10257
10318
|
}
|
|
10258
10319
|
} catch (error) {
|
|
10259
10320
|
routed.errors += 1;
|
|
10321
|
+
routed.undelivered += 1;
|
|
10322
|
+
routed.tasks.push({ taskId, action: "error", ok: false, sessionId: null, branch: null, worktree: null, verification: error.message, delivered: false });
|
|
10260
10323
|
appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_failed", taskId, message: error.message });
|
|
10324
|
+
appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_task_undelivered", taskId, action: "error", sessionId: null, reason: "startup_reconciliation_task_failed" });
|
|
10261
10325
|
await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "startup_reconciliation_task_failed", source: "startup_reconciliation_task" });
|
|
10262
10326
|
}
|
|
10263
10327
|
if (!lastWebhookMs || !clickupClient?.getTaskComments) continue;
|
|
@@ -10280,6 +10344,7 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
|
|
|
10280
10344
|
sessionExists,
|
|
10281
10345
|
createSession,
|
|
10282
10346
|
sendSessionEvent,
|
|
10347
|
+
verifySessionEventDelivery,
|
|
10283
10348
|
saveState: persistState,
|
|
10284
10349
|
now
|
|
10285
10350
|
});
|
|
@@ -10291,8 +10356,12 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
|
|
|
10291
10356
|
await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "startup_reconciliation_comments_failed", source: "startup_reconciliation_comments" });
|
|
10292
10357
|
}
|
|
10293
10358
|
}
|
|
10359
|
+
const emptyPromptSessions = routed.tasks.filter((task) => task.delivered === false && task.sessionId);
|
|
10360
|
+
if (emptyPromptSessions.length > 0) {
|
|
10361
|
+
appendClickUpWebhookLocalLog(worktree, { type: "startup_reconciliation_validation_failed", tasks: emptyPromptSessions });
|
|
10362
|
+
}
|
|
10294
10363
|
clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_finished", ...routed });
|
|
10295
|
-
return { ok: routed.errors === 0, ...routed };
|
|
10364
|
+
return { ok: routed.errors === 0 && routed.undelivered === 0, ...routed, validation: { undelivered: routed.undelivered, emptyPromptSessions } };
|
|
10296
10365
|
}
|
|
10297
10366
|
function clickUpWebhookExpectedPath(config) {
|
|
10298
10367
|
try {
|
|
@@ -11410,6 +11479,19 @@ ${additionFragment}`;
|
|
|
11410
11479
|
delete agentConfig.tools.optima_prompt_workflow;
|
|
11411
11480
|
}
|
|
11412
11481
|
}
|
|
11482
|
+
if (id === "workflow_product_manager") {
|
|
11483
|
+
const humanRoleContext = buildHumanRoleContext({ roleIds: options.clickUpWebhookValidation?.config?.routing?.humanRoleClickUpIds });
|
|
11484
|
+
const humanRoleLines = formatHumanRoleContext(humanRoleContext);
|
|
11485
|
+
if (humanRoleLines) {
|
|
11486
|
+
agentConfig.prompt = `${agentConfig.prompt}
|
|
11487
|
+
|
|
11488
|
+
## Optima Human Role Fallback Registry
|
|
11489
|
+
|
|
11490
|
+
${humanRoleLines}
|
|
11491
|
+
|
|
11492
|
+
Use this Optima-provided fallback when the current task worktree lacks docs/core/humans.md; missing repo-local humans.md is not a hard blocker when this context or configured ClickUp IDs are present.`;
|
|
11493
|
+
}
|
|
11494
|
+
}
|
|
11413
11495
|
ourAgents[id] = agentConfig;
|
|
11414
11496
|
if (repoCfg.features?.debug_dumps !== false) {
|
|
11415
11497
|
const debugPath = path2.join(debugDir, `${id}.md`);
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
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
|
-
- Human role registry: resolve `CTO` and `PO` from `docs/core/humans.md
|
|
24
|
+
- Human role registry: resolve `CTO` and `PO` from `docs/core/humans.md` when present; if a task worktree lacks that file, use the Optima-provided human role fallback context and configured ClickUp IDs instead of blocking solely on the missing repo-local file. Use role identifiers in workflow text and automation config.
|
|
25
25
|
- Status actions are deterministic: `backlog` ignore, `plan` plan plus `Story Points`, test strategy, `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.
|
|
26
26
|
- 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
27
|
- `workflow_product_manager` is registered only when explicit ClickUp webhook mode is configured and the local webhook subscription state is active/valid.
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
- Routing comes from `docs/core/task_model.md`: `tiny` lightweight, `standard` bounded, `complex` decomposed; full mode supports all, mini mode refuses complex.
|
|
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
|
-
- Human role registry: resolve `CTO` and `PO` from `docs/core/humans.md
|
|
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
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.
|
|
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 task pulls remote once at start; subtasks trust parent local branch; PoC branches stay `poc/<clickup-task-id>`; subtasks PR to parent branch, 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,7 +20,7 @@
|
|
|
20
20
|
- Human-readable task/evidence summaries, validation results, AC coverage, documentation impact, blockers, reopen history, status-transition rationale, and final handoffs must be posted to linked ClickUp task/subtask comments or fields.
|
|
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
|
-
- Human role registry: resolve `CTO` and `PO` from `docs/core/humans.md
|
|
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
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
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`.
|
|
26
26
|
- 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.
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
- Sync human-readable task/evidence summaries, validation results, AC coverage, documentation impact, blockers, reopen history, status-transition rationale, and final handoffs to linked ClickUp task/subtask comments or fields.
|
|
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
|
-
- Human role registry: resolve `CTO` and `PO` from `docs/core/humans.md
|
|
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
18
|
- Validator/QA may merge validated subtask PRs into the parent branch/workspace without `CTO`/`PO` approval.
|
|
19
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`.
|
|
20
20
|
- Failed or conflicted subtask/parent merges return the affected ClickUp item to `in progress` for the coding owner.
|
package/docs/core/task_model.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
- Delivery task types: `Tarea`, `Bug`, `Doc`, `PoC`.
|
|
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
|
-
- Human role registry: resolve `CTO` and `PO` from `docs/core/humans.md
|
|
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
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.
|
|
16
16
|
- Branch-safe type slugs are lowercase ASCII: `tarea`, `bug`, `doc`, `poc`.
|
|
17
17
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
- Slices: `foundation`, `core`, `logic`, `ui`, `polish`, `qa`, `docs`.
|
|
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
|
-
- Human role registry: resolve `CTO` and `PO` from `docs/core/humans.md
|
|
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
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.
|
|
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.
|
|
@@ -18,7 +18,7 @@ We adhere to a strict test pyramid strategy to ensure 100% reliability.
|
|
|
18
18
|
- **Planning Participation:** Validator/QA participates during `plan` for test strategy, required new coverage, Playwright needs, regression scope, and documentation-validation expectations.
|
|
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
|
-
- **Human Role Registry:** Resolve `CTO` and `PO` from `docs/core/humans.md
|
|
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
|
- **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.
|
|
23
23
|
- **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
24
|
- **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.
|