@defend-tech/opencode-optima 0.1.16 → 0.1.18
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/dist/index.js +140 -12
- package/dist/sanitize_cli.js +140 -12
- package/package.json +1 -1
- package/policies/README.md +0 -62
- package/policies/codemap.yml +0 -12
- package/policies/definition-of-done.md +0 -27
- package/policies/definition-of-ready.md +0 -27
- package/policies/development-guidelines.md +0 -21
- package/policies/documentation-guidelines.md +0 -39
- package/policies/git-commit-messaging.md +0 -14
- package/policies/product-guidelines.md +0 -20
- package/policies/testing-guidelines.md +0 -30
- package/policies/ui-ux-guidelines.md +0 -33
package/dist/index.js
CHANGED
|
@@ -8017,6 +8017,9 @@ var REPO_LOCAL_POLICIES_README = [
|
|
|
8017
8017
|
].join("\n");
|
|
8018
8018
|
var activeWorkflows = /* @__PURE__ */ new Map();
|
|
8019
8019
|
var activeClickUpWebhookListeners = /* @__PURE__ */ new Map();
|
|
8020
|
+
var activeClickUpWebhookLifecycleRegistry = /* @__PURE__ */ new Map();
|
|
8021
|
+
var CLICKUP_WEBHOOK_CLEANUP_TIMEOUT_MS = 8e3;
|
|
8022
|
+
var CLICKUP_WEBHOOK_SIGNAL_STATE = Symbol.for("opencode-optima.clickup-webhook.signal-state");
|
|
8020
8023
|
var activeClickUpTaskRoutes = /* @__PURE__ */ new Map();
|
|
8021
8024
|
var objectIdentityMap = /* @__PURE__ */ new WeakMap();
|
|
8022
8025
|
var objectIdentitySequence = 0;
|
|
@@ -9017,6 +9020,26 @@ function clickUpWebhookListItems(response = {}) {
|
|
|
9017
9020
|
if (Array.isArray(response?.data?.webhooks)) return response.data.webhooks;
|
|
9018
9021
|
return [];
|
|
9019
9022
|
}
|
|
9023
|
+
function clickUpWebhookLocationCompatible(webhook = {}, config = {}) {
|
|
9024
|
+
const location = config?.webhook?.location || {};
|
|
9025
|
+
for (const key of ["space_id", "folder_id", "list_id"]) {
|
|
9026
|
+
const expected = String(location[key] || "").trim();
|
|
9027
|
+
if (expected && String(webhook[key] || webhook.location?.[key] || "").trim() !== expected) return false;
|
|
9028
|
+
}
|
|
9029
|
+
return true;
|
|
9030
|
+
}
|
|
9031
|
+
async function findReusableClickUpWebhook(config, clickupClient = null) {
|
|
9032
|
+
if (!clickupClient?.listWebhooks) return null;
|
|
9033
|
+
const listed = await clickupClient.listWebhooks({ teamId: config.teamId });
|
|
9034
|
+
for (const webhook of clickUpWebhookListItems(listed)) {
|
|
9035
|
+
const remote = normalizeClickUpWebhookApiResponse(webhook, config);
|
|
9036
|
+
if (remote.publicUrl !== config.webhook.publicUrl) continue;
|
|
9037
|
+
if (!clickUpWebhookLocationCompatible(webhook, config)) continue;
|
|
9038
|
+
if (!isClickUpWebhookStateActive(remote, config)) continue;
|
|
9039
|
+
return remote;
|
|
9040
|
+
}
|
|
9041
|
+
return null;
|
|
9042
|
+
}
|
|
9020
9043
|
async function validateClickUpWebhookState(state, config, clickupClient = null) {
|
|
9021
9044
|
if (!isClickUpWebhookStateActive(state, config)) return { valid: false, reason: "state_incomplete" };
|
|
9022
9045
|
if (clickupClient?.listWebhooks) {
|
|
@@ -9041,8 +9064,19 @@ async function ensureClickUpWebhookSubscription({ validation, worktree, clickupC
|
|
|
9041
9064
|
const existingValidation = await validateClickUpWebhookState(existing, config, clickupClient);
|
|
9042
9065
|
if (existingValidation.valid) {
|
|
9043
9066
|
const state = writeClickUpWebhookState(worktree, { ...existingValidation.state, recentEventKeys: existing.recentEventKeys || [] }, config);
|
|
9067
|
+
clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_reused", webhookId: state.webhookId, mode: existingValidation.mode });
|
|
9044
9068
|
return { active: true, valid: true, mode: existingValidation.mode, limitation: existingValidation.limitation, state };
|
|
9045
9069
|
}
|
|
9070
|
+
if (existing.webhookId && clickupClient?.deleteWebhook) {
|
|
9071
|
+
await deleteClickUpWebhookBestEffort({ webhookId: existing.webhookId, clickupClient, worktree, reason: existingValidation.reason || "startup_self_heal" });
|
|
9072
|
+
markClickUpWebhookInactive(worktree, existing, config);
|
|
9073
|
+
}
|
|
9074
|
+
const reusableRemote = await findReusableClickUpWebhook(config, clickupClient);
|
|
9075
|
+
if (reusableRemote) {
|
|
9076
|
+
const state = writeClickUpWebhookState(worktree, { ...reusableRemote, recentEventKeys: existing.recentEventKeys || [] }, config);
|
|
9077
|
+
clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_reused", webhookId: state.webhookId, mode: "remote_discovered" });
|
|
9078
|
+
return { active: true, valid: true, mode: "remote_discovered", state };
|
|
9079
|
+
}
|
|
9046
9080
|
if (!clickupClient?.createWebhook) {
|
|
9047
9081
|
return { active: false, valid: false, reason: "clickup_client_unavailable", state: existing };
|
|
9048
9082
|
}
|
|
@@ -9062,15 +9096,12 @@ async function ensureClickUpWebhookSubscription({ validation, worktree, clickupC
|
|
|
9062
9096
|
}
|
|
9063
9097
|
}
|
|
9064
9098
|
if (!createdValidation.valid) {
|
|
9065
|
-
|
|
9066
|
-
|
|
9067
|
-
await clickupClient.deleteWebhook(stateForValidation.webhookId);
|
|
9068
|
-
} catch {
|
|
9069
|
-
}
|
|
9070
|
-
}
|
|
9099
|
+
await deleteClickUpWebhookBestEffort({ webhookId: stateForValidation.webhookId, clickupClient, worktree, reason: "created_state_invalid" });
|
|
9100
|
+
clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_create_invalid", webhookId: stateForValidation.webhookId, reason: createdValidation.reason });
|
|
9071
9101
|
return { active: false, valid: false, reason: "created_state_invalid", validation: createdValidation, state: stateForValidation };
|
|
9072
9102
|
}
|
|
9073
9103
|
const next = writeClickUpWebhookState(worktree, createdValidation.state || stateForValidation, config);
|
|
9104
|
+
clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_created", webhookId: next.webhookId, mode: createdValidation.mode || "created" });
|
|
9074
9105
|
const mode = createdValidation.mode === "created_pending_remote_validation" ? "created_pending_remote_validation" : clickupClient?.listWebhooks ? "created_remote_validated" : "created";
|
|
9075
9106
|
return { active: true, valid: true, mode, limitation: createdValidation.limitation, state: next };
|
|
9076
9107
|
}
|
|
@@ -9253,6 +9284,93 @@ function appendClickUpWebhookLocalLog(worktree, entry) {
|
|
|
9253
9284
|
fs2.appendFileSync(logPath, `${JSON.stringify(safeEntry)}
|
|
9254
9285
|
`, "utf8");
|
|
9255
9286
|
}
|
|
9287
|
+
function clickUpWebhookLifecycleLog(worktree, entry) {
|
|
9288
|
+
try {
|
|
9289
|
+
appendClickUpWebhookLocalLog(worktree, { scope: "lifecycle", ...entry });
|
|
9290
|
+
} catch {
|
|
9291
|
+
}
|
|
9292
|
+
}
|
|
9293
|
+
function promiseWithTimeout(promise, timeoutMs, timeoutValue) {
|
|
9294
|
+
let timeout;
|
|
9295
|
+
return Promise.race([
|
|
9296
|
+
Promise.resolve(promise),
|
|
9297
|
+
new Promise((resolve) => {
|
|
9298
|
+
timeout = setTimeout(() => resolve(timeoutValue), Math.max(1, Number(timeoutMs) || CLICKUP_WEBHOOK_CLEANUP_TIMEOUT_MS));
|
|
9299
|
+
})
|
|
9300
|
+
]).finally(() => clearTimeout(timeout));
|
|
9301
|
+
}
|
|
9302
|
+
function closeClickUpWebhookServer(server) {
|
|
9303
|
+
if (!server || typeof server.close !== "function") return Promise.resolve({ ok: true, skipped: true });
|
|
9304
|
+
return new Promise((resolve) => {
|
|
9305
|
+
try {
|
|
9306
|
+
server.close((error) => resolve(error ? { ok: false, error: error.message } : { ok: true }));
|
|
9307
|
+
} catch (error) {
|
|
9308
|
+
resolve({ ok: false, error: error.message });
|
|
9309
|
+
}
|
|
9310
|
+
});
|
|
9311
|
+
}
|
|
9312
|
+
function managedClickUpWebhookKey({ worktree, state, config } = {}) {
|
|
9313
|
+
return [path2.resolve(worktree || process.cwd()), state?.webhookId || "", config?.webhook?.publicUrl || ""].join("|");
|
|
9314
|
+
}
|
|
9315
|
+
async function deleteClickUpWebhookBestEffort({ webhookId, clickupClient, worktree, reason = "cleanup" } = {}) {
|
|
9316
|
+
const id = String(webhookId || "").trim();
|
|
9317
|
+
if (!id || !clickupClient?.deleteWebhook) return { ok: false, skipped: true, reason: "delete_unavailable" };
|
|
9318
|
+
try {
|
|
9319
|
+
await clickupClient.deleteWebhook(id);
|
|
9320
|
+
clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_deleted", reason, webhookId: id });
|
|
9321
|
+
return { ok: true };
|
|
9322
|
+
} catch (error) {
|
|
9323
|
+
clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_delete_failed", reason, webhookId: id, message: error.message });
|
|
9324
|
+
return { ok: false, error: error.message };
|
|
9325
|
+
}
|
|
9326
|
+
}
|
|
9327
|
+
function markClickUpWebhookInactive(worktree, state = {}, config = null) {
|
|
9328
|
+
if (!worktree || !state?.webhookId) return null;
|
|
9329
|
+
return writeClickUpWebhookState(worktree, { ...state, active: false, listener: {}, lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString() }, config);
|
|
9330
|
+
}
|
|
9331
|
+
async function cleanupManagedClickUpWebhook(entry = {}, { timeoutMs = CLICKUP_WEBHOOK_CLEANUP_TIMEOUT_MS, reason = "shutdown" } = {}) {
|
|
9332
|
+
if (!entry || entry.cleaning) return { ok: true, skipped: true, reason: "already_cleaning" };
|
|
9333
|
+
entry.cleaning = true;
|
|
9334
|
+
const cleanup = (async () => {
|
|
9335
|
+
clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_started", reason, key: entry.key, webhookId: entry.state?.webhookId });
|
|
9336
|
+
const closeResult = await closeClickUpWebhookServer(entry.listener?.server);
|
|
9337
|
+
if (entry.listenerRegistry && entry.listener?.key) entry.listenerRegistry.delete(entry.listener.key);
|
|
9338
|
+
const deleteResult = await deleteClickUpWebhookBestEffort({ webhookId: entry.state?.webhookId, clickupClient: entry.clickupClient, worktree: entry.worktree, reason });
|
|
9339
|
+
if (deleteResult.ok) markClickUpWebhookInactive(entry.worktree, entry.state, entry.config);
|
|
9340
|
+
activeClickUpWebhookLifecycleRegistry.delete(entry.key);
|
|
9341
|
+
clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_finished", reason, close_ok: closeResult.ok, delete_ok: deleteResult.ok, delete_reason: deleteResult.reason });
|
|
9342
|
+
return { ok: closeResult.ok !== false && deleteResult.ok !== false, closeResult, deleteResult };
|
|
9343
|
+
})();
|
|
9344
|
+
const result = await promiseWithTimeout(cleanup, timeoutMs, { ok: false, timeout: true, reason: "cleanup_timeout" });
|
|
9345
|
+
if (result?.timeout) clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_timeout", reason, webhookId: entry.state?.webhookId });
|
|
9346
|
+
return result;
|
|
9347
|
+
}
|
|
9348
|
+
function registerClickUpWebhookLifecycle({ config, state, worktree, clickupClient, listener, listenerRegistry = activeClickUpWebhookListeners } = {}) {
|
|
9349
|
+
if (!isClickUpWebhookStateActive(state, config)) return null;
|
|
9350
|
+
const key = managedClickUpWebhookKey({ worktree, state, config });
|
|
9351
|
+
const entry = { key, config, state, worktree, clickupClient, listener, listenerRegistry, registeredAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
9352
|
+
activeClickUpWebhookLifecycleRegistry.set(key, entry);
|
|
9353
|
+
installClickUpWebhookSignalHandlers();
|
|
9354
|
+
clickUpWebhookLifecycleLog(worktree, { type: "lifecycle_registered", key, webhookId: state.webhookId });
|
|
9355
|
+
return entry;
|
|
9356
|
+
}
|
|
9357
|
+
function installClickUpWebhookSignalHandlers() {
|
|
9358
|
+
const globalState = globalThis[CLICKUP_WEBHOOK_SIGNAL_STATE] || { installed: false, running: false };
|
|
9359
|
+
if (globalState.installed) return;
|
|
9360
|
+
const handler = (signal) => {
|
|
9361
|
+
if (globalState.running) return;
|
|
9362
|
+
globalState.running = true;
|
|
9363
|
+
const timeoutMs = CLICKUP_WEBHOOK_CLEANUP_TIMEOUT_MS;
|
|
9364
|
+
const cleanup = Promise.allSettled([...activeClickUpWebhookLifecycleRegistry.values()].map((entry) => cleanupManagedClickUpWebhook(entry, { timeoutMs, reason: signal })));
|
|
9365
|
+
promiseWithTimeout(cleanup, timeoutMs + 500, { timeout: true }).finally(() => {
|
|
9366
|
+
process.exit(signal === "SIGINT" ? 130 : 143);
|
|
9367
|
+
});
|
|
9368
|
+
};
|
|
9369
|
+
process.once("SIGTERM", handler);
|
|
9370
|
+
process.once("SIGINT", handler);
|
|
9371
|
+
globalState.installed = true;
|
|
9372
|
+
globalThis[CLICKUP_WEBHOOK_SIGNAL_STATE] = globalState;
|
|
9373
|
+
}
|
|
9256
9374
|
function redactClickUpWebhookAuditValue(value, secretValues = []) {
|
|
9257
9375
|
if (typeof value === "string") {
|
|
9258
9376
|
return secretValues.reduce((next, secret) => secret ? next.split(secret).join(CLICKUP_WEBHOOK_REDACTED) : next, value);
|
|
@@ -10566,25 +10684,35 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
|
|
|
10566
10684
|
let clickUpWebhookActive = clickUpWebhookRuntime.active === true && clickUpWebhookRuntime.valid === true;
|
|
10567
10685
|
if (clickUpWebhookActive && input.startClickUpWebhookListener !== false) {
|
|
10568
10686
|
try {
|
|
10687
|
+
const lifecycleClickUpClient = input.clickupClient || createClickUpApiClient(clickUpWebhookValidation.config, input.fetch);
|
|
10688
|
+
const listenerRegistry = input.clickUpWebhookListenerRegistry || activeClickUpWebhookListeners;
|
|
10689
|
+
const listenerState = clickUpWebhookRuntime.state || readClickUpWebhookState(worktree, clickUpWebhookValidation.config);
|
|
10569
10690
|
const listener = startClickUpWebhookListener({
|
|
10570
10691
|
config: clickUpWebhookValidation.config,
|
|
10571
|
-
state:
|
|
10692
|
+
state: listenerState,
|
|
10572
10693
|
worktree,
|
|
10573
|
-
clickupClient:
|
|
10694
|
+
clickupClient: lifecycleClickUpClient,
|
|
10574
10695
|
openCodeClient: input.client,
|
|
10575
|
-
listenerRegistry
|
|
10696
|
+
listenerRegistry
|
|
10576
10697
|
});
|
|
10577
10698
|
const readyListener = listener.readyPromise ? await listener.readyPromise : listener;
|
|
10578
10699
|
if (readyListener.active && readyListener.ready !== false) {
|
|
10579
|
-
writeClickUpWebhookState(worktree, {
|
|
10580
|
-
...
|
|
10700
|
+
const activeState = writeClickUpWebhookState(worktree, {
|
|
10701
|
+
...listenerState,
|
|
10581
10702
|
listener: { bindHost: clickUpWebhookValidation.config.webhook.bindHost, bindPort: clickUpWebhookValidation.config.webhook.bindPort, startedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
10582
10703
|
}, clickUpWebhookValidation.config);
|
|
10704
|
+
registerClickUpWebhookLifecycle({ config: clickUpWebhookValidation.config, state: activeState, worktree, clickupClient: lifecycleClickUpClient, listener: readyListener, listenerRegistry });
|
|
10583
10705
|
} else {
|
|
10706
|
+
await deleteClickUpWebhookBestEffort({ webhookId: listenerState.webhookId, clickupClient: lifecycleClickUpClient, worktree, reason: readyListener.reason || "listener_unavailable" });
|
|
10707
|
+
markClickUpWebhookInactive(worktree, listenerState, clickUpWebhookValidation.config);
|
|
10584
10708
|
clickUpWebhookActive = false;
|
|
10585
10709
|
clickUpWebhookRuntime = { ...clickUpWebhookRuntime, active: false, valid: false, reason: readyListener.reason || "listener_unavailable" };
|
|
10586
10710
|
}
|
|
10587
10711
|
} catch (error) {
|
|
10712
|
+
const failureState = clickUpWebhookRuntime.state || readClickUpWebhookState(worktree, clickUpWebhookValidation.config);
|
|
10713
|
+
const failureClient = input.clickupClient || createClickUpApiClient(clickUpWebhookValidation.config, input.fetch);
|
|
10714
|
+
await deleteClickUpWebhookBestEffort({ webhookId: failureState.webhookId, clickupClient: failureClient, worktree, reason: "listener_failed" });
|
|
10715
|
+
markClickUpWebhookInactive(worktree, failureState, clickUpWebhookValidation.config);
|
|
10588
10716
|
clickUpWebhookActive = false;
|
|
10589
10717
|
clickUpWebhookRuntime = { ...clickUpWebhookRuntime, active: false, valid: false, reason: "listener_failed", error: error.message };
|
|
10590
10718
|
appendClickUpWebhookLocalLog(worktree, { type: "listener_failed", message: error.message });
|
|
@@ -11139,7 +11267,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
|
|
|
11139
11267
|
}
|
|
11140
11268
|
};
|
|
11141
11269
|
}
|
|
11142
|
-
OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, createClickUpApiClient, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, readClickUpWebhookState, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, 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 };
|
|
11270
|
+
OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, createClickUpApiClient, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, readClickUpWebhookState, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, 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 };
|
|
11143
11271
|
export {
|
|
11144
11272
|
OptimaPlugin as default
|
|
11145
11273
|
};
|
package/dist/sanitize_cli.js
CHANGED
|
@@ -8024,6 +8024,9 @@ var REPO_LOCAL_POLICIES_README = [
|
|
|
8024
8024
|
].join("\n");
|
|
8025
8025
|
var activeWorkflows = /* @__PURE__ */ new Map();
|
|
8026
8026
|
var activeClickUpWebhookListeners = /* @__PURE__ */ new Map();
|
|
8027
|
+
var activeClickUpWebhookLifecycleRegistry = /* @__PURE__ */ new Map();
|
|
8028
|
+
var CLICKUP_WEBHOOK_CLEANUP_TIMEOUT_MS = 8e3;
|
|
8029
|
+
var CLICKUP_WEBHOOK_SIGNAL_STATE = Symbol.for("opencode-optima.clickup-webhook.signal-state");
|
|
8027
8030
|
var activeClickUpTaskRoutes = /* @__PURE__ */ new Map();
|
|
8028
8031
|
var objectIdentityMap = /* @__PURE__ */ new WeakMap();
|
|
8029
8032
|
var objectIdentitySequence = 0;
|
|
@@ -9024,6 +9027,26 @@ function clickUpWebhookListItems(response = {}) {
|
|
|
9024
9027
|
if (Array.isArray(response?.data?.webhooks)) return response.data.webhooks;
|
|
9025
9028
|
return [];
|
|
9026
9029
|
}
|
|
9030
|
+
function clickUpWebhookLocationCompatible(webhook = {}, config = {}) {
|
|
9031
|
+
const location = config?.webhook?.location || {};
|
|
9032
|
+
for (const key of ["space_id", "folder_id", "list_id"]) {
|
|
9033
|
+
const expected = String(location[key] || "").trim();
|
|
9034
|
+
if (expected && String(webhook[key] || webhook.location?.[key] || "").trim() !== expected) return false;
|
|
9035
|
+
}
|
|
9036
|
+
return true;
|
|
9037
|
+
}
|
|
9038
|
+
async function findReusableClickUpWebhook(config, clickupClient = null) {
|
|
9039
|
+
if (!clickupClient?.listWebhooks) return null;
|
|
9040
|
+
const listed = await clickupClient.listWebhooks({ teamId: config.teamId });
|
|
9041
|
+
for (const webhook of clickUpWebhookListItems(listed)) {
|
|
9042
|
+
const remote = normalizeClickUpWebhookApiResponse(webhook, config);
|
|
9043
|
+
if (remote.publicUrl !== config.webhook.publicUrl) continue;
|
|
9044
|
+
if (!clickUpWebhookLocationCompatible(webhook, config)) continue;
|
|
9045
|
+
if (!isClickUpWebhookStateActive(remote, config)) continue;
|
|
9046
|
+
return remote;
|
|
9047
|
+
}
|
|
9048
|
+
return null;
|
|
9049
|
+
}
|
|
9027
9050
|
async function validateClickUpWebhookState(state, config, clickupClient = null) {
|
|
9028
9051
|
if (!isClickUpWebhookStateActive(state, config)) return { valid: false, reason: "state_incomplete" };
|
|
9029
9052
|
if (clickupClient?.listWebhooks) {
|
|
@@ -9048,8 +9071,19 @@ async function ensureClickUpWebhookSubscription({ validation, worktree, clickupC
|
|
|
9048
9071
|
const existingValidation = await validateClickUpWebhookState(existing, config, clickupClient);
|
|
9049
9072
|
if (existingValidation.valid) {
|
|
9050
9073
|
const state = writeClickUpWebhookState(worktree, { ...existingValidation.state, recentEventKeys: existing.recentEventKeys || [] }, config);
|
|
9074
|
+
clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_reused", webhookId: state.webhookId, mode: existingValidation.mode });
|
|
9051
9075
|
return { active: true, valid: true, mode: existingValidation.mode, limitation: existingValidation.limitation, state };
|
|
9052
9076
|
}
|
|
9077
|
+
if (existing.webhookId && clickupClient?.deleteWebhook) {
|
|
9078
|
+
await deleteClickUpWebhookBestEffort({ webhookId: existing.webhookId, clickupClient, worktree, reason: existingValidation.reason || "startup_self_heal" });
|
|
9079
|
+
markClickUpWebhookInactive(worktree, existing, config);
|
|
9080
|
+
}
|
|
9081
|
+
const reusableRemote = await findReusableClickUpWebhook(config, clickupClient);
|
|
9082
|
+
if (reusableRemote) {
|
|
9083
|
+
const state = writeClickUpWebhookState(worktree, { ...reusableRemote, recentEventKeys: existing.recentEventKeys || [] }, config);
|
|
9084
|
+
clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_reused", webhookId: state.webhookId, mode: "remote_discovered" });
|
|
9085
|
+
return { active: true, valid: true, mode: "remote_discovered", state };
|
|
9086
|
+
}
|
|
9053
9087
|
if (!clickupClient?.createWebhook) {
|
|
9054
9088
|
return { active: false, valid: false, reason: "clickup_client_unavailable", state: existing };
|
|
9055
9089
|
}
|
|
@@ -9069,15 +9103,12 @@ async function ensureClickUpWebhookSubscription({ validation, worktree, clickupC
|
|
|
9069
9103
|
}
|
|
9070
9104
|
}
|
|
9071
9105
|
if (!createdValidation.valid) {
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
await clickupClient.deleteWebhook(stateForValidation.webhookId);
|
|
9075
|
-
} catch {
|
|
9076
|
-
}
|
|
9077
|
-
}
|
|
9106
|
+
await deleteClickUpWebhookBestEffort({ webhookId: stateForValidation.webhookId, clickupClient, worktree, reason: "created_state_invalid" });
|
|
9107
|
+
clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_create_invalid", webhookId: stateForValidation.webhookId, reason: createdValidation.reason });
|
|
9078
9108
|
return { active: false, valid: false, reason: "created_state_invalid", validation: createdValidation, state: stateForValidation };
|
|
9079
9109
|
}
|
|
9080
9110
|
const next = writeClickUpWebhookState(worktree, createdValidation.state || stateForValidation, config);
|
|
9111
|
+
clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_created", webhookId: next.webhookId, mode: createdValidation.mode || "created" });
|
|
9081
9112
|
const mode = createdValidation.mode === "created_pending_remote_validation" ? "created_pending_remote_validation" : clickupClient?.listWebhooks ? "created_remote_validated" : "created";
|
|
9082
9113
|
return { active: true, valid: true, mode, limitation: createdValidation.limitation, state: next };
|
|
9083
9114
|
}
|
|
@@ -9260,6 +9291,93 @@ function appendClickUpWebhookLocalLog(worktree, entry) {
|
|
|
9260
9291
|
fs2.appendFileSync(logPath, `${JSON.stringify(safeEntry)}
|
|
9261
9292
|
`, "utf8");
|
|
9262
9293
|
}
|
|
9294
|
+
function clickUpWebhookLifecycleLog(worktree, entry) {
|
|
9295
|
+
try {
|
|
9296
|
+
appendClickUpWebhookLocalLog(worktree, { scope: "lifecycle", ...entry });
|
|
9297
|
+
} catch {
|
|
9298
|
+
}
|
|
9299
|
+
}
|
|
9300
|
+
function promiseWithTimeout(promise, timeoutMs, timeoutValue) {
|
|
9301
|
+
let timeout;
|
|
9302
|
+
return Promise.race([
|
|
9303
|
+
Promise.resolve(promise),
|
|
9304
|
+
new Promise((resolve) => {
|
|
9305
|
+
timeout = setTimeout(() => resolve(timeoutValue), Math.max(1, Number(timeoutMs) || CLICKUP_WEBHOOK_CLEANUP_TIMEOUT_MS));
|
|
9306
|
+
})
|
|
9307
|
+
]).finally(() => clearTimeout(timeout));
|
|
9308
|
+
}
|
|
9309
|
+
function closeClickUpWebhookServer(server) {
|
|
9310
|
+
if (!server || typeof server.close !== "function") return Promise.resolve({ ok: true, skipped: true });
|
|
9311
|
+
return new Promise((resolve) => {
|
|
9312
|
+
try {
|
|
9313
|
+
server.close((error) => resolve(error ? { ok: false, error: error.message } : { ok: true }));
|
|
9314
|
+
} catch (error) {
|
|
9315
|
+
resolve({ ok: false, error: error.message });
|
|
9316
|
+
}
|
|
9317
|
+
});
|
|
9318
|
+
}
|
|
9319
|
+
function managedClickUpWebhookKey({ worktree, state, config } = {}) {
|
|
9320
|
+
return [path2.resolve(worktree || process.cwd()), state?.webhookId || "", config?.webhook?.publicUrl || ""].join("|");
|
|
9321
|
+
}
|
|
9322
|
+
async function deleteClickUpWebhookBestEffort({ webhookId, clickupClient, worktree, reason = "cleanup" } = {}) {
|
|
9323
|
+
const id = String(webhookId || "").trim();
|
|
9324
|
+
if (!id || !clickupClient?.deleteWebhook) return { ok: false, skipped: true, reason: "delete_unavailable" };
|
|
9325
|
+
try {
|
|
9326
|
+
await clickupClient.deleteWebhook(id);
|
|
9327
|
+
clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_deleted", reason, webhookId: id });
|
|
9328
|
+
return { ok: true };
|
|
9329
|
+
} catch (error) {
|
|
9330
|
+
clickUpWebhookLifecycleLog(worktree, { type: "remote_webhook_delete_failed", reason, webhookId: id, message: error.message });
|
|
9331
|
+
return { ok: false, error: error.message };
|
|
9332
|
+
}
|
|
9333
|
+
}
|
|
9334
|
+
function markClickUpWebhookInactive(worktree, state = {}, config = null) {
|
|
9335
|
+
if (!worktree || !state?.webhookId) return null;
|
|
9336
|
+
return writeClickUpWebhookState(worktree, { ...state, active: false, listener: {}, lastValidatedAt: (/* @__PURE__ */ new Date()).toISOString() }, config);
|
|
9337
|
+
}
|
|
9338
|
+
async function cleanupManagedClickUpWebhook(entry = {}, { timeoutMs = CLICKUP_WEBHOOK_CLEANUP_TIMEOUT_MS, reason = "shutdown" } = {}) {
|
|
9339
|
+
if (!entry || entry.cleaning) return { ok: true, skipped: true, reason: "already_cleaning" };
|
|
9340
|
+
entry.cleaning = true;
|
|
9341
|
+
const cleanup = (async () => {
|
|
9342
|
+
clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_started", reason, key: entry.key, webhookId: entry.state?.webhookId });
|
|
9343
|
+
const closeResult = await closeClickUpWebhookServer(entry.listener?.server);
|
|
9344
|
+
if (entry.listenerRegistry && entry.listener?.key) entry.listenerRegistry.delete(entry.listener.key);
|
|
9345
|
+
const deleteResult = await deleteClickUpWebhookBestEffort({ webhookId: entry.state?.webhookId, clickupClient: entry.clickupClient, worktree: entry.worktree, reason });
|
|
9346
|
+
if (deleteResult.ok) markClickUpWebhookInactive(entry.worktree, entry.state, entry.config);
|
|
9347
|
+
activeClickUpWebhookLifecycleRegistry.delete(entry.key);
|
|
9348
|
+
clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_finished", reason, close_ok: closeResult.ok, delete_ok: deleteResult.ok, delete_reason: deleteResult.reason });
|
|
9349
|
+
return { ok: closeResult.ok !== false && deleteResult.ok !== false, closeResult, deleteResult };
|
|
9350
|
+
})();
|
|
9351
|
+
const result = await promiseWithTimeout(cleanup, timeoutMs, { ok: false, timeout: true, reason: "cleanup_timeout" });
|
|
9352
|
+
if (result?.timeout) clickUpWebhookLifecycleLog(entry.worktree, { type: "cleanup_timeout", reason, webhookId: entry.state?.webhookId });
|
|
9353
|
+
return result;
|
|
9354
|
+
}
|
|
9355
|
+
function registerClickUpWebhookLifecycle({ config, state, worktree, clickupClient, listener, listenerRegistry = activeClickUpWebhookListeners } = {}) {
|
|
9356
|
+
if (!isClickUpWebhookStateActive(state, config)) return null;
|
|
9357
|
+
const key = managedClickUpWebhookKey({ worktree, state, config });
|
|
9358
|
+
const entry = { key, config, state, worktree, clickupClient, listener, listenerRegistry, registeredAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
9359
|
+
activeClickUpWebhookLifecycleRegistry.set(key, entry);
|
|
9360
|
+
installClickUpWebhookSignalHandlers();
|
|
9361
|
+
clickUpWebhookLifecycleLog(worktree, { type: "lifecycle_registered", key, webhookId: state.webhookId });
|
|
9362
|
+
return entry;
|
|
9363
|
+
}
|
|
9364
|
+
function installClickUpWebhookSignalHandlers() {
|
|
9365
|
+
const globalState = globalThis[CLICKUP_WEBHOOK_SIGNAL_STATE] || { installed: false, running: false };
|
|
9366
|
+
if (globalState.installed) return;
|
|
9367
|
+
const handler = (signal) => {
|
|
9368
|
+
if (globalState.running) return;
|
|
9369
|
+
globalState.running = true;
|
|
9370
|
+
const timeoutMs = CLICKUP_WEBHOOK_CLEANUP_TIMEOUT_MS;
|
|
9371
|
+
const cleanup = Promise.allSettled([...activeClickUpWebhookLifecycleRegistry.values()].map((entry) => cleanupManagedClickUpWebhook(entry, { timeoutMs, reason: signal })));
|
|
9372
|
+
promiseWithTimeout(cleanup, timeoutMs + 500, { timeout: true }).finally(() => {
|
|
9373
|
+
process.exit(signal === "SIGINT" ? 130 : 143);
|
|
9374
|
+
});
|
|
9375
|
+
};
|
|
9376
|
+
process.once("SIGTERM", handler);
|
|
9377
|
+
process.once("SIGINT", handler);
|
|
9378
|
+
globalState.installed = true;
|
|
9379
|
+
globalThis[CLICKUP_WEBHOOK_SIGNAL_STATE] = globalState;
|
|
9380
|
+
}
|
|
9263
9381
|
function redactClickUpWebhookAuditValue(value, secretValues = []) {
|
|
9264
9382
|
if (typeof value === "string") {
|
|
9265
9383
|
return secretValues.reduce((next, secret) => secret ? next.split(secret).join(CLICKUP_WEBHOOK_REDACTED) : next, value);
|
|
@@ -10573,25 +10691,35 @@ async function OptimaPlugin(input = {}, pluginOptions = {}) {
|
|
|
10573
10691
|
let clickUpWebhookActive = clickUpWebhookRuntime.active === true && clickUpWebhookRuntime.valid === true;
|
|
10574
10692
|
if (clickUpWebhookActive && input.startClickUpWebhookListener !== false) {
|
|
10575
10693
|
try {
|
|
10694
|
+
const lifecycleClickUpClient = input.clickupClient || createClickUpApiClient(clickUpWebhookValidation.config, input.fetch);
|
|
10695
|
+
const listenerRegistry = input.clickUpWebhookListenerRegistry || activeClickUpWebhookListeners;
|
|
10696
|
+
const listenerState = clickUpWebhookRuntime.state || readClickUpWebhookState(worktree, clickUpWebhookValidation.config);
|
|
10576
10697
|
const listener = startClickUpWebhookListener({
|
|
10577
10698
|
config: clickUpWebhookValidation.config,
|
|
10578
|
-
state:
|
|
10699
|
+
state: listenerState,
|
|
10579
10700
|
worktree,
|
|
10580
|
-
clickupClient:
|
|
10701
|
+
clickupClient: lifecycleClickUpClient,
|
|
10581
10702
|
openCodeClient: input.client,
|
|
10582
|
-
listenerRegistry
|
|
10703
|
+
listenerRegistry
|
|
10583
10704
|
});
|
|
10584
10705
|
const readyListener = listener.readyPromise ? await listener.readyPromise : listener;
|
|
10585
10706
|
if (readyListener.active && readyListener.ready !== false) {
|
|
10586
|
-
writeClickUpWebhookState(worktree, {
|
|
10587
|
-
...
|
|
10707
|
+
const activeState = writeClickUpWebhookState(worktree, {
|
|
10708
|
+
...listenerState,
|
|
10588
10709
|
listener: { bindHost: clickUpWebhookValidation.config.webhook.bindHost, bindPort: clickUpWebhookValidation.config.webhook.bindPort, startedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
10589
10710
|
}, clickUpWebhookValidation.config);
|
|
10711
|
+
registerClickUpWebhookLifecycle({ config: clickUpWebhookValidation.config, state: activeState, worktree, clickupClient: lifecycleClickUpClient, listener: readyListener, listenerRegistry });
|
|
10590
10712
|
} else {
|
|
10713
|
+
await deleteClickUpWebhookBestEffort({ webhookId: listenerState.webhookId, clickupClient: lifecycleClickUpClient, worktree, reason: readyListener.reason || "listener_unavailable" });
|
|
10714
|
+
markClickUpWebhookInactive(worktree, listenerState, clickUpWebhookValidation.config);
|
|
10591
10715
|
clickUpWebhookActive = false;
|
|
10592
10716
|
clickUpWebhookRuntime = { ...clickUpWebhookRuntime, active: false, valid: false, reason: readyListener.reason || "listener_unavailable" };
|
|
10593
10717
|
}
|
|
10594
10718
|
} catch (error) {
|
|
10719
|
+
const failureState = clickUpWebhookRuntime.state || readClickUpWebhookState(worktree, clickUpWebhookValidation.config);
|
|
10720
|
+
const failureClient = input.clickupClient || createClickUpApiClient(clickUpWebhookValidation.config, input.fetch);
|
|
10721
|
+
await deleteClickUpWebhookBestEffort({ webhookId: failureState.webhookId, clickupClient: failureClient, worktree, reason: "listener_failed" });
|
|
10722
|
+
markClickUpWebhookInactive(worktree, failureState, clickUpWebhookValidation.config);
|
|
10595
10723
|
clickUpWebhookActive = false;
|
|
10596
10724
|
clickUpWebhookRuntime = { ...clickUpWebhookRuntime, active: false, valid: false, reason: "listener_failed", error: error.message };
|
|
10597
10725
|
appendClickUpWebhookLocalLog(worktree, { type: "listener_failed", message: error.message });
|
|
@@ -11146,7 +11274,7 @@ Follow-up: use optima_prompt_workflow with session_id '${sessionId}' to check in
|
|
|
11146
11274
|
}
|
|
11147
11275
|
};
|
|
11148
11276
|
}
|
|
11149
|
-
OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, createClickUpApiClient, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, readClickUpWebhookState, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, 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 };
|
|
11277
|
+
OptimaPlugin.__internals = { BUNDLE_AGENTS_DIR, activeClickUpWebhookLifecycleRegistry, cleanupManagedClickUpWebhook, deleteClickUpWebhookBestEffort, BUNDLE_POLICIES_DIR, buildClickUpApplyPayloadResult, buildClickUpCreateSubtasksPayload, buildClickUpStartTaskPayload, buildClickUpSummaryPayload, buildClickUpTransitionPayload, buildOptimaAgents, clickUpCommentMentionsProductManager, clickUpWebhookAuditLogDir, clickUpWebhookAuditLogPath, createClickUpApiClient, ensureClickUpWebhookSubscription, handleClickUpWebhookRequest, isClickUpWebhookStateActive, normalizeClickUpWebhookConfig, normalizeClickUpWebhookLogLevel, readClickUpWebhookState, resolveOptimaPluginOptions, resolveSecretReference, routeClickUpWebhookEvent, sendOpenCodeSessionEvent, 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 };
|
|
11150
11278
|
|
|
11151
11279
|
// src/sanitize_cli.js
|
|
11152
11280
|
var { migrateLegacyOptimaLayout: migrateLegacyOptimaLayout2 } = OptimaPlugin.__internals;
|
package/package.json
CHANGED
package/policies/README.md
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
# Optima Policies
|
|
2
|
-
|
|
3
|
-
This package-internal directory contains bundled fallback policy assets. Optima target repositories should put repo-local policy overrides in `.optima/policies/`, not in a root `policies/` directory.
|
|
4
|
-
|
|
5
|
-
## How Policy Resolution Works
|
|
6
|
-
|
|
7
|
-
For any `<include:policy:<file>.md>` include, Optima resolves policy files in this order:
|
|
8
|
-
|
|
9
|
-
1. `.optima/policies/<file>.md`
|
|
10
|
-
2. bundled plugin default `policies/<file>.md`
|
|
11
|
-
|
|
12
|
-
Files under `.optima/.config/generated/policies/` are reference copies only. They are not read directly at runtime.
|
|
13
|
-
|
|
14
|
-
## Available Policies
|
|
15
|
-
|
|
16
|
-
- `development-guidelines.md`
|
|
17
|
-
- Repository-specific engineering rules, stack notes, and implementation conventions.
|
|
18
|
-
- Used by: `developer`, `technical_architect`, `tech_lead`, `workflow_runner`
|
|
19
|
-
|
|
20
|
-
- `testing-guidelines.md`
|
|
21
|
-
- Testing, evidence, regression, and verification conventions.
|
|
22
|
-
- Used by: `developer`, `qa_engineer`, `tech_lead`, `workflow_runner`
|
|
23
|
-
|
|
24
|
-
- `documentation-guidelines.md`
|
|
25
|
-
- Documentation layout, naming, ownership, and update expectations.
|
|
26
|
-
- Used by all agents through the shared prompt.
|
|
27
|
-
|
|
28
|
-
- `definition-of-ready.md`
|
|
29
|
-
- Canonical readiness criteria before execution begins.
|
|
30
|
-
- Used by all agents through the shared prompt and reflected in task templates.
|
|
31
|
-
|
|
32
|
-
- `definition-of-done.md`
|
|
33
|
-
- Canonical completion criteria before closure.
|
|
34
|
-
- Used by all agents through the shared prompt and reflected in task templates.
|
|
35
|
-
|
|
36
|
-
- `git-commit-messaging.md`
|
|
37
|
-
- Commit subject and body rules.
|
|
38
|
-
- Used by: `tech_lead`, `workflow_runner`
|
|
39
|
-
|
|
40
|
-
- `product-guidelines.md`
|
|
41
|
-
- User story, acceptance criteria, terminology, and product-truth conventions.
|
|
42
|
-
- Used by: `product_manager`, `business_analyst`
|
|
43
|
-
|
|
44
|
-
- `ui-ux-guidelines.md`
|
|
45
|
-
- UI review standards and visual quality expectations.
|
|
46
|
-
- Used by: `ui_ux_designer`
|
|
47
|
-
|
|
48
|
-
## Customizing A Policy
|
|
49
|
-
|
|
50
|
-
1. Set `.optima/.config/optima.yaml` `policies.extract_defaults` to `all` if you want reference copies of all bundled defaults.
|
|
51
|
-
2. Inspect `.optima/.config/generated/policies/` for the default files.
|
|
52
|
-
3. Copy the policy you want to customize into `.optima/policies/`.
|
|
53
|
-
4. Edit the copied file. The repo-local version will override the plugin default automatically.
|
|
54
|
-
|
|
55
|
-
## Policy Extraction
|
|
56
|
-
|
|
57
|
-
`policies.extract_defaults` supports:
|
|
58
|
-
|
|
59
|
-
- `none`: do not generate reference policy files
|
|
60
|
-
- `all`: write all bundled default policy files to `.optima/.config/generated/policies/`
|
|
61
|
-
|
|
62
|
-
Only files in `.optima/policies/` affect runtime prompt behavior.
|
package/policies/codemap.yml
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
scope: module
|
|
2
|
-
parent: ../.optima/codemap.yml
|
|
3
|
-
sources_of_truth:
|
|
4
|
-
- path: README.md
|
|
5
|
-
- path: definition-of-done.md
|
|
6
|
-
- path: definition-of-ready.md
|
|
7
|
-
- path: development-guidelines.md
|
|
8
|
-
- path: documentation-guidelines.md
|
|
9
|
-
- path: git-commit-messaging.md
|
|
10
|
-
- path: product-guidelines.md
|
|
11
|
-
- path: testing-guidelines.md
|
|
12
|
-
- path: ui-ux-guidelines.md
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# Definition Of Done
|
|
2
|
-
|
|
3
|
-
A task is done only when the implementation, verification, documentation, and workflow closure requirements are all complete.
|
|
4
|
-
|
|
5
|
-
## Completion Criteria
|
|
6
|
-
|
|
7
|
-
- All in-scope acceptance criteria are satisfied or explicitly marked blocked with documented reason.
|
|
8
|
-
- Required tests, builds, and other verification commands pass according to the repository testing policy.
|
|
9
|
-
- Required evidence and verification artifacts are recorded.
|
|
10
|
-
- Product and technical documentation impact is resolved according to the repository documentation policy.
|
|
11
|
-
- Relevant CodeMap updates are completed when the changed code affects entrypoints, wiring, or maintained source structure.
|
|
12
|
-
- Task files, discussion references, and workflow registries are updated as needed.
|
|
13
|
-
- `.optima/` task, todo, evidence, SCR/docs, registry, discussion, and runtime tracking artifacts are written before the final commit.
|
|
14
|
-
- The authorized review and closure roles have completed their required checks.
|
|
15
|
-
- The final committed state includes all required code, documentation, `.optima` closure artifacts, and registry updates.
|
|
16
|
-
|
|
17
|
-
## Not Done Conditions
|
|
18
|
-
|
|
19
|
-
- Any required test or build fails.
|
|
20
|
-
- Evidence is missing for claimed verification.
|
|
21
|
-
- Documentation or CodeMap impact remains unresolved.
|
|
22
|
-
- Acceptance criteria are incomplete, unclear, or unverified.
|
|
23
|
-
- Required finalization or archiving steps are missing.
|
|
24
|
-
|
|
25
|
-
## Operational Rule
|
|
26
|
-
|
|
27
|
-
A task must not be marked complete while any Definition of Done item remains open.
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# Definition Of Ready
|
|
2
|
-
|
|
3
|
-
A task is ready to begin only when the repository has enough information to execute safely and efficiently without inventing scope.
|
|
4
|
-
|
|
5
|
-
## Readiness Criteria
|
|
6
|
-
|
|
7
|
-
- Scope is clear, bounded, and appropriate for the task's declared complexity.
|
|
8
|
-
- The task objective is specific enough that the next responsible agent can act without guessing intent.
|
|
9
|
-
- Acceptance criteria are present, testable, and aligned with the stated scope.
|
|
10
|
-
- Complexity, track, and slice are set correctly for the work being requested.
|
|
11
|
-
- Required dependencies, assumptions, blockers, and open questions are either resolved or explicitly recorded.
|
|
12
|
-
- Required pre-sync specialists have reviewed the task definition according to the active task model.
|
|
13
|
-
- An approved SCR exists whenever the workflow requires one.
|
|
14
|
-
- The relevant repository areas are identified well enough to begin safe investigation, design, or implementation.
|
|
15
|
-
|
|
16
|
-
## Not Ready Conditions
|
|
17
|
-
|
|
18
|
-
- Requirements are ambiguous or contradictory.
|
|
19
|
-
- Acceptance criteria are missing or too vague to verify.
|
|
20
|
-
- The task is larger or riskier than its current routing metadata suggests.
|
|
21
|
-
- Required specialist review has not happened yet.
|
|
22
|
-
- A required SCR is missing or not approved.
|
|
23
|
-
- Critical blockers or dependencies are unknown or unrecorded.
|
|
24
|
-
|
|
25
|
-
## Operational Rule
|
|
26
|
-
|
|
27
|
-
If the task fails the Definition of Ready, execution should pause until the missing information is resolved or explicitly recorded for follow-up.
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# Development Guidelines
|
|
2
|
-
|
|
3
|
-
These defaults are intended to be customized per repository when needed.
|
|
4
|
-
|
|
5
|
-
## Stack Notes
|
|
6
|
-
|
|
7
|
-
- Language: define in the repository if needed.
|
|
8
|
-
- Runtime / Framework: define in the repository if needed.
|
|
9
|
-
- Frontend stack: define in the repository if needed.
|
|
10
|
-
- Testing stack: define in the repository if needed.
|
|
11
|
-
- Database / storage: define in the repository if needed.
|
|
12
|
-
|
|
13
|
-
## Default Engineering Conventions
|
|
14
|
-
|
|
15
|
-
- Prefer clear module or feature boundaries over ad-hoc file placement.
|
|
16
|
-
- Keep external integrations behind stable interfaces or wrappers when practical.
|
|
17
|
-
- Update `.gitignore` when repository changes introduce generated, temporary, or sensitive files.
|
|
18
|
-
- Prefer stable dependency versions unless repository compatibility requires otherwise.
|
|
19
|
-
- Use dependency-provided setup or initialization utilities when they are the standard way to integrate the dependency safely.
|
|
20
|
-
- Document meaningful architecture changes in the repository's documentation before or alongside implementation.
|
|
21
|
-
- Keep code changes aligned with existing repository conventions unless the repository policy explicitly changes them.
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# Documentation Guidelines
|
|
2
|
-
|
|
3
|
-
## Documentation Goals
|
|
4
|
-
|
|
5
|
-
- Keep documentation easy to locate and update.
|
|
6
|
-
- Separate steady-state truth from change proposals and workflow records.
|
|
7
|
-
- Update documentation in the same change set as the implementation whenever the documented truth changes.
|
|
8
|
-
|
|
9
|
-
## Default Documentation Layout
|
|
10
|
-
|
|
11
|
-
- `docs/product/`: whole-product truth and top-level feature inventory
|
|
12
|
-
- `docs/domains/`: stable product-area truth shared by multiple features
|
|
13
|
-
- `docs/features/`: one concrete capability or feature specification
|
|
14
|
-
- `docs/architecture/`: technical design, contracts, and cross-cutting decisions
|
|
15
|
-
- `.optima/docs/scrs/`: proposed and approved changes, not steady-state truth
|
|
16
|
-
|
|
17
|
-
## Update Expectations
|
|
18
|
-
|
|
19
|
-
Update the relevant documentation when work changes:
|
|
20
|
-
|
|
21
|
-
- product behavior, terminology, or feature inventory
|
|
22
|
-
- architecture, interfaces, or technical invariants
|
|
23
|
-
- feature specifications or acceptance criteria
|
|
24
|
-
- documentation ownership, naming, or structure conventions
|
|
25
|
-
|
|
26
|
-
## Default Ownership
|
|
27
|
-
|
|
28
|
-
- Business Analyst: product, domain, and feature truth from the product perspective
|
|
29
|
-
- Technical Architect: architecture truth and technical design documentation
|
|
30
|
-
- Product Manager: verifies documentation closure during workflow execution
|
|
31
|
-
- Developer / Tech Lead / QA: contribute technical accuracy when implementation changes documented truth
|
|
32
|
-
|
|
33
|
-
## Default Repository Matrix
|
|
34
|
-
|
|
35
|
-
- Product overview: `docs/product/PRODUCT_OVERVIEW.md`
|
|
36
|
-
- Features list: `docs/product/FEATURES_LIST.md`
|
|
37
|
-
- Architecture: `docs/architecture/TECHNICAL_ARCHITECTURE.md`
|
|
38
|
-
- Feature specification: `docs/features/<feature>/SPECIFICATION.md`
|
|
39
|
-
- CodeMap updates: relevant `codemap.yml` files for changed code areas
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# Git Commit Messaging
|
|
2
|
-
|
|
3
|
-
Use a concise subject line in this format:
|
|
4
|
-
|
|
5
|
-
`<type>: <optional-task-id> <short summary>`
|
|
6
|
-
|
|
7
|
-
Examples:
|
|
8
|
-
|
|
9
|
-
- `docs: update workflow guidance`
|
|
10
|
-
- `fix: TASK-014 correct task archive logic`
|
|
11
|
-
|
|
12
|
-
Always include a brief body that explains what the commit is for and why the change exists.
|
|
13
|
-
|
|
14
|
-
If the commit is associated with a task, include the task ID in the subject when practical.
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# Product Guidelines
|
|
2
|
-
|
|
3
|
-
## Product Writing Defaults
|
|
4
|
-
|
|
5
|
-
- Write user stories and requirements in clear, unambiguous language.
|
|
6
|
-
- Keep acceptance criteria specific, testable, and easy to map to verification evidence.
|
|
7
|
-
- Use numbered acceptance criteria (`AC-1`, `AC-2`, ...) for tracked work.
|
|
8
|
-
- Maintain consistent product terminology across SCRs, tasks, and steady-state docs.
|
|
9
|
-
|
|
10
|
-
## User Story And Acceptance Criteria Conventions
|
|
11
|
-
|
|
12
|
-
- User stories may use the format: `As a <user>, I want <action>, so that <benefit>.`
|
|
13
|
-
- Acceptance criteria should describe observable behavior or outcomes rather than implementation details.
|
|
14
|
-
- When requirements are incomplete or ambiguous, stop and push for clarification instead of inventing scope.
|
|
15
|
-
|
|
16
|
-
## Product Truth Stewardship
|
|
17
|
-
|
|
18
|
-
- Keep product documentation cross-linked and internally consistent.
|
|
19
|
-
- When behavior changes, update the relevant product-facing docs and SCR registries.
|
|
20
|
-
- If the repository establishes domain or feature naming conventions, apply them consistently.
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# Testing Guidelines
|
|
2
|
-
|
|
3
|
-
## Test Levels
|
|
4
|
-
|
|
5
|
-
1. Unit tests verify isolated logic, functions, and classes.
|
|
6
|
-
2. Integration tests verify interactions between multiple modules or external services.
|
|
7
|
-
3. End-to-end tests verify real user or system flows through the product.
|
|
8
|
-
4. Manual verification is allowed for visual or interaction checks that cannot be automated effectively.
|
|
9
|
-
|
|
10
|
-
## Verification Policy
|
|
11
|
-
|
|
12
|
-
- All automated tests must pass. No expected skips or tolerated failures are allowed by default.
|
|
13
|
-
- Tests should live close to the code they verify unless the repository uses a clearly defined alternative structure.
|
|
14
|
-
- Every `implementation` task must produce reviewable verification artifacts under `.optima/evidences/<task_id>/`.
|
|
15
|
-
- Do not create or reference a root `evidences/` folder; implementation evidence belongs only under `.optima/evidences/<task_id>/`.
|
|
16
|
-
- Verification artifacts should map back to the task's numbered acceptance criteria.
|
|
17
|
-
- Run the relevant regression coverage before handing implementation back for technical review.
|
|
18
|
-
|
|
19
|
-
## Evidence Defaults
|
|
20
|
-
|
|
21
|
-
By default, `.optima/evidences/<task_id>/` implementation evidence should include:
|
|
22
|
-
|
|
23
|
-
- `SUMMARY.md` with a short summary and numbered acceptance criteria coverage
|
|
24
|
-
- `logs/` with command output or logs for relevant automated checks
|
|
25
|
-
- `screenshots/` with UI screenshots or visual review artifacts when UI changes are involved
|
|
26
|
-
|
|
27
|
-
## Non-Implementation Outputs
|
|
28
|
-
|
|
29
|
-
- `investigation` tasks should produce findings, reproduction notes, useful logs, and a recommended next step.
|
|
30
|
-
- `spec` tasks should produce SCR or documentation updates that define the accepted change and its impact.
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# UI/UX Guidelines
|
|
2
|
-
|
|
3
|
-
## Core Principles
|
|
4
|
-
|
|
5
|
-
1. Prioritize ease of use, accessibility, and intuitive navigation.
|
|
6
|
-
2. Aim for a modern, clean, and polished visual design.
|
|
7
|
-
3. Keep UI elements visually consistent with the repository's design language.
|
|
8
|
-
4. Use layout, color, and typography to create clear visual hierarchy.
|
|
9
|
-
|
|
10
|
-
## Review Workflow
|
|
11
|
-
|
|
12
|
-
- Define the intended screens, interactions, and layout before implementation when UI work is involved.
|
|
13
|
-
- Review screenshots and other visual evidence under `.optima/evidences/<task_id>/` after implementation; do not look for a root evidence folder.
|
|
14
|
-
- Evaluate the result visually rather than by reading code.
|
|
15
|
-
- If the available evidence is insufficient, say so clearly and ask for better screenshots or artifacts.
|
|
16
|
-
|
|
17
|
-
## Visual Quality Checklist
|
|
18
|
-
|
|
19
|
-
Reject or request fixes when you see:
|
|
20
|
-
|
|
21
|
-
- obvious misalignment against the page or component grid
|
|
22
|
-
- inconsistent spacing between similar elements
|
|
23
|
-
- weak typography hierarchy that makes the screen hard to scan
|
|
24
|
-
- interactive elements that do not look interactive
|
|
25
|
-
- low-contrast text or other readability issues
|
|
26
|
-
- cluttered, dated, or visibly unpolished presentation
|
|
27
|
-
|
|
28
|
-
## Required Fix Triggers
|
|
29
|
-
|
|
30
|
-
- overlapping UI or clipped text
|
|
31
|
-
- missing key interaction steps that were part of the intended flow
|
|
32
|
-
- ignored design system conventions for color, typography, or spacing
|
|
33
|
-
- an overall result that feels amateur or not ready for users
|