@defend-tech/opencode-optima 0.1.31 → 0.1.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/agents/workflow_product_manager.md +41 -58
- package/dist/index.js +113 -14
- package/dist/sanitize_cli.js +113 -14
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: ClickUp-first workflow orchestrator for Defend.tech delivery.
|
|
2
|
+
description: ClickUp-first workflow orchestrator for Defend.tech delivery.
|
|
3
3
|
mode: primary
|
|
4
4
|
tools:
|
|
5
5
|
optima_init: true
|
|
@@ -9,81 +9,64 @@ tools:
|
|
|
9
9
|
optima_run_workflow: true
|
|
10
10
|
optima_prompt_workflow: true
|
|
11
11
|
---
|
|
12
|
-
You are
|
|
12
|
+
You are Workflow_Product_Manager, Optima's ClickUp-first delivery orchestrator.
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## PM Model And Truth
|
|
15
15
|
|
|
16
|
-
- `workflow_product_manager` owns delivery
|
|
17
|
-
- `product_manager`
|
|
18
|
-
-
|
|
19
|
-
-
|
|
16
|
+
- Dual PMs: `workflow_product_manager` owns delivery ops--ClickUp status, routing/handoffs, decomposition, validation gates, Git worktree/branch/PR flow, evidence, closure. `product_manager` remains compatibility/product/planning PMA for requirements, SCRs, product truth, rough pre-estimation, and default/legacy orchestration when ClickUp-first is not opted in.
|
|
17
|
+
- `product_manager` without workflow never develops; convert dev asks into typed/routed ClickUp delivery tasks. Do not remove, shadow, or break `product_manager`; default Optima may still route there unless repo config says otherwise.
|
|
18
|
+
- ClickUp Docs/tasks are source of truth for intent, state, comments, assignment, validation, and closure. Use the ClickUp skill plus ClickUp MCP/tools for every read/write/comment/field/status/assignment/dashboard action.
|
|
19
|
+
- RULE NUMBER ONE: your operating objective is zero ClickUp tasks assigned to Workflow/Product Manager. If any ClickUp task is assigned to PM, you cannot stop working unless you have posted a human-visible ClickUp task comment, removed yourself from assignees, and assigned `CTO` plus `PO` or the next responsible owner.
|
|
20
|
+
- OpenCode session output is not visible to humans unless you post it to ClickUp. Before any pause/exit for stop, blocker, error, clarification, missing tool, or handoff rationale, post a task comment. If ClickUp writes are forbidden/unavailable, record the blocker/manual-sync payload in task/evidence and still post that blocker before stopping.
|
|
21
|
+
- Keep raw logs in evidence; ClickUp gets summaries, paths/links, or excerpts only. Post task/evidence summaries, validation, AC coverage, docs impact, blockers, reopen history, transition rationale, and final handoffs to linked comments/fields.
|
|
20
22
|
|
|
21
|
-
##
|
|
23
|
+
## Definition, Mirrors, Sessions
|
|
22
24
|
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
- Keep raw logs in evidence storage; ClickUp receives concise summaries, paths/links, or relevant excerpts only, never pasted raw logs wholesale.
|
|
30
|
-
- If the ClickUp skill/MCP is unavailable or ClickUp writes are forbidden, record the sync blocker and manual-sync payload in the task/evidence and post the blocker/manual-sync need as a ClickUp task comment before stopping.
|
|
31
|
-
- `Definition` is the plan contract. Link it in the ClickUp `Definition` custom field when native task-doc association is unavailable.
|
|
32
|
-
- Create Definition docs under ClickUp parent doc/page `2kxuv6pq-852/2kxuv6pq-2292`; never create new Definition docs at ClickUp Docs root.
|
|
33
|
-
- Definition document content must contain the complete plan/Definition, including scope, acceptance criteria, task decomposition, owners, test strategy, evidence expectations, handoff rules, and open risks; an empty doc or comment-only plan is invalid.
|
|
34
|
-
- Final Documentation is delivered behavior/technical/user documentation, separate from `Definition`; Validator/QA must fail validation when required final documentation is missing or outdated.
|
|
35
|
-
- Store `agent_metadata` JSON with session IDs per agent/type/task/subtask; update it whenever an agent session starts or changes responsibility.
|
|
36
|
-
- Evidence remains in git for now under `.optima/evidences/<task-id>/`.
|
|
37
|
-
- Use the framework doc page `Optima ClickUp-first Workflow Framework` as the steady-state operating guide when present.
|
|
38
|
-
- This agent is registered only when Optima's opt-in ClickUp webhook mode is configured and active/valid.
|
|
39
|
-
- Webhook mode wakes this agent only after signed `X-Signature` HMAC SHA-256 verification, duplicate suppression, Product Manager assignment/non-terminal status checks, and comment mention gating for `@Defend Tech Product Manager`.
|
|
40
|
-
- If ClickUp `agent_metadata` has no session id, Optima creates a session and writes the resulting `ses_...`; if a stored session is missing in OpenCode, Optima logs locally and comments on ClickUp with host, datetime, and missing id instead of creating a replacement.
|
|
25
|
+
- `.optima/tasks`, `.optima/docs/scrs`, and `.optima/evidences` are compatibility mirrors/evidence for local agents, tests, and audit. Update only from the correct task worktree; never treat mirrors as newer than ClickUp without explicit human confirmation. Evidence stays in git under `.optima/evidences/<task-id>/`.
|
|
26
|
+
- `Definition` is the plan contract. Link it in the ClickUp `Definition` custom field when native task-doc association is unavailable. Create Definition docs only under ClickUp parent doc/page `2kxuv6pq-852/2kxuv6pq-2292`; never root docs.
|
|
27
|
+
- Definition content must be complete: scope, AC, decomposition, owners, test strategy, evidence expectations, handoff rules, open risks. Empty docs or comment-only plans are invalid.
|
|
28
|
+
- Final Documentation is separate delivered behavior/technical/user documentation; Validator/QA fails validation when required final docs are missing or stale.
|
|
29
|
+
- Store `agent_metadata` JSON with session IDs per agent/type/task/subtask and update it whenever an agent session starts or responsibility changes.
|
|
30
|
+
- Use `Optima ClickUp-first Workflow Framework` as the steady-state guide when present.
|
|
41
31
|
|
|
42
|
-
##
|
|
32
|
+
## Webhook And Intake
|
|
43
33
|
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
- `
|
|
47
|
-
-
|
|
48
|
-
-
|
|
34
|
+
- Register only when Optima opt-in ClickUp webhook mode is configured and active/valid.
|
|
35
|
+
- Webhook wakeup requires signed `X-Signature` HMAC SHA-256 verification, duplicate suppression, PM assignment/non-terminal status checks, and comment mention gating for `@Defend Tech Product Manager`.
|
|
36
|
+
- If ClickUp `agent_metadata` lacks a session id, Optima creates a session and writes `ses_...`; if a stored session is missing in OpenCode, Optima logs locally and comments on ClickUp with host, datetime, and missing id instead of replacing it.
|
|
37
|
+
- Delivery task types: `Tarea`, `Bug`, `Doc`, `PoC`. Ignore unless converted/linked to delivery: `Idea`, legacy `Backlog` alias, `Hito`, `Nota de reunión`, `Respuesta del formulario`. Treat `Backlog` task type as `Idea`, not `backlog` status.
|
|
38
|
+
- Branch-safe slugs: `Tarea` -> `tarea`, `Bug` -> `bug`, `Doc` -> `doc`, `PoC` -> `poc`. Unknown task type: pause execution and ask PMA/PO for clarification.
|
|
49
39
|
|
|
50
40
|
## Status Actions
|
|
51
41
|
|
|
52
|
-
- `backlog`: ignore
|
|
53
|
-
- Human
|
|
54
|
-
- `plan`:
|
|
55
|
-
- `in progress`: execute through
|
|
56
|
-
- `validation`: route Tech Lead for architecture/code/PR/standards/repo-skill review and Validator/QA for tests, Playwright
|
|
57
|
-
- `merge`:
|
|
58
|
-
- `completed` / `Closed`: no
|
|
42
|
+
- `backlog`: ignore until prioritized.
|
|
43
|
+
- Human registry: resolve `CTO` and `PO` from `docs/core/humans.md`; use role IDs in workflow text, ClickUp comments, and automation config.
|
|
44
|
+
- `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.
|
|
45
|
+
- `in progress`: execute through assigned delivery agent or workflow runner.
|
|
46
|
+
- `validation`: route Tech Lead for architecture/code/PR/standards/repo-skill review and Validator/QA for tests, Playwright/regression/coverage/evidence/final-doc checks. Validator/QA may merge validated subtasks into parent branch without `CTO`/`PO`; validated parents stay in `validation`, assigned to `CTO`/`PO`, ready for approval.
|
|
47
|
+
- `merge`: only after `CTO`/`PO` move parent from `validation` to `merge`; Validator/QA then merges parent PR into `dev`. Any subtask/parent conflict or merge failure returns affected item to `in progress` for the coding owner.
|
|
48
|
+
- `completed` / `Closed`: no execution unless explicitly reopened.
|
|
59
49
|
|
|
60
|
-
## Git, Worktree,
|
|
50
|
+
## Git, Worktree, PR
|
|
61
51
|
|
|
62
|
-
-
|
|
52
|
+
- Principal workspace stays on `dev`; never use `main` for delivery and never push directly to `main`.
|
|
63
53
|
- Do not implement, plan, or write ClickUp task mirrors in the principal workspace. Use task-specific worktrees/branches for delivery and planning.
|
|
64
54
|
- To plan a ClickUp task, first create or reuse that task's branch/worktree with `optima_clickup_start_task`, then do Definition planning and local `.optima` mirror writes inside that task worktree.
|
|
65
55
|
- Do not update the principal `dev` workspace `.optima/tasks/current.md` for ClickUp task planning.
|
|
66
|
-
-
|
|
67
|
-
- Parent
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
- PoC branch format is always `poc/<clickup-task-id>` and remains there; do not merge PoC work to `dev` or `main` unless a later productization task explicitly converts it.
|
|
71
|
-
- Subtask PR target: the parent task branch; after successful subtask validation, Validator/QA merges it directly into that parent branch/workspace without human approval.
|
|
72
|
-
- Parent task PR target: `dev`; after Tech Lead and Validator/QA pass, `CTO`/`PO` approve by moving the parent task to `merge`, then Validator/QA attempts the merge.
|
|
73
|
-
- Release PR target: `dev` -> `main` only after explicit approval.
|
|
74
|
-
- Merge conflicts or failed merge attempts always move the affected task/subtask back to `in progress` for the coding owner.
|
|
56
|
+
- Unrelated active tasks in `.optima/tasks/current.md` must not block planning; move to/create the correct task worktree instead.
|
|
57
|
+
- Parent setup pulls remote once; after parent branch creation, subtasks can trust the parent local branch without continuous remote polling.
|
|
58
|
+
- Branches: parent `<clickup-task-type>/<parent-task-id>`; subtask `<clickup-task-type>/<parent-task-id>/<subtask-id>`; PoC always `poc/<clickup-task-id>` and remains there unless a later productization task converts it.
|
|
59
|
+
- PR targets: subtask -> parent branch, merged by Validator/QA after validation; parent -> `dev`, merged by Validator/QA only after Tech Lead + Validator/QA pass and `CTO`/`PO` move to `merge`; release -> `dev` to `main` only after explicit approval.
|
|
75
60
|
- Preserve user work and unrelated dirty files. Stop and ask if unexpected changes appear.
|
|
76
61
|
|
|
77
62
|
## Operating Style
|
|
78
63
|
|
|
79
|
-
- Orchestrate; do not silently implement specialist work yourself.
|
|
80
|
-
- Never abandon a PM-assigned task: keep working until
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
-
|
|
85
|
-
- Before assigning `CTO`/`PO` or any other next owner, remove the PM assignee from the ClickUp task and verify no task remains assigned to Workflow/Product Manager unless explicitly re-queued.
|
|
86
|
-
- Keep `.optima` mirrors updated for compatibility evidence until a future SCR removes local mirrors.
|
|
64
|
+
- Orchestrate; do not silently implement specialist work yourself. Delegate with ClickUp context, AC, evidence, sync duties, branch target, Story Points, Definition link, final Documentation needs, and validation requirements.
|
|
65
|
+
- Never abandon a PM-assigned task: keep working until unblocked and handed off, or post the stop/blocker/clarification/error as a ClickUp comment, remove Workflow/Product Manager, and assign `CTO` plus `PO` or next owner.
|
|
66
|
+
- On pickup, rewrite the ClickUp task description with the complete current description of what must be done; do not rely on status comments.
|
|
67
|
+
- At plan completion, rewrite the ClickUp task description again with the complete final plan/Definition, distinct from the plan comment.
|
|
68
|
+
- Estimate Story Points during `plan`, write them to ClickUp `Story Points`, and re-estimate when material plan changes alter scope/risk.
|
|
69
|
+
- Before assigning `CTO`/`PO` or any next owner, remove the PM assignee and verify no task remains assigned to Workflow/Product Manager unless explicitly re-queued.
|
|
87
70
|
- Final handoffs must include Summary, Work Performed, AC Coverage, Documentation Impact, Open Risks, Recommended Next Step, verification results, and commit/PR status.
|
|
88
71
|
|
|
89
72
|
<include:plugin:Agents_Common.md>
|
package/dist/index.js
CHANGED
|
@@ -7946,8 +7946,8 @@ var LEGACY_NOMADWORKS_DIRNAME = ".nomadworks";
|
|
|
7946
7946
|
var LEGACY_ORBITA_DIRNAME = ".orbita";
|
|
7947
7947
|
var LEGACY_STATICENG_DIRNAME = ".staticeng";
|
|
7948
7948
|
var OPTIMA_GITIGNORE_RULES = [
|
|
7949
|
-
".optima/",
|
|
7950
|
-
".optima/.config/",
|
|
7949
|
+
".optima/tasks/current.md",
|
|
7950
|
+
".optima/.config/runtime/*",
|
|
7951
7951
|
".optima/**/.env",
|
|
7952
7952
|
".optima/**/.env.*",
|
|
7953
7953
|
".optima/**/*.env",
|
|
@@ -8497,9 +8497,11 @@ function buildClickUpSummaryPayload({ summaryMarkdown = "", summaryPath = "", ta
|
|
|
8497
8497
|
["Open Risks", sectionValue(summarySections, ["Open Risks"])],
|
|
8498
8498
|
["Recommended Next Step", sectionValue(summarySections, ["Recommended Next Step"])]
|
|
8499
8499
|
];
|
|
8500
|
+
const deliveryPath = summaryPath.includes("docs/delivery/") ? summaryPath : "";
|
|
8500
8501
|
const contextParts = [
|
|
8501
8502
|
branchValue ? `- Branch: ${compactMarkdownValue(branchValue)}` : null,
|
|
8502
8503
|
worktreeValue ? `- Worktree: ${compactMarkdownValue(worktreeValue)}` : null,
|
|
8504
|
+
deliveryPath ? `- Delivery evidence: ${compactMarkdownValue(deliveryPath)}` : null,
|
|
8503
8505
|
prValue ? `- PR: ${compactMarkdownValue(prValue)}` : null
|
|
8504
8506
|
].filter(Boolean);
|
|
8505
8507
|
const comment = [
|
|
@@ -8524,7 +8526,8 @@ function buildClickUpSummaryPayload({ summaryMarkdown = "", summaryPath = "", ta
|
|
|
8524
8526
|
recommended_next_step: compactMarkdownValue(sectionValue(summarySections, ["Recommended Next Step"])),
|
|
8525
8527
|
branch: compactMarkdownValue(branchValue),
|
|
8526
8528
|
worktree: compactMarkdownValue(worktreeValue),
|
|
8527
|
-
pr: compactMarkdownValue(prValue)
|
|
8529
|
+
pr: compactMarkdownValue(prValue),
|
|
8530
|
+
delivery_evidence_path: compactMarkdownValue(deliveryPath)
|
|
8528
8531
|
}
|
|
8529
8532
|
},
|
|
8530
8533
|
parsed: { summary: summarySections, task: taskSections }
|
|
@@ -8535,6 +8538,60 @@ function deriveClickUpWorktree({ baseWorktree = "", taskId, taskType, parentTask
|
|
|
8535
8538
|
const root = baseWorktree || process.cwd();
|
|
8536
8539
|
return path2.join(path2.dirname(root), `${path2.basename(root)}-${branch.replace(/\//g, "-")}`);
|
|
8537
8540
|
}
|
|
8541
|
+
function clickUpCustomFieldValue(task = {}, names = []) {
|
|
8542
|
+
const fields = Array.isArray(task.custom_fields) ? task.custom_fields : [];
|
|
8543
|
+
const wanted = new Set(names.map((name) => normalizeLooseToken(name)));
|
|
8544
|
+
const field = fields.find((item) => wanted.has(normalizeLooseToken(item?.name || item?.id || "")));
|
|
8545
|
+
return field?.value ?? "";
|
|
8546
|
+
}
|
|
8547
|
+
function clickUpTaskType(task = {}, fallback = "Tarea") {
|
|
8548
|
+
return task.type || task.task_type || task.taskType || clickUpCustomFieldValue(task, ["Type", "Tipo", "Task Type"]) || fallback;
|
|
8549
|
+
}
|
|
8550
|
+
function clickUpParentTaskId(task = {}) {
|
|
8551
|
+
return task.parent || task.parent_id || task.parentId || task.parent_task_id || task.parentTaskId || "";
|
|
8552
|
+
}
|
|
8553
|
+
function deliveryEvidencePathForClickUpTask(taskId) {
|
|
8554
|
+
return `docs/delivery/${branchSafeClickUpId(taskId)}/SUMMARY.md`;
|
|
8555
|
+
}
|
|
8556
|
+
function metadataTaskRouting(metadata = {}) {
|
|
8557
|
+
return isPlainObject(metadata.task) ? metadata.task : {};
|
|
8558
|
+
}
|
|
8559
|
+
function safeExistingClickUpWorktree({ metadata = {}, branch = "" } = {}) {
|
|
8560
|
+
const taskMetadata = metadataTaskRouting(metadata);
|
|
8561
|
+
const existingWorktree = typeof taskMetadata.worktree === "string" ? taskMetadata.worktree.trim() : "";
|
|
8562
|
+
const existingBranch = typeof taskMetadata.branch === "string" ? taskMetadata.branch.trim() : "";
|
|
8563
|
+
if (!existingWorktree || !path2.isAbsolute(existingWorktree) || !fs2.existsSync(existingWorktree)) return null;
|
|
8564
|
+
try {
|
|
8565
|
+
const stat = fs2.statSync(existingWorktree);
|
|
8566
|
+
if (!stat.isDirectory()) return null;
|
|
8567
|
+
if (branch && existingBranch && existingBranch !== branch) return null;
|
|
8568
|
+
return { branch: existingBranch || branch, worktree: path2.resolve(existingWorktree), reused: true };
|
|
8569
|
+
} catch {
|
|
8570
|
+
return null;
|
|
8571
|
+
}
|
|
8572
|
+
}
|
|
8573
|
+
function ensureClickUpTaskWorktree({ baseWorktree = "", taskId, taskType = "Tarea", parentTaskId = "", subtaskId = "", existingMetadata = {}, runGitFn = runGit, allowNonGitFallback = false } = {}) {
|
|
8574
|
+
const effectiveParent = parentTaskId || taskId;
|
|
8575
|
+
const branch = deriveClickUpBranchName({ taskType, parentTaskId: effectiveParent, subtaskId, taskId });
|
|
8576
|
+
const existing = safeExistingClickUpWorktree({ metadata: existingMetadata, branch });
|
|
8577
|
+
if (existing) return { ...existing, branch };
|
|
8578
|
+
const worktreePath = deriveClickUpWorktree({ baseWorktree, taskId, taskType, parentTaskId: effectiveParent, subtaskId });
|
|
8579
|
+
if (fs2.existsSync(worktreePath)) return { branch, worktree: path2.resolve(worktreePath), reused: true };
|
|
8580
|
+
if (allowNonGitFallback) {
|
|
8581
|
+
fs2.mkdirSync(worktreePath, { recursive: true });
|
|
8582
|
+
return { branch, worktree: path2.resolve(worktreePath), reused: false, fallback: "non_git" };
|
|
8583
|
+
}
|
|
8584
|
+
const startPoint = (() => {
|
|
8585
|
+
try {
|
|
8586
|
+
runGitFn(baseWorktree, ["rev-parse", "--verify", "dev"]);
|
|
8587
|
+
return "dev";
|
|
8588
|
+
} catch {
|
|
8589
|
+
return "origin/dev";
|
|
8590
|
+
}
|
|
8591
|
+
})();
|
|
8592
|
+
runGitFn(baseWorktree, ["worktree", "add", "-b", branch, worktreePath, startPoint]);
|
|
8593
|
+
return { branch, worktree: path2.resolve(worktreePath), reused: false, startPoint };
|
|
8594
|
+
}
|
|
8538
8595
|
function normalizeClickUpDefinitionDocParent(parent = {}) {
|
|
8539
8596
|
const docId = String(parent.doc_id || parent.docId || CLICKUP_DEFINITION_DOC_PARENT.doc_id).trim();
|
|
8540
8597
|
const pageId = String(parent.page_id || parent.pageId || CLICKUP_DEFINITION_DOC_PARENT.page_id).trim();
|
|
@@ -8564,6 +8621,7 @@ function buildClickUpStartTaskPayload({ taskId, taskType = "Tarea", parentTaskId
|
|
|
8564
8621
|
worktree,
|
|
8565
8622
|
mirror_task_path: `.optima/tasks/${branchSafeClickUpId(taskId || subtaskId || effectiveParent)}.md`,
|
|
8566
8623
|
evidence_path: `.optima/evidences/${branchSafeClickUpId(taskId || subtaskId || effectiveParent)}/SUMMARY.md`,
|
|
8624
|
+
delivery_evidence_path: deliveryEvidencePathForClickUpTask(taskId || subtaskId || effectiveParent),
|
|
8567
8625
|
...agentMetadata
|
|
8568
8626
|
}
|
|
8569
8627
|
});
|
|
@@ -8574,7 +8632,8 @@ function buildClickUpStartTaskPayload({ taskId, taskType = "Tarea", parentTaskId
|
|
|
8574
8632
|
required_first_actions: [
|
|
8575
8633
|
"Create or reuse the task-specific branch/worktree before planning or writing local .optima mirrors.",
|
|
8576
8634
|
`Use branch ${branch} from dev and worktree ${worktree} for this task workspace.`,
|
|
8577
|
-
"Write planning state, .optima/tasks/current.md, task mirrors, and evidence only inside the task worktree.",
|
|
8635
|
+
"Write planning state, .optima/tasks/current.md, task mirrors, and local evidence only inside the task worktree.",
|
|
8636
|
+
`Write merge-trackable final evidence under ${deliveryEvidencePathForClickUpTask(taskId || subtaskId || effectiveParent)}; .optima remains local mirror/staging.`,
|
|
8578
8637
|
"Do not update the principal dev workspace .optima/tasks/current.md for ClickUp task planning.",
|
|
8579
8638
|
"If another active task is present in current.md, move to or create this task worktree instead of stopping."
|
|
8580
8639
|
],
|
|
@@ -8587,13 +8646,14 @@ function buildClickUpStartTaskPayload({ taskId, taskType = "Tarea", parentTaskId
|
|
|
8587
8646
|
mirror: {
|
|
8588
8647
|
taskPath: `.optima/tasks/${branchSafeClickUpId(taskId || subtaskId || effectiveParent)}.md`,
|
|
8589
8648
|
evidenceSummaryPath: `.optima/evidences/${branchSafeClickUpId(taskId || subtaskId || effectiveParent)}/SUMMARY.md`,
|
|
8649
|
+
deliveryEvidencePath: deliveryEvidencePathForClickUpTask(taskId || subtaskId || effectiveParent),
|
|
8590
8650
|
workspace: "task_worktree_only",
|
|
8591
8651
|
principalWorkspaceCurrentMd: "do_not_update",
|
|
8592
8652
|
currentMdConflictPolicy: "move_to_or_create_task_worktree",
|
|
8593
8653
|
wouldCreate: true
|
|
8594
8654
|
},
|
|
8595
8655
|
clickup: {
|
|
8596
|
-
comment: `Starting task from dev. Branch: ${branch}. Worktree: ${worktree}.`,
|
|
8656
|
+
comment: `Starting task from dev. Branch: ${branch}. Worktree: ${worktree}. Delivery evidence: ${deliveryEvidencePathForClickUpTask(taskId || subtaskId || effectiveParent)}.`,
|
|
8597
8657
|
description,
|
|
8598
8658
|
fields: {
|
|
8599
8659
|
Definition: definitionContent,
|
|
@@ -9306,6 +9366,14 @@ function recordClickUpCommentVersionProcessed({ ledgerPath, key, taskId, eventTy
|
|
|
9306
9366
|
function clickUpTaskIdFromPayload(payload = {}) {
|
|
9307
9367
|
return String(payload.task_id || payload.taskId || payload.task?.id || payload.task?.task_id || "").trim();
|
|
9308
9368
|
}
|
|
9369
|
+
function clickUpTaskName(task = {}) {
|
|
9370
|
+
return String(task?.name || "").trim();
|
|
9371
|
+
}
|
|
9372
|
+
function formatClickUpSessionTitle({ taskId, payloadTask, task } = {}) {
|
|
9373
|
+
const id = String(taskId || "").trim();
|
|
9374
|
+
const name = clickUpTaskName(payloadTask) || clickUpTaskName(task) || "ClickUp PM";
|
|
9375
|
+
return `[${id}]: ${name}`;
|
|
9376
|
+
}
|
|
9309
9377
|
function clickUpEventType(payload = {}) {
|
|
9310
9378
|
return String(payload.event || payload.event_type || payload.eventType || "").trim();
|
|
9311
9379
|
}
|
|
@@ -9376,6 +9444,11 @@ function clickUpPendingSessionKey(metadataKey) {
|
|
|
9376
9444
|
function mergeClickUpPendingSessionMetadata(existing, metadataKey, sessionId) {
|
|
9377
9445
|
return mergeClickUpSessionMetadata(existing, clickUpPendingSessionKey(metadataKey), sessionId);
|
|
9378
9446
|
}
|
|
9447
|
+
function setClickUpTaskRoutingMetadata(existing, routing = {}) {
|
|
9448
|
+
const root = normalizeAgentMetadataJson(existing);
|
|
9449
|
+
root.task = { ...isPlainObject(root.task) ? root.task : {}, ...routing };
|
|
9450
|
+
return JSON.stringify(sortJsonValue(root), null, 2);
|
|
9451
|
+
}
|
|
9379
9452
|
function setClickUpSessionMetadata(existing, metadataKey, sessionId) {
|
|
9380
9453
|
const root = normalizeAgentMetadataJson(existing);
|
|
9381
9454
|
const parts = String(metadataKey || CLICKUP_PM_METADATA_KEY).split(".").filter(Boolean);
|
|
@@ -9470,16 +9543,20 @@ async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, direct
|
|
|
9470
9543
|
if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, fetchImpl });
|
|
9471
9544
|
throw new Error("OpenCode client does not expose session.prompt or session.promptAsync.");
|
|
9472
9545
|
}
|
|
9473
|
-
function formatClickUpWebhookPrompt({ eventType, taskId, payload }) {
|
|
9546
|
+
function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", worktree = "", deliveryEvidencePath = "" }) {
|
|
9474
9547
|
const comment = clickUpCommentFromPayload(payload);
|
|
9475
9548
|
const commentText = clickUpCommentText(comment).trim();
|
|
9476
9549
|
return [
|
|
9477
9550
|
"[ClickUp Webhook Event]",
|
|
9478
9551
|
`Event: ${eventType}`,
|
|
9479
9552
|
`Task ID: ${taskId}`,
|
|
9553
|
+
branch ? `Branch: ${branch}` : null,
|
|
9554
|
+
worktree ? `Worktree: ${worktree}` : null,
|
|
9555
|
+
deliveryEvidencePath ? `Delivery evidence path: ${deliveryEvidencePath}` : null,
|
|
9480
9556
|
commentText ? `Comment: ${commentText}` : null,
|
|
9481
9557
|
"",
|
|
9482
|
-
"Handle this ClickUp Product Manager event using the ClickUp-first workflow rules. Do not assume broad chat routing; this event passed Optima webhook gates."
|
|
9558
|
+
"Handle this ClickUp Product Manager event using the ClickUp-first workflow rules. Do not assume broad chat routing; this event passed Optima webhook gates.",
|
|
9559
|
+
deliveryEvidencePath ? `Final merge-trackable evidence must be written under ${deliveryEvidencePath}; use .optima only as local mirror/staging.` : null
|
|
9483
9560
|
].filter((part) => part !== null).join("\n");
|
|
9484
9561
|
}
|
|
9485
9562
|
function appendClickUpWebhookLocalLog(worktree, entry) {
|
|
@@ -9658,7 +9735,7 @@ async function withClickUpTaskRouteLock(taskId, operation) {
|
|
|
9658
9735
|
if (activeClickUpTaskRoutes.get(key) === current) activeClickUpTaskRoutes.delete(key);
|
|
9659
9736
|
}
|
|
9660
9737
|
}
|
|
9661
|
-
async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, saveState = null, now = () => /* @__PURE__ */ new Date(), host = os.hostname(), commentLedgerPath = clickUpCommentLedgerPath(worktree) } = {}) {
|
|
9738
|
+
async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, ensureTaskWorktree = ensureClickUpTaskWorktree, saveState = null, now = () => /* @__PURE__ */ new Date(), host = os.hostname(), commentLedgerPath = clickUpCommentLedgerPath(worktree) } = {}) {
|
|
9662
9739
|
const eventType = clickUpEventType(payload);
|
|
9663
9740
|
const eventKey = clickUpWebhookEventKey(payload);
|
|
9664
9741
|
const remembered = rememberClickUpWebhookEvent(state, eventKey);
|
|
@@ -9702,14 +9779,35 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
9702
9779
|
const latestTask = await clickupClient.getTask(taskId);
|
|
9703
9780
|
if (latestTask) task = latestTask;
|
|
9704
9781
|
}
|
|
9782
|
+
const sessionTitle = formatClickUpSessionTitle({ taskId, payloadTask: payload.task, task });
|
|
9705
9783
|
const existingMetadata = clickUpTaskAgentMetadata(task, config.routing.metadataFieldId);
|
|
9706
9784
|
const metadata = normalizeAgentMetadataJson(existingMetadata);
|
|
9707
9785
|
const existingSessionId = getNestedMetadataValue(metadata, config.routing.metadataKey);
|
|
9708
|
-
const
|
|
9786
|
+
const taskType = clickUpTaskType(task);
|
|
9787
|
+
const parentTaskId = clickUpParentTaskId(task) || taskId;
|
|
9788
|
+
const subtaskId = parentTaskId && parentTaskId !== taskId ? taskId : "";
|
|
9789
|
+
let taskRoute;
|
|
9790
|
+
try {
|
|
9791
|
+
taskRoute = ensureTaskWorktree({ baseWorktree: config.basePath, taskId, taskType, parentTaskId, subtaskId, existingMetadata: metadata, allowNonGitFallback: process.env.NODE_ENV === "test" });
|
|
9792
|
+
} catch (error) {
|
|
9793
|
+
const message = `Optima webhook could not create or reuse a task worktree for ${taskId}: ${error.message}`;
|
|
9794
|
+
appendClickUpWebhookLocalLog(worktree, { type: "task_worktree_failed", taskId, message });
|
|
9795
|
+
if (typeof clickupClient?.postTaskComment === "function") await clickupClient.postTaskComment({ taskId, comment: message });
|
|
9796
|
+
return { ok: false, action: "error", reason: "task_worktree_failed", taskId, message };
|
|
9797
|
+
}
|
|
9798
|
+
const deliveryEvidencePath = deliveryEvidencePathForClickUpTask(taskId);
|
|
9799
|
+
const routingMetadata = {
|
|
9800
|
+
branch: taskRoute.branch,
|
|
9801
|
+
worktree: taskRoute.worktree,
|
|
9802
|
+
delivery_evidence_path: deliveryEvidencePath,
|
|
9803
|
+
evidence_path: `.optima/evidences/${branchSafeClickUpId(taskId)}/SUMMARY.md`
|
|
9804
|
+
};
|
|
9805
|
+
const metadataWithRouting = setClickUpTaskRoutingMetadata(existingMetadata, routingMetadata);
|
|
9806
|
+
const prompt = formatClickUpWebhookPrompt({ eventType, taskId, payload, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath });
|
|
9709
9807
|
if (!existingSessionId) {
|
|
9710
9808
|
let pendingSessionId = getNestedMetadataValue(metadata, clickUpPendingSessionKey(config.routing.metadataKey)) || state.pendingSessions?.[taskId];
|
|
9711
9809
|
if (!pendingSessionId) {
|
|
9712
|
-
pendingSessionId = await createSession(openCodeClient, { title:
|
|
9810
|
+
pendingSessionId = await createSession(openCodeClient, { title: sessionTitle, directory: taskRoute.worktree, agent: config.routing.targetAgent });
|
|
9713
9811
|
if (!String(pendingSessionId || "").startsWith("ses_")) return { ok: false, action: "error", reason: "session_create_failed", taskId };
|
|
9714
9812
|
if (saveState) {
|
|
9715
9813
|
saveState({
|
|
@@ -9718,14 +9816,14 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
9718
9816
|
});
|
|
9719
9817
|
}
|
|
9720
9818
|
}
|
|
9721
|
-
const pendingMetadata = mergeClickUpPendingSessionMetadata(
|
|
9819
|
+
const pendingMetadata = mergeClickUpPendingSessionMetadata(metadataWithRouting, config.routing.metadataKey, pendingSessionId);
|
|
9722
9820
|
try {
|
|
9723
9821
|
await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: pendingMetadata });
|
|
9724
9822
|
} catch (error) {
|
|
9725
9823
|
appendClickUpWebhookLocalLog(worktree, { type: "pending_session_metadata_failed", taskId, sessionId: pendingSessionId, message: error.message });
|
|
9726
9824
|
throw error;
|
|
9727
9825
|
}
|
|
9728
|
-
await sendSessionEvent(openCodeClient, { sessionId: pendingSessionId, agent: config.routing.targetAgent, text: prompt, directory:
|
|
9826
|
+
await sendSessionEvent(openCodeClient, { sessionId: pendingSessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl });
|
|
9729
9827
|
const nextMetadata = clearClickUpPendingSessionMetadata(setClickUpSessionMetadata(pendingMetadata, config.routing.metadataKey, pendingSessionId), config.routing.metadataKey);
|
|
9730
9828
|
await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: nextMetadata });
|
|
9731
9829
|
const { [taskId]: _completedPending, ...remainingPending } = stateToPersist.pendingSessions || {};
|
|
@@ -9734,7 +9832,8 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
9734
9832
|
}
|
|
9735
9833
|
const sessionId = String(existingSessionId);
|
|
9736
9834
|
if (await sessionExists(openCodeClient, sessionId)) {
|
|
9737
|
-
|
|
9835
|
+
if (typeof clickupClient?.updateTaskMetadata === "function") await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: metadataWithRouting });
|
|
9836
|
+
await sendSessionEvent(openCodeClient, { sessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl });
|
|
9738
9837
|
return finish({ ok: true, action: "sent_to_existing_session", taskId, sessionId, eventKey });
|
|
9739
9838
|
}
|
|
9740
9839
|
const at = now().toISOString();
|
|
@@ -11506,7 +11605,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
|
|
|
11506
11605
|
}
|
|
11507
11606
|
};
|
|
11508
11607
|
}
|
|
11509
|
-
OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, BUNDLE_ASSETS_DIR, CLICKUP_DEFINITION_DOC_PARENT, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentLedgerKey, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, createClickUpApiClient, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, readClickUpCommentLedger, readClickUpWebhookState, recordClickUpCommentVersionProcessed, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, stableClickUpCommentVersionMarker, startClickUpWebhookListener, validateClickUpWebhookState, verifyClickUpSignature, writeClickUpWebhookState, decideClickUpStatusAction, deriveClickUpBranchName, deriveClickUpPendingSubtaskBranch, deriveClickUpPrTarget, deriveClickUpWorktree, determineClickUpMergeAuthority, ensureOptimaGitignoreRules, explicitSafeInputWorktree, finalApprovalAssignees, formatValidationResult, isIgnoredClickUpTaskType, isOptimaPluginPackageWorktree, isSafeWritableDirectory, loadHumansRegistry, mergeClickUpAgentMetadata, mergeClickUpSessionMetadata, migrateLegacyOptimaLayout, normalizeAgentMetadataJson, normalizeClickUpStatus, normalizeClickUpTaskType, normalizePromptResponseParts, normalizeWorkflowTaskPath, parseClickUpSubtasksMarkdown, parseHumansRegistry, parseMarkdownArtifact, parseMarkdownSections, preEstimateClickUpWork, readMarkdownArtifact, resolveHumanRoles, resolveSafeWorktree, safeWorktreeOrFailure, stripRawLogSections, validateMainWorkspaceBranchSafety, workflowFinalMessageFromPromptResponse };
|
|
11608
|
+
OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, BUNDLE_ASSETS_DIR, CLICKUP_DEFINITION_DOC_PARENT, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentLedgerKey, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, createClickUpApiClient, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, readClickUpCommentLedger, readClickUpWebhookState, recordClickUpCommentVersionProcessed, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, stableClickUpCommentVersionMarker, startClickUpWebhookListener, validateClickUpWebhookState, verifyClickUpSignature, writeClickUpWebhookState, decideClickUpStatusAction, deriveClickUpBranchName, deriveClickUpPendingSubtaskBranch, deriveClickUpPrTarget, deriveClickUpWorktree, determineClickUpMergeAuthority, ensureOptimaGitignoreRules, explicitSafeInputWorktree, finalApprovalAssignees, formatValidationResult, isIgnoredClickUpTaskType, isOptimaPluginPackageWorktree, isSafeWritableDirectory, loadHumansRegistry, mergeClickUpAgentMetadata, mergeClickUpSessionMetadata, migrateLegacyOptimaLayout, normalizeAgentMetadataJson, normalizeClickUpStatus, normalizeClickUpTaskType, normalizePromptResponseParts, normalizeWorkflowTaskPath, parseClickUpSubtasksMarkdown, parseHumansRegistry, parseMarkdownArtifact, parseMarkdownSections, preEstimateClickUpWork, readMarkdownArtifact, resolveHumanRoles, resolveSafeWorktree, safeWorktreeOrFailure, stripRawLogSections, validateMainWorkspaceBranchSafety, workflowFinalMessageFromPromptResponse };
|
|
11510
11609
|
export {
|
|
11511
11610
|
OptimaPlugin as default
|
|
11512
11611
|
};
|
package/dist/sanitize_cli.js
CHANGED
|
@@ -7953,8 +7953,8 @@ var LEGACY_NOMADWORKS_DIRNAME = ".nomadworks";
|
|
|
7953
7953
|
var LEGACY_ORBITA_DIRNAME = ".orbita";
|
|
7954
7954
|
var LEGACY_STATICENG_DIRNAME = ".staticeng";
|
|
7955
7955
|
var OPTIMA_GITIGNORE_RULES = [
|
|
7956
|
-
".optima/",
|
|
7957
|
-
".optima/.config/",
|
|
7956
|
+
".optima/tasks/current.md",
|
|
7957
|
+
".optima/.config/runtime/*",
|
|
7958
7958
|
".optima/**/.env",
|
|
7959
7959
|
".optima/**/.env.*",
|
|
7960
7960
|
".optima/**/*.env",
|
|
@@ -8504,9 +8504,11 @@ function buildClickUpSummaryPayload({ summaryMarkdown = "", summaryPath = "", ta
|
|
|
8504
8504
|
["Open Risks", sectionValue(summarySections, ["Open Risks"])],
|
|
8505
8505
|
["Recommended Next Step", sectionValue(summarySections, ["Recommended Next Step"])]
|
|
8506
8506
|
];
|
|
8507
|
+
const deliveryPath = summaryPath.includes("docs/delivery/") ? summaryPath : "";
|
|
8507
8508
|
const contextParts = [
|
|
8508
8509
|
branchValue ? `- Branch: ${compactMarkdownValue(branchValue)}` : null,
|
|
8509
8510
|
worktreeValue ? `- Worktree: ${compactMarkdownValue(worktreeValue)}` : null,
|
|
8511
|
+
deliveryPath ? `- Delivery evidence: ${compactMarkdownValue(deliveryPath)}` : null,
|
|
8510
8512
|
prValue ? `- PR: ${compactMarkdownValue(prValue)}` : null
|
|
8511
8513
|
].filter(Boolean);
|
|
8512
8514
|
const comment = [
|
|
@@ -8531,7 +8533,8 @@ function buildClickUpSummaryPayload({ summaryMarkdown = "", summaryPath = "", ta
|
|
|
8531
8533
|
recommended_next_step: compactMarkdownValue(sectionValue(summarySections, ["Recommended Next Step"])),
|
|
8532
8534
|
branch: compactMarkdownValue(branchValue),
|
|
8533
8535
|
worktree: compactMarkdownValue(worktreeValue),
|
|
8534
|
-
pr: compactMarkdownValue(prValue)
|
|
8536
|
+
pr: compactMarkdownValue(prValue),
|
|
8537
|
+
delivery_evidence_path: compactMarkdownValue(deliveryPath)
|
|
8535
8538
|
}
|
|
8536
8539
|
},
|
|
8537
8540
|
parsed: { summary: summarySections, task: taskSections }
|
|
@@ -8542,6 +8545,60 @@ function deriveClickUpWorktree({ baseWorktree = "", taskId, taskType, parentTask
|
|
|
8542
8545
|
const root = baseWorktree || process.cwd();
|
|
8543
8546
|
return path2.join(path2.dirname(root), `${path2.basename(root)}-${branch.replace(/\//g, "-")}`);
|
|
8544
8547
|
}
|
|
8548
|
+
function clickUpCustomFieldValue(task = {}, names = []) {
|
|
8549
|
+
const fields = Array.isArray(task.custom_fields) ? task.custom_fields : [];
|
|
8550
|
+
const wanted = new Set(names.map((name) => normalizeLooseToken(name)));
|
|
8551
|
+
const field = fields.find((item) => wanted.has(normalizeLooseToken(item?.name || item?.id || "")));
|
|
8552
|
+
return field?.value ?? "";
|
|
8553
|
+
}
|
|
8554
|
+
function clickUpTaskType(task = {}, fallback = "Tarea") {
|
|
8555
|
+
return task.type || task.task_type || task.taskType || clickUpCustomFieldValue(task, ["Type", "Tipo", "Task Type"]) || fallback;
|
|
8556
|
+
}
|
|
8557
|
+
function clickUpParentTaskId(task = {}) {
|
|
8558
|
+
return task.parent || task.parent_id || task.parentId || task.parent_task_id || task.parentTaskId || "";
|
|
8559
|
+
}
|
|
8560
|
+
function deliveryEvidencePathForClickUpTask(taskId) {
|
|
8561
|
+
return `docs/delivery/${branchSafeClickUpId(taskId)}/SUMMARY.md`;
|
|
8562
|
+
}
|
|
8563
|
+
function metadataTaskRouting(metadata = {}) {
|
|
8564
|
+
return isPlainObject(metadata.task) ? metadata.task : {};
|
|
8565
|
+
}
|
|
8566
|
+
function safeExistingClickUpWorktree({ metadata = {}, branch = "" } = {}) {
|
|
8567
|
+
const taskMetadata = metadataTaskRouting(metadata);
|
|
8568
|
+
const existingWorktree = typeof taskMetadata.worktree === "string" ? taskMetadata.worktree.trim() : "";
|
|
8569
|
+
const existingBranch = typeof taskMetadata.branch === "string" ? taskMetadata.branch.trim() : "";
|
|
8570
|
+
if (!existingWorktree || !path2.isAbsolute(existingWorktree) || !fs2.existsSync(existingWorktree)) return null;
|
|
8571
|
+
try {
|
|
8572
|
+
const stat = fs2.statSync(existingWorktree);
|
|
8573
|
+
if (!stat.isDirectory()) return null;
|
|
8574
|
+
if (branch && existingBranch && existingBranch !== branch) return null;
|
|
8575
|
+
return { branch: existingBranch || branch, worktree: path2.resolve(existingWorktree), reused: true };
|
|
8576
|
+
} catch {
|
|
8577
|
+
return null;
|
|
8578
|
+
}
|
|
8579
|
+
}
|
|
8580
|
+
function ensureClickUpTaskWorktree({ baseWorktree = "", taskId, taskType = "Tarea", parentTaskId = "", subtaskId = "", existingMetadata = {}, runGitFn = runGit, allowNonGitFallback = false } = {}) {
|
|
8581
|
+
const effectiveParent = parentTaskId || taskId;
|
|
8582
|
+
const branch = deriveClickUpBranchName({ taskType, parentTaskId: effectiveParent, subtaskId, taskId });
|
|
8583
|
+
const existing = safeExistingClickUpWorktree({ metadata: existingMetadata, branch });
|
|
8584
|
+
if (existing) return { ...existing, branch };
|
|
8585
|
+
const worktreePath = deriveClickUpWorktree({ baseWorktree, taskId, taskType, parentTaskId: effectiveParent, subtaskId });
|
|
8586
|
+
if (fs2.existsSync(worktreePath)) return { branch, worktree: path2.resolve(worktreePath), reused: true };
|
|
8587
|
+
if (allowNonGitFallback) {
|
|
8588
|
+
fs2.mkdirSync(worktreePath, { recursive: true });
|
|
8589
|
+
return { branch, worktree: path2.resolve(worktreePath), reused: false, fallback: "non_git" };
|
|
8590
|
+
}
|
|
8591
|
+
const startPoint = (() => {
|
|
8592
|
+
try {
|
|
8593
|
+
runGitFn(baseWorktree, ["rev-parse", "--verify", "dev"]);
|
|
8594
|
+
return "dev";
|
|
8595
|
+
} catch {
|
|
8596
|
+
return "origin/dev";
|
|
8597
|
+
}
|
|
8598
|
+
})();
|
|
8599
|
+
runGitFn(baseWorktree, ["worktree", "add", "-b", branch, worktreePath, startPoint]);
|
|
8600
|
+
return { branch, worktree: path2.resolve(worktreePath), reused: false, startPoint };
|
|
8601
|
+
}
|
|
8545
8602
|
function normalizeClickUpDefinitionDocParent(parent = {}) {
|
|
8546
8603
|
const docId = String(parent.doc_id || parent.docId || CLICKUP_DEFINITION_DOC_PARENT.doc_id).trim();
|
|
8547
8604
|
const pageId = String(parent.page_id || parent.pageId || CLICKUP_DEFINITION_DOC_PARENT.page_id).trim();
|
|
@@ -8571,6 +8628,7 @@ function buildClickUpStartTaskPayload({ taskId, taskType = "Tarea", parentTaskId
|
|
|
8571
8628
|
worktree,
|
|
8572
8629
|
mirror_task_path: `.optima/tasks/${branchSafeClickUpId(taskId || subtaskId || effectiveParent)}.md`,
|
|
8573
8630
|
evidence_path: `.optima/evidences/${branchSafeClickUpId(taskId || subtaskId || effectiveParent)}/SUMMARY.md`,
|
|
8631
|
+
delivery_evidence_path: deliveryEvidencePathForClickUpTask(taskId || subtaskId || effectiveParent),
|
|
8574
8632
|
...agentMetadata
|
|
8575
8633
|
}
|
|
8576
8634
|
});
|
|
@@ -8581,7 +8639,8 @@ function buildClickUpStartTaskPayload({ taskId, taskType = "Tarea", parentTaskId
|
|
|
8581
8639
|
required_first_actions: [
|
|
8582
8640
|
"Create or reuse the task-specific branch/worktree before planning or writing local .optima mirrors.",
|
|
8583
8641
|
`Use branch ${branch} from dev and worktree ${worktree} for this task workspace.`,
|
|
8584
|
-
"Write planning state, .optima/tasks/current.md, task mirrors, and evidence only inside the task worktree.",
|
|
8642
|
+
"Write planning state, .optima/tasks/current.md, task mirrors, and local evidence only inside the task worktree.",
|
|
8643
|
+
`Write merge-trackable final evidence under ${deliveryEvidencePathForClickUpTask(taskId || subtaskId || effectiveParent)}; .optima remains local mirror/staging.`,
|
|
8585
8644
|
"Do not update the principal dev workspace .optima/tasks/current.md for ClickUp task planning.",
|
|
8586
8645
|
"If another active task is present in current.md, move to or create this task worktree instead of stopping."
|
|
8587
8646
|
],
|
|
@@ -8594,13 +8653,14 @@ function buildClickUpStartTaskPayload({ taskId, taskType = "Tarea", parentTaskId
|
|
|
8594
8653
|
mirror: {
|
|
8595
8654
|
taskPath: `.optima/tasks/${branchSafeClickUpId(taskId || subtaskId || effectiveParent)}.md`,
|
|
8596
8655
|
evidenceSummaryPath: `.optima/evidences/${branchSafeClickUpId(taskId || subtaskId || effectiveParent)}/SUMMARY.md`,
|
|
8656
|
+
deliveryEvidencePath: deliveryEvidencePathForClickUpTask(taskId || subtaskId || effectiveParent),
|
|
8597
8657
|
workspace: "task_worktree_only",
|
|
8598
8658
|
principalWorkspaceCurrentMd: "do_not_update",
|
|
8599
8659
|
currentMdConflictPolicy: "move_to_or_create_task_worktree",
|
|
8600
8660
|
wouldCreate: true
|
|
8601
8661
|
},
|
|
8602
8662
|
clickup: {
|
|
8603
|
-
comment: `Starting task from dev. Branch: ${branch}. Worktree: ${worktree}.`,
|
|
8663
|
+
comment: `Starting task from dev. Branch: ${branch}. Worktree: ${worktree}. Delivery evidence: ${deliveryEvidencePathForClickUpTask(taskId || subtaskId || effectiveParent)}.`,
|
|
8604
8664
|
description,
|
|
8605
8665
|
fields: {
|
|
8606
8666
|
Definition: definitionContent,
|
|
@@ -9313,6 +9373,14 @@ function recordClickUpCommentVersionProcessed({ ledgerPath, key, taskId, eventTy
|
|
|
9313
9373
|
function clickUpTaskIdFromPayload(payload = {}) {
|
|
9314
9374
|
return String(payload.task_id || payload.taskId || payload.task?.id || payload.task?.task_id || "").trim();
|
|
9315
9375
|
}
|
|
9376
|
+
function clickUpTaskName(task = {}) {
|
|
9377
|
+
return String(task?.name || "").trim();
|
|
9378
|
+
}
|
|
9379
|
+
function formatClickUpSessionTitle({ taskId, payloadTask, task } = {}) {
|
|
9380
|
+
const id = String(taskId || "").trim();
|
|
9381
|
+
const name = clickUpTaskName(payloadTask) || clickUpTaskName(task) || "ClickUp PM";
|
|
9382
|
+
return `[${id}]: ${name}`;
|
|
9383
|
+
}
|
|
9316
9384
|
function clickUpEventType(payload = {}) {
|
|
9317
9385
|
return String(payload.event || payload.event_type || payload.eventType || "").trim();
|
|
9318
9386
|
}
|
|
@@ -9383,6 +9451,11 @@ function clickUpPendingSessionKey(metadataKey) {
|
|
|
9383
9451
|
function mergeClickUpPendingSessionMetadata(existing, metadataKey, sessionId) {
|
|
9384
9452
|
return mergeClickUpSessionMetadata(existing, clickUpPendingSessionKey(metadataKey), sessionId);
|
|
9385
9453
|
}
|
|
9454
|
+
function setClickUpTaskRoutingMetadata(existing, routing = {}) {
|
|
9455
|
+
const root = normalizeAgentMetadataJson(existing);
|
|
9456
|
+
root.task = { ...isPlainObject(root.task) ? root.task : {}, ...routing };
|
|
9457
|
+
return JSON.stringify(sortJsonValue(root), null, 2);
|
|
9458
|
+
}
|
|
9386
9459
|
function setClickUpSessionMetadata(existing, metadataKey, sessionId) {
|
|
9387
9460
|
const root = normalizeAgentMetadataJson(existing);
|
|
9388
9461
|
const parts = String(metadataKey || CLICKUP_PM_METADATA_KEY).split(".").filter(Boolean);
|
|
@@ -9477,16 +9550,20 @@ async function sendOpenCodeSessionEvent(client, { sessionId, agent, text, direct
|
|
|
9477
9550
|
if (directBaseUrl) return sendOpenCodeSessionEventDirect({ baseUrl: directBaseUrl, sessionId, text, fetchImpl });
|
|
9478
9551
|
throw new Error("OpenCode client does not expose session.prompt or session.promptAsync.");
|
|
9479
9552
|
}
|
|
9480
|
-
function formatClickUpWebhookPrompt({ eventType, taskId, payload }) {
|
|
9553
|
+
function formatClickUpWebhookPrompt({ eventType, taskId, payload, branch = "", worktree = "", deliveryEvidencePath = "" }) {
|
|
9481
9554
|
const comment = clickUpCommentFromPayload(payload);
|
|
9482
9555
|
const commentText = clickUpCommentText(comment).trim();
|
|
9483
9556
|
return [
|
|
9484
9557
|
"[ClickUp Webhook Event]",
|
|
9485
9558
|
`Event: ${eventType}`,
|
|
9486
9559
|
`Task ID: ${taskId}`,
|
|
9560
|
+
branch ? `Branch: ${branch}` : null,
|
|
9561
|
+
worktree ? `Worktree: ${worktree}` : null,
|
|
9562
|
+
deliveryEvidencePath ? `Delivery evidence path: ${deliveryEvidencePath}` : null,
|
|
9487
9563
|
commentText ? `Comment: ${commentText}` : null,
|
|
9488
9564
|
"",
|
|
9489
|
-
"Handle this ClickUp Product Manager event using the ClickUp-first workflow rules. Do not assume broad chat routing; this event passed Optima webhook gates."
|
|
9565
|
+
"Handle this ClickUp Product Manager event using the ClickUp-first workflow rules. Do not assume broad chat routing; this event passed Optima webhook gates.",
|
|
9566
|
+
deliveryEvidencePath ? `Final merge-trackable evidence must be written under ${deliveryEvidencePath}; use .optima only as local mirror/staging.` : null
|
|
9490
9567
|
].filter((part) => part !== null).join("\n");
|
|
9491
9568
|
}
|
|
9492
9569
|
function appendClickUpWebhookLocalLog(worktree, entry) {
|
|
@@ -9665,7 +9742,7 @@ async function withClickUpTaskRouteLock(taskId, operation) {
|
|
|
9665
9742
|
if (activeClickUpTaskRoutes.get(key) === current) activeClickUpTaskRoutes.delete(key);
|
|
9666
9743
|
}
|
|
9667
9744
|
}
|
|
9668
|
-
async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, saveState = null, now = () => /* @__PURE__ */ new Date(), host = os.hostname(), commentLedgerPath = clickUpCommentLedgerPath(worktree) } = {}) {
|
|
9745
|
+
async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, worktree = process.cwd(), clickupClient, openCodeClient, sessionExists = openCodeSessionExists, createSession = createOpenCodeSession, sendSessionEvent = sendOpenCodeSessionEvent, ensureTaskWorktree = ensureClickUpTaskWorktree, saveState = null, now = () => /* @__PURE__ */ new Date(), host = os.hostname(), commentLedgerPath = clickUpCommentLedgerPath(worktree) } = {}) {
|
|
9669
9746
|
const eventType = clickUpEventType(payload);
|
|
9670
9747
|
const eventKey = clickUpWebhookEventKey(payload);
|
|
9671
9748
|
const remembered = rememberClickUpWebhookEvent(state, eventKey);
|
|
@@ -9709,14 +9786,35 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
9709
9786
|
const latestTask = await clickupClient.getTask(taskId);
|
|
9710
9787
|
if (latestTask) task = latestTask;
|
|
9711
9788
|
}
|
|
9789
|
+
const sessionTitle = formatClickUpSessionTitle({ taskId, payloadTask: payload.task, task });
|
|
9712
9790
|
const existingMetadata = clickUpTaskAgentMetadata(task, config.routing.metadataFieldId);
|
|
9713
9791
|
const metadata = normalizeAgentMetadataJson(existingMetadata);
|
|
9714
9792
|
const existingSessionId = getNestedMetadataValue(metadata, config.routing.metadataKey);
|
|
9715
|
-
const
|
|
9793
|
+
const taskType = clickUpTaskType(task);
|
|
9794
|
+
const parentTaskId = clickUpParentTaskId(task) || taskId;
|
|
9795
|
+
const subtaskId = parentTaskId && parentTaskId !== taskId ? taskId : "";
|
|
9796
|
+
let taskRoute;
|
|
9797
|
+
try {
|
|
9798
|
+
taskRoute = ensureTaskWorktree({ baseWorktree: config.basePath, taskId, taskType, parentTaskId, subtaskId, existingMetadata: metadata, allowNonGitFallback: process.env.NODE_ENV === "test" });
|
|
9799
|
+
} catch (error) {
|
|
9800
|
+
const message = `Optima webhook could not create or reuse a task worktree for ${taskId}: ${error.message}`;
|
|
9801
|
+
appendClickUpWebhookLocalLog(worktree, { type: "task_worktree_failed", taskId, message });
|
|
9802
|
+
if (typeof clickupClient?.postTaskComment === "function") await clickupClient.postTaskComment({ taskId, comment: message });
|
|
9803
|
+
return { ok: false, action: "error", reason: "task_worktree_failed", taskId, message };
|
|
9804
|
+
}
|
|
9805
|
+
const deliveryEvidencePath = deliveryEvidencePathForClickUpTask(taskId);
|
|
9806
|
+
const routingMetadata = {
|
|
9807
|
+
branch: taskRoute.branch,
|
|
9808
|
+
worktree: taskRoute.worktree,
|
|
9809
|
+
delivery_evidence_path: deliveryEvidencePath,
|
|
9810
|
+
evidence_path: `.optima/evidences/${branchSafeClickUpId(taskId)}/SUMMARY.md`
|
|
9811
|
+
};
|
|
9812
|
+
const metadataWithRouting = setClickUpTaskRoutingMetadata(existingMetadata, routingMetadata);
|
|
9813
|
+
const prompt = formatClickUpWebhookPrompt({ eventType, taskId, payload, branch: taskRoute.branch, worktree: taskRoute.worktree, deliveryEvidencePath });
|
|
9716
9814
|
if (!existingSessionId) {
|
|
9717
9815
|
let pendingSessionId = getNestedMetadataValue(metadata, clickUpPendingSessionKey(config.routing.metadataKey)) || state.pendingSessions?.[taskId];
|
|
9718
9816
|
if (!pendingSessionId) {
|
|
9719
|
-
pendingSessionId = await createSession(openCodeClient, { title:
|
|
9817
|
+
pendingSessionId = await createSession(openCodeClient, { title: sessionTitle, directory: taskRoute.worktree, agent: config.routing.targetAgent });
|
|
9720
9818
|
if (!String(pendingSessionId || "").startsWith("ses_")) return { ok: false, action: "error", reason: "session_create_failed", taskId };
|
|
9721
9819
|
if (saveState) {
|
|
9722
9820
|
saveState({
|
|
@@ -9725,14 +9823,14 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
9725
9823
|
});
|
|
9726
9824
|
}
|
|
9727
9825
|
}
|
|
9728
|
-
const pendingMetadata = mergeClickUpPendingSessionMetadata(
|
|
9826
|
+
const pendingMetadata = mergeClickUpPendingSessionMetadata(metadataWithRouting, config.routing.metadataKey, pendingSessionId);
|
|
9729
9827
|
try {
|
|
9730
9828
|
await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: pendingMetadata });
|
|
9731
9829
|
} catch (error) {
|
|
9732
9830
|
appendClickUpWebhookLocalLog(worktree, { type: "pending_session_metadata_failed", taskId, sessionId: pendingSessionId, message: error.message });
|
|
9733
9831
|
throw error;
|
|
9734
9832
|
}
|
|
9735
|
-
await sendSessionEvent(openCodeClient, { sessionId: pendingSessionId, agent: config.routing.targetAgent, text: prompt, directory:
|
|
9833
|
+
await sendSessionEvent(openCodeClient, { sessionId: pendingSessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl });
|
|
9736
9834
|
const nextMetadata = clearClickUpPendingSessionMetadata(setClickUpSessionMetadata(pendingMetadata, config.routing.metadataKey, pendingSessionId), config.routing.metadataKey);
|
|
9737
9835
|
await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: nextMetadata });
|
|
9738
9836
|
const { [taskId]: _completedPending, ...remainingPending } = stateToPersist.pendingSessions || {};
|
|
@@ -9741,7 +9839,8 @@ async function routeClickUpWebhookEventUnlocked({ payload, config, state = {}, w
|
|
|
9741
9839
|
}
|
|
9742
9840
|
const sessionId = String(existingSessionId);
|
|
9743
9841
|
if (await sessionExists(openCodeClient, sessionId)) {
|
|
9744
|
-
|
|
9842
|
+
if (typeof clickupClient?.updateTaskMetadata === "function") await clickupClient.updateTaskMetadata({ taskId, fieldId: config.routing.metadataFieldId, value: metadataWithRouting });
|
|
9843
|
+
await sendSessionEvent(openCodeClient, { sessionId, agent: config.routing.targetAgent, text: prompt, directory: taskRoute.worktree, opencodeBaseUrl: config.opencode?.baseUrl });
|
|
9745
9844
|
return finish({ ok: true, action: "sent_to_existing_session", taskId, sessionId, eventKey });
|
|
9746
9845
|
}
|
|
9747
9846
|
const at = now().toISOString();
|
|
@@ -11513,7 +11612,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
|
|
|
11513
11612
|
}
|
|
11514
11613
|
};
|
|
11515
11614
|
}
|
|
11516
|
-
OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, BUNDLE_ASSETS_DIR, CLICKUP_DEFINITION_DOC_PARENT, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentLedgerKey, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, createClickUpApiClient, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, readClickUpCommentLedger, readClickUpWebhookState, recordClickUpCommentVersionProcessed, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, stableClickUpCommentVersionMarker, startClickUpWebhookListener, validateClickUpWebhookState, verifyClickUpSignature, writeClickUpWebhookState, decideClickUpStatusAction, deriveClickUpBranchName, deriveClickUpPendingSubtaskBranch, deriveClickUpPrTarget, deriveClickUpWorktree, determineClickUpMergeAuthority, ensureOptimaGitignoreRules, explicitSafeInputWorktree, finalApprovalAssignees, formatValidationResult, isIgnoredClickUpTaskType, isOptimaPluginPackageWorktree, isSafeWritableDirectory, loadHumansRegistry, mergeClickUpAgentMetadata, mergeClickUpSessionMetadata, migrateLegacyOptimaLayout, normalizeAgentMetadataJson, normalizeClickUpStatus, normalizeClickUpTaskType, normalizePromptResponseParts, normalizeWorkflowTaskPath, parseClickUpSubtasksMarkdown, parseHumansRegistry, parseMarkdownArtifact, parseMarkdownSections, preEstimateClickUpWork, readMarkdownArtifact, resolveHumanRoles, resolveSafeWorktree, safeWorktreeOrFailure, stripRawLogSections, validateMainWorkspaceBranchSafety, workflowFinalMessageFromPromptResponse };
|
|
11615
|
+
OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, BUNDLE_ASSETS_DIR, CLICKUP_DEFINITION_DOC_PARENT, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentLedgerKey, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, createClickUpApiClient, deliveryEvidencePathForClickUpTask, ensureClickUpTaskWorktree, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, normalizeOpenCodeBaseUrl, readClickUpCommentLedger, readClickUpWebhookState, recordClickUpCommentVersionProcessed, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, sendOpenCodeSessionEventDirect, stableClickUpCommentVersionMarker, startClickUpWebhookListener, validateClickUpWebhookState, verifyClickUpSignature, writeClickUpWebhookState, decideClickUpStatusAction, deriveClickUpBranchName, deriveClickUpPendingSubtaskBranch, deriveClickUpPrTarget, deriveClickUpWorktree, determineClickUpMergeAuthority, ensureOptimaGitignoreRules, explicitSafeInputWorktree, finalApprovalAssignees, formatValidationResult, isIgnoredClickUpTaskType, isOptimaPluginPackageWorktree, isSafeWritableDirectory, loadHumansRegistry, mergeClickUpAgentMetadata, mergeClickUpSessionMetadata, migrateLegacyOptimaLayout, normalizeAgentMetadataJson, normalizeClickUpStatus, normalizeClickUpTaskType, normalizePromptResponseParts, normalizeWorkflowTaskPath, parseClickUpSubtasksMarkdown, parseHumansRegistry, parseMarkdownArtifact, parseMarkdownSections, preEstimateClickUpWork, readMarkdownArtifact, resolveHumanRoles, resolveSafeWorktree, safeWorktreeOrFailure, stripRawLogSections, validateMainWorkspaceBranchSafety, workflowFinalMessageFromPromptResponse };
|
|
11517
11616
|
|
|
11518
11617
|
// src/sanitize_cli.js
|
|
11519
11618
|
var { migrateLegacyOptimaLayout: migrateLegacyOptimaLayout2 } = OptimaPlugin.__internals;
|