@defend-tech/opencode-optima 0.1.46 → 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 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`; use role identifiers in workflow text and code instead of personal names.
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>`.
@@ -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`; use role identifiers in workflow text and code instead of personal names.
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`; use role IDs in workflow text, ClickUp comments, and automation config.
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
  }
@@ -10077,7 +10117,8 @@ async function withClickUpTaskRouteLock(taskId, operation) {
10077
10117
  async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, ensureTaskWorktree = ensureClickUpTaskWorktree, verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, saveState = null, now = () => /* @__PURE__ */ new Date(), host = os.hostname(), commentLedgerPath = clickUpCommentLedgerPath(worktree) } = {}) {
10078
10118
  const eventType = clickUpEventType(payload);
10079
10119
  const eventKey = clickUpWebhookEventKey(payload);
10080
- const remembered = rememberClickUpWebhookEvent(state, eventKey);
10120
+ const isStartupAssignmentReconciliation = payload?.startup_reconciliation === true && eventType === "taskAssigneeUpdated";
10121
+ const remembered = isStartupAssignmentReconciliation ? { duplicate: false, state } : rememberClickUpWebhookEvent(state, eventKey);
10081
10122
  if (remembered.duplicate) return { ok: true, action: "ignored", reason: "duplicate", eventKey };
10082
10123
  let stateToPersist = remembered.state;
10083
10124
  let commentLedgerKey = "";
@@ -10144,7 +10185,8 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
10144
10185
  evidence_path: `.optima/evidences/${branchSafeClickUpId(taskId)}/SUMMARY.md`
10145
10186
  };
10146
10187
  const metadataWithRouting = setClickUpTaskRoutingMetadata(existingMetadata, routingMetadata);
10147
- const prompt = formatClickUpWebhookPrompt({ eventType, taskId, payload, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath });
10188
+ const humanRoleContext = buildHumanRoleContext({ roleIds: config.routing?.humanRoleClickUpIds });
10189
+ const prompt = formatClickUpWebhookPrompt({ eventType, taskId, payload, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, humanRoleContext });
10148
10190
  if (!existingSessionId) {
10149
10191
  let pendingSessionId = getNestedMetadataValue(metadata, clickUpPendingSessionKey(config.routing.metadataKey)) || state.pendingSessions?.[taskId];
10150
10192
  if (!pendingSessionId) {
@@ -10191,7 +10233,7 @@ async function routeClickUpWebhookEvent(options = {}) {
10191
10233
  const taskId = clickUpTaskIdFromPayload(options.payload || {});
10192
10234
  return withClickUpTaskRouteLock(taskId, () => routeClickUpWebhookEventUnlocked(options));
10193
10235
  }
10194
- 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 } = {}) {
10195
10237
  if (!config || !clickupClient?.listAssignedTasks) return { ok: true, skipped: true, reason: "clickup_task_listing_unavailable", assigned: 0, comments: 0 };
10196
10238
  let authorizedUserId = "";
10197
10239
  if (clickupClient?.getAuthorizedUser) {
@@ -10209,7 +10251,7 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
10209
10251
  };
10210
10252
  const lastWebhookMs = clickUpTimestampMs(mutableState.lastWebhookAt);
10211
10253
  const ignored = new Set((config.routing?.ignoredStatuses || CLICKUP_WEBHOOK_TERMINAL_STATUSES).map(normalizeClickUpStatus));
10212
- const routed = { assigned: 0, comments: 0, ignored: 0, errors: 0 };
10254
+ const routed = { assigned: 0, comments: 0, ignored: 0, errors: 0, undelivered: 0, tasks: [] };
10213
10255
  clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_started", lastWebhookAt: state.lastWebhookAt || null });
10214
10256
  const listed = await clickupClient.listAssignedTasks({ assigneeId: config.routing.productManagerAssigneeId, limit });
10215
10257
  const tasks = clickUpTaskListItems(listed).slice(0, limit);
@@ -10238,18 +10280,41 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
10238
10280
  sessionExists,
10239
10281
  createSession,
10240
10282
  sendSessionEvent,
10283
+ verifySessionEventDelivery,
10241
10284
  saveState: persistState,
10242
10285
  now
10243
10286
  });
10244
- if (result?.ok && result.action !== "ignored") {
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) {
10245
10300
  routed.assigned += 1;
10301
+ } else if (result?.ok && result.action === "ignored") {
10302
+ routed.ignored += 1;
10246
10303
  } else if (result?.ok === false) {
10247
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" });
10248
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" });
10249
10311
  }
10250
10312
  } catch (error) {
10251
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 });
10252
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" });
10253
10318
  await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "startup_reconciliation_task_failed", source: "startup_reconciliation_task" });
10254
10319
  }
10255
10320
  if (!lastWebhookMs || !clickupClient?.getTaskComments) continue;
@@ -10272,6 +10337,7 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
10272
10337
  sessionExists,
10273
10338
  createSession,
10274
10339
  sendSessionEvent,
10340
+ verifySessionEventDelivery,
10275
10341
  saveState: persistState,
10276
10342
  now
10277
10343
  });
@@ -10283,8 +10349,12 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
10283
10349
  await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "startup_reconciliation_comments_failed", source: "startup_reconciliation_comments" });
10284
10350
  }
10285
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
+ }
10286
10356
  clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_finished", ...routed });
10287
- return { ok: routed.errors === 0, ...routed };
10357
+ return { ok: routed.errors === 0 && routed.undelivered === 0, ...routed, validation: { undelivered: routed.undelivered, emptyPromptSessions } };
10288
10358
  }
10289
10359
  function clickUpWebhookExpectedPath(config) {
10290
10360
  try {
@@ -11402,6 +11472,19 @@ ${additionFragment}`;
11402
11472
  delete agentConfig.tools.optima_prompt_workflow;
11403
11473
  }
11404
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
+ }
11405
11488
  ourAgents[id] = agentConfig;
11406
11489
  if (repoCfg.features?.debug_dumps !== false) {
11407
11490
  const debugPath = path2.join(debugDir, `${id}.md`);
@@ -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
  }
@@ -10084,7 +10124,8 @@ async function withClickUpTaskRouteLock(taskId, operation) {
10084
10124
  async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, ensureTaskWorktree = ensureClickUpTaskWorktree, verifySessionEventDelivery = verifyOpenCodeSessionEventDelivery, saveState = null, now = () => /* @__PURE__ */ new Date(), host = os.hostname(), commentLedgerPath = clickUpCommentLedgerPath(worktree) } = {}) {
10085
10125
  const eventType = clickUpEventType(payload);
10086
10126
  const eventKey = clickUpWebhookEventKey(payload);
10087
- const remembered = rememberClickUpWebhookEvent(state, eventKey);
10127
+ const isStartupAssignmentReconciliation = payload?.startup_reconciliation === true && eventType === "taskAssigneeUpdated";
10128
+ const remembered = isStartupAssignmentReconciliation ? { duplicate: false, state } : rememberClickUpWebhookEvent(state, eventKey);
10088
10129
  if (remembered.duplicate) return { ok: true, action: "ignored", reason: "duplicate", eventKey };
10089
10130
  let stateToPersist = remembered.state;
10090
10131
  let commentLedgerKey = "";
@@ -10151,7 +10192,8 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
10151
10192
  evidence_path: `.optima/evidences/${branchSafeClickUpId(taskId)}/SUMMARY.md`
10152
10193
  };
10153
10194
  const metadataWithRouting = setClickUpTaskRoutingMetadata(existingMetadata, routingMetadata);
10154
- const prompt = formatClickUpWebhookPrompt({ eventType, taskId, payload, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath });
10195
+ const humanRoleContext = buildHumanRoleContext({ roleIds: config.routing?.humanRoleClickUpIds });
10196
+ const prompt = formatClickUpWebhookPrompt({ eventType, taskId, payload, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath, humanRoleContext });
10155
10197
  if (!existingSessionId) {
10156
10198
  let pendingSessionId = getNestedMetadataValue(metadata, clickUpPendingSessionKey(config.routing.metadataKey)) || state.pendingSessions?.[taskId];
10157
10199
  if (!pendingSessionId) {
@@ -10198,7 +10240,7 @@ async function routeClickUpWebhookEvent(options = {}) {
10198
10240
  const taskId = clickUpTaskIdFromPayload(options.payload || {});
10199
10241
  return withClickUpTaskRouteLock(taskId, () => routeClickUpWebhookEventUnlocked(options));
10200
10242
  }
10201
- 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 } = {}) {
10202
10244
  if (!config || !clickupClient?.listAssignedTasks) return { ok: true, skipped: true, reason: "clickup_task_listing_unavailable", assigned: 0, comments: 0 };
10203
10245
  let authorizedUserId = "";
10204
10246
  if (clickupClient?.getAuthorizedUser) {
@@ -10216,7 +10258,7 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
10216
10258
  };
10217
10259
  const lastWebhookMs = clickUpTimestampMs(mutableState.lastWebhookAt);
10218
10260
  const ignored = new Set((config.routing?.ignoredStatuses || CLICKUP_WEBHOOK_TERMINAL_STATUSES).map(normalizeClickUpStatus));
10219
- const routed = { assigned: 0, comments: 0, ignored: 0, errors: 0 };
10261
+ const routed = { assigned: 0, comments: 0, ignored: 0, errors: 0, undelivered: 0, tasks: [] };
10220
10262
  clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_started", lastWebhookAt: state.lastWebhookAt || null });
10221
10263
  const listed = await clickupClient.listAssignedTasks({ assigneeId: config.routing.productManagerAssigneeId, limit });
10222
10264
  const tasks = clickUpTaskListItems(listed).slice(0, limit);
@@ -10245,18 +10287,41 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
10245
10287
  sessionExists,
10246
10288
  createSession,
10247
10289
  sendSessionEvent,
10290
+ verifySessionEventDelivery,
10248
10291
  saveState: persistState,
10249
10292
  now
10250
10293
  });
10251
- if (result?.ok && result.action !== "ignored") {
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) {
10252
10307
  routed.assigned += 1;
10308
+ } else if (result?.ok && result.action === "ignored") {
10309
+ routed.ignored += 1;
10253
10310
  } else if (result?.ok === false) {
10254
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" });
10255
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" });
10256
10318
  }
10257
10319
  } catch (error) {
10258
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 });
10259
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" });
10260
10325
  await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "startup_reconciliation_task_failed", source: "startup_reconciliation_task" });
10261
10326
  }
10262
10327
  if (!lastWebhookMs || !clickupClient?.getTaskComments) continue;
@@ -10279,6 +10344,7 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
10279
10344
  sessionExists,
10280
10345
  createSession,
10281
10346
  sendSessionEvent,
10347
+ verifySessionEventDelivery,
10282
10348
  saveState: persistState,
10283
10349
  now
10284
10350
  });
@@ -10290,8 +10356,12 @@ async function reconcileClickUpStartup({ config, state = {}, worktree = process.
10290
10356
  await applyClickUpBlockerTag({ clickupClient, worktree, taskId, reason: "startup_reconciliation_comments_failed", source: "startup_reconciliation_comments" });
10291
10357
  }
10292
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
+ }
10293
10363
  clickUpWebhookLifecycleLog(worktree, { type: "startup_reconciliation_finished", ...routed });
10294
- return { ok: routed.errors === 0, ...routed };
10364
+ return { ok: routed.errors === 0 && routed.undelivered === 0, ...routed, validation: { undelivered: routed.undelivered, emptyPromptSessions } };
10295
10365
  }
10296
10366
  function clickUpWebhookExpectedPath(config) {
10297
10367
  try {
@@ -11409,6 +11479,19 @@ ${additionFragment}`;
11409
11479
  delete agentConfig.tools.optima_prompt_workflow;
11410
11480
  }
11411
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
+ }
11412
11495
  ourAgents[id] = agentConfig;
11413
11496
  if (repoCfg.features?.debug_dumps !== false) {
11414
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`; use role identifiers in workflow text and automation config.
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`; use role identifiers in workflow text and automation config.
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`; use role identifiers in workflow text and automation config.
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`; use role identifiers in workflow text and automation config.
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.
@@ -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`; use role identifiers in workflow text and automation config.
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`; use role identifiers in workflow text and automation config.
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`; use role identifiers in workflow text and automation config.
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defend-tech/opencode-optima",
3
- "version": "0.1.46",
3
+ "version": "0.1.48",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+ssh://git@github.com/defend-tech/opencode-optima.git"