diff --git a/assets/oh-my-opencode.schema.json b/assets/oh-my-opencode.schema.json index c562bdad..343c3c07 100644 --- a/assets/oh-my-opencode.schema.json +++ b/assets/oh-my-opencode.schema.json @@ -2,3046 +2,5 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json", "title": "Oh My OpenCode Configuration", - "description": "Configuration schema for oh-my-opencode plugin", - "type": "object", - "properties": { - "$schema": { - "type": "string" - }, - "new_task_system_enabled": { - "type": "boolean" - }, - "default_run_agent": { - "type": "string" - }, - "disabled_mcps": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - } - }, - "disabled_agents": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "sisyphus", - "hephaestus", - "prometheus", - "oracle", - "librarian", - "explore", - "multimodal-looker", - "metis", - "momus", - "atlas" - ] - } - }, - "disabled_skills": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "playwright", - "agent-browser", - "frontend-ui-ux", - "git-master" - ] - } - }, - "disabled_hooks": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "todo-continuation-enforcer", - "context-window-monitor", - "session-recovery", - "session-notification", - "comment-checker", - "grep-output-truncator", - "tool-output-truncator", - "directory-agents-injector", - "directory-readme-injector", - "empty-task-response-detector", - "think-mode", - "anthropic-context-window-limit-recovery", - "preemptive-compaction", - "rules-injector", - "background-notification", - "auto-update-checker", - "startup-toast", - "keyword-detector", - "agent-usage-reminder", - "non-interactive-env", - "interactive-bash-session", - "thinking-block-validator", - "ralph-loop", - "category-skill-reminder", - "compaction-context-injector", - "claude-code-hooks", - "auto-slash-command", - "edit-error-recovery", - "delegate-task-retry", - "prometheus-md-only", - "sisyphus-junior-notepad", - "start-work", - "atlas", - "unstable-agent-babysitter", - "stop-continuation-guard", - "tasks-todowrite-disabler" - ] - } - }, - "disabled_commands": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "init-deep", - "start-work" - ] - } - }, - "disabled_tools": { - "type": "array", - "items": { - "type": "string" - } - }, - "agents": { - "type": "object", - "properties": { - "build": { - "type": "object", - "properties": { - "model": { - "type": "string" - }, - "variant": { - "type": "string" - }, - "category": { - "type": "string" - }, - "skills": { - "type": "array", - "items": { - "type": "string" - } - }, - "temperature": { - "type": "number", - "minimum": 0, - "maximum": 2 - }, - "top_p": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "prompt": { - "type": "string" - }, - "prompt_append": { - "type": "string" - }, - "tools": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "boolean" - } - }, - "disable": { - "type": "boolean" - }, - "description": { - "type": "string" - }, - "mode": { - "type": "string", - "enum": [ - "subagent", - "primary", - "all" - ] - }, - "color": { - "type": "string", - "pattern": "^#[0-9A-Fa-f]{6}$" - }, - "permission": { - "type": "object", - "properties": { - "edit": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "bash": { - "anyOf": [ - { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - ] - }, - "webfetch": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "doom_loop": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "external_directory": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - }, - "maxTokens": { - "type": "number" - }, - "thinking": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "enabled", - "disabled" - ] - }, - "budgetTokens": { - "type": "number" - } - }, - "required": [ - "type" - ] - }, - "reasoningEffort": { - "type": "string", - "enum": [ - "low", - "medium", - "high", - "xhigh" - ] - }, - "textVerbosity": { - "type": "string", - "enum": [ - "low", - "medium", - "high" - ] - }, - "providerOptions": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - } - }, - "plan": { - "type": "object", - "properties": { - "model": { - "type": "string" - }, - "variant": { - "type": "string" - }, - "category": { - "type": "string" - }, - "skills": { - "type": "array", - "items": { - "type": "string" - } - }, - "temperature": { - "type": "number", - "minimum": 0, - "maximum": 2 - }, - "top_p": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "prompt": { - "type": "string" - }, - "prompt_append": { - "type": "string" - }, - "tools": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "boolean" - } - }, - "disable": { - "type": "boolean" - }, - "description": { - "type": "string" - }, - "mode": { - "type": "string", - "enum": [ - "subagent", - "primary", - "all" - ] - }, - "color": { - "type": "string", - "pattern": "^#[0-9A-Fa-f]{6}$" - }, - "permission": { - "type": "object", - "properties": { - "edit": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "bash": { - "anyOf": [ - { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - ] - }, - "webfetch": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "doom_loop": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "external_directory": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - }, - "maxTokens": { - "type": "number" - }, - "thinking": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "enabled", - "disabled" - ] - }, - "budgetTokens": { - "type": "number" - } - }, - "required": [ - "type" - ] - }, - "reasoningEffort": { - "type": "string", - "enum": [ - "low", - "medium", - "high", - "xhigh" - ] - }, - "textVerbosity": { - "type": "string", - "enum": [ - "low", - "medium", - "high" - ] - }, - "providerOptions": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - } - }, - "sisyphus": { - "type": "object", - "properties": { - "model": { - "type": "string" - }, - "variant": { - "type": "string" - }, - "category": { - "type": "string" - }, - "skills": { - "type": "array", - "items": { - "type": "string" - } - }, - "temperature": { - "type": "number", - "minimum": 0, - "maximum": 2 - }, - "top_p": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "prompt": { - "type": "string" - }, - "prompt_append": { - "type": "string" - }, - "tools": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "boolean" - } - }, - "disable": { - "type": "boolean" - }, - "description": { - "type": "string" - }, - "mode": { - "type": "string", - "enum": [ - "subagent", - "primary", - "all" - ] - }, - "color": { - "type": "string", - "pattern": "^#[0-9A-Fa-f]{6}$" - }, - "permission": { - "type": "object", - "properties": { - "edit": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "bash": { - "anyOf": [ - { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - ] - }, - "webfetch": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "doom_loop": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "external_directory": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - }, - "maxTokens": { - "type": "number" - }, - "thinking": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "enabled", - "disabled" - ] - }, - "budgetTokens": { - "type": "number" - } - }, - "required": [ - "type" - ] - }, - "reasoningEffort": { - "type": "string", - "enum": [ - "low", - "medium", - "high", - "xhigh" - ] - }, - "textVerbosity": { - "type": "string", - "enum": [ - "low", - "medium", - "high" - ] - }, - "providerOptions": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - } - }, - "hephaestus": { - "type": "object", - "properties": { - "model": { - "type": "string" - }, - "variant": { - "type": "string" - }, - "category": { - "type": "string" - }, - "skills": { - "type": "array", - "items": { - "type": "string" - } - }, - "temperature": { - "type": "number", - "minimum": 0, - "maximum": 2 - }, - "top_p": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "prompt": { - "type": "string" - }, - "prompt_append": { - "type": "string" - }, - "tools": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "boolean" - } - }, - "disable": { - "type": "boolean" - }, - "description": { - "type": "string" - }, - "mode": { - "type": "string", - "enum": [ - "subagent", - "primary", - "all" - ] - }, - "color": { - "type": "string", - "pattern": "^#[0-9A-Fa-f]{6}$" - }, - "permission": { - "type": "object", - "properties": { - "edit": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "bash": { - "anyOf": [ - { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - ] - }, - "webfetch": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "doom_loop": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "external_directory": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - }, - "maxTokens": { - "type": "number" - }, - "thinking": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "enabled", - "disabled" - ] - }, - "budgetTokens": { - "type": "number" - } - }, - "required": [ - "type" - ] - }, - "reasoningEffort": { - "type": "string", - "enum": [ - "low", - "medium", - "high", - "xhigh" - ] - }, - "textVerbosity": { - "type": "string", - "enum": [ - "low", - "medium", - "high" - ] - }, - "providerOptions": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - } - }, - "sisyphus-junior": { - "type": "object", - "properties": { - "model": { - "type": "string" - }, - "variant": { - "type": "string" - }, - "category": { - "type": "string" - }, - "skills": { - "type": "array", - "items": { - "type": "string" - } - }, - "temperature": { - "type": "number", - "minimum": 0, - "maximum": 2 - }, - "top_p": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "prompt": { - "type": "string" - }, - "prompt_append": { - "type": "string" - }, - "tools": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "boolean" - } - }, - "disable": { - "type": "boolean" - }, - "description": { - "type": "string" - }, - "mode": { - "type": "string", - "enum": [ - "subagent", - "primary", - "all" - ] - }, - "color": { - "type": "string", - "pattern": "^#[0-9A-Fa-f]{6}$" - }, - "permission": { - "type": "object", - "properties": { - "edit": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "bash": { - "anyOf": [ - { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - ] - }, - "webfetch": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "doom_loop": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "external_directory": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - }, - "maxTokens": { - "type": "number" - }, - "thinking": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "enabled", - "disabled" - ] - }, - "budgetTokens": { - "type": "number" - } - }, - "required": [ - "type" - ] - }, - "reasoningEffort": { - "type": "string", - "enum": [ - "low", - "medium", - "high", - "xhigh" - ] - }, - "textVerbosity": { - "type": "string", - "enum": [ - "low", - "medium", - "high" - ] - }, - "providerOptions": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - } - }, - "OpenCode-Builder": { - "type": "object", - "properties": { - "model": { - "type": "string" - }, - "variant": { - "type": "string" - }, - "category": { - "type": "string" - }, - "skills": { - "type": "array", - "items": { - "type": "string" - } - }, - "temperature": { - "type": "number", - "minimum": 0, - "maximum": 2 - }, - "top_p": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "prompt": { - "type": "string" - }, - "prompt_append": { - "type": "string" - }, - "tools": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "boolean" - } - }, - "disable": { - "type": "boolean" - }, - "description": { - "type": "string" - }, - "mode": { - "type": "string", - "enum": [ - "subagent", - "primary", - "all" - ] - }, - "color": { - "type": "string", - "pattern": "^#[0-9A-Fa-f]{6}$" - }, - "permission": { - "type": "object", - "properties": { - "edit": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "bash": { - "anyOf": [ - { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - ] - }, - "webfetch": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "doom_loop": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "external_directory": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - }, - "maxTokens": { - "type": "number" - }, - "thinking": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "enabled", - "disabled" - ] - }, - "budgetTokens": { - "type": "number" - } - }, - "required": [ - "type" - ] - }, - "reasoningEffort": { - "type": "string", - "enum": [ - "low", - "medium", - "high", - "xhigh" - ] - }, - "textVerbosity": { - "type": "string", - "enum": [ - "low", - "medium", - "high" - ] - }, - "providerOptions": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - } - }, - "prometheus": { - "type": "object", - "properties": { - "model": { - "type": "string" - }, - "variant": { - "type": "string" - }, - "category": { - "type": "string" - }, - "skills": { - "type": "array", - "items": { - "type": "string" - } - }, - "temperature": { - "type": "number", - "minimum": 0, - "maximum": 2 - }, - "top_p": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "prompt": { - "type": "string" - }, - "prompt_append": { - "type": "string" - }, - "tools": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "boolean" - } - }, - "disable": { - "type": "boolean" - }, - "description": { - "type": "string" - }, - "mode": { - "type": "string", - "enum": [ - "subagent", - "primary", - "all" - ] - }, - "color": { - "type": "string", - "pattern": "^#[0-9A-Fa-f]{6}$" - }, - "permission": { - "type": "object", - "properties": { - "edit": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "bash": { - "anyOf": [ - { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - ] - }, - "webfetch": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "doom_loop": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "external_directory": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - }, - "maxTokens": { - "type": "number" - }, - "thinking": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "enabled", - "disabled" - ] - }, - "budgetTokens": { - "type": "number" - } - }, - "required": [ - "type" - ] - }, - "reasoningEffort": { - "type": "string", - "enum": [ - "low", - "medium", - "high", - "xhigh" - ] - }, - "textVerbosity": { - "type": "string", - "enum": [ - "low", - "medium", - "high" - ] - }, - "providerOptions": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - } - }, - "metis": { - "type": "object", - "properties": { - "model": { - "type": "string" - }, - "variant": { - "type": "string" - }, - "category": { - "type": "string" - }, - "skills": { - "type": "array", - "items": { - "type": "string" - } - }, - "temperature": { - "type": "number", - "minimum": 0, - "maximum": 2 - }, - "top_p": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "prompt": { - "type": "string" - }, - "prompt_append": { - "type": "string" - }, - "tools": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "boolean" - } - }, - "disable": { - "type": "boolean" - }, - "description": { - "type": "string" - }, - "mode": { - "type": "string", - "enum": [ - "subagent", - "primary", - "all" - ] - }, - "color": { - "type": "string", - "pattern": "^#[0-9A-Fa-f]{6}$" - }, - "permission": { - "type": "object", - "properties": { - "edit": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "bash": { - "anyOf": [ - { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - ] - }, - "webfetch": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "doom_loop": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "external_directory": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - }, - "maxTokens": { - "type": "number" - }, - "thinking": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "enabled", - "disabled" - ] - }, - "budgetTokens": { - "type": "number" - } - }, - "required": [ - "type" - ] - }, - "reasoningEffort": { - "type": "string", - "enum": [ - "low", - "medium", - "high", - "xhigh" - ] - }, - "textVerbosity": { - "type": "string", - "enum": [ - "low", - "medium", - "high" - ] - }, - "providerOptions": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - } - }, - "momus": { - "type": "object", - "properties": { - "model": { - "type": "string" - }, - "variant": { - "type": "string" - }, - "category": { - "type": "string" - }, - "skills": { - "type": "array", - "items": { - "type": "string" - } - }, - "temperature": { - "type": "number", - "minimum": 0, - "maximum": 2 - }, - "top_p": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "prompt": { - "type": "string" - }, - "prompt_append": { - "type": "string" - }, - "tools": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "boolean" - } - }, - "disable": { - "type": "boolean" - }, - "description": { - "type": "string" - }, - "mode": { - "type": "string", - "enum": [ - "subagent", - "primary", - "all" - ] - }, - "color": { - "type": "string", - "pattern": "^#[0-9A-Fa-f]{6}$" - }, - "permission": { - "type": "object", - "properties": { - "edit": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "bash": { - "anyOf": [ - { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - ] - }, - "webfetch": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "doom_loop": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "external_directory": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - }, - "maxTokens": { - "type": "number" - }, - "thinking": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "enabled", - "disabled" - ] - }, - "budgetTokens": { - "type": "number" - } - }, - "required": [ - "type" - ] - }, - "reasoningEffort": { - "type": "string", - "enum": [ - "low", - "medium", - "high", - "xhigh" - ] - }, - "textVerbosity": { - "type": "string", - "enum": [ - "low", - "medium", - "high" - ] - }, - "providerOptions": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - } - }, - "oracle": { - "type": "object", - "properties": { - "model": { - "type": "string" - }, - "variant": { - "type": "string" - }, - "category": { - "type": "string" - }, - "skills": { - "type": "array", - "items": { - "type": "string" - } - }, - "temperature": { - "type": "number", - "minimum": 0, - "maximum": 2 - }, - "top_p": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "prompt": { - "type": "string" - }, - "prompt_append": { - "type": "string" - }, - "tools": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "boolean" - } - }, - "disable": { - "type": "boolean" - }, - "description": { - "type": "string" - }, - "mode": { - "type": "string", - "enum": [ - "subagent", - "primary", - "all" - ] - }, - "color": { - "type": "string", - "pattern": "^#[0-9A-Fa-f]{6}$" - }, - "permission": { - "type": "object", - "properties": { - "edit": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "bash": { - "anyOf": [ - { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - ] - }, - "webfetch": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "doom_loop": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "external_directory": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - }, - "maxTokens": { - "type": "number" - }, - "thinking": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "enabled", - "disabled" - ] - }, - "budgetTokens": { - "type": "number" - } - }, - "required": [ - "type" - ] - }, - "reasoningEffort": { - "type": "string", - "enum": [ - "low", - "medium", - "high", - "xhigh" - ] - }, - "textVerbosity": { - "type": "string", - "enum": [ - "low", - "medium", - "high" - ] - }, - "providerOptions": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - } - }, - "librarian": { - "type": "object", - "properties": { - "model": { - "type": "string" - }, - "variant": { - "type": "string" - }, - "category": { - "type": "string" - }, - "skills": { - "type": "array", - "items": { - "type": "string" - } - }, - "temperature": { - "type": "number", - "minimum": 0, - "maximum": 2 - }, - "top_p": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "prompt": { - "type": "string" - }, - "prompt_append": { - "type": "string" - }, - "tools": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "boolean" - } - }, - "disable": { - "type": "boolean" - }, - "description": { - "type": "string" - }, - "mode": { - "type": "string", - "enum": [ - "subagent", - "primary", - "all" - ] - }, - "color": { - "type": "string", - "pattern": "^#[0-9A-Fa-f]{6}$" - }, - "permission": { - "type": "object", - "properties": { - "edit": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "bash": { - "anyOf": [ - { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - ] - }, - "webfetch": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "doom_loop": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "external_directory": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - }, - "maxTokens": { - "type": "number" - }, - "thinking": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "enabled", - "disabled" - ] - }, - "budgetTokens": { - "type": "number" - } - }, - "required": [ - "type" - ] - }, - "reasoningEffort": { - "type": "string", - "enum": [ - "low", - "medium", - "high", - "xhigh" - ] - }, - "textVerbosity": { - "type": "string", - "enum": [ - "low", - "medium", - "high" - ] - }, - "providerOptions": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - } - }, - "explore": { - "type": "object", - "properties": { - "model": { - "type": "string" - }, - "variant": { - "type": "string" - }, - "category": { - "type": "string" - }, - "skills": { - "type": "array", - "items": { - "type": "string" - } - }, - "temperature": { - "type": "number", - "minimum": 0, - "maximum": 2 - }, - "top_p": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "prompt": { - "type": "string" - }, - "prompt_append": { - "type": "string" - }, - "tools": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "boolean" - } - }, - "disable": { - "type": "boolean" - }, - "description": { - "type": "string" - }, - "mode": { - "type": "string", - "enum": [ - "subagent", - "primary", - "all" - ] - }, - "color": { - "type": "string", - "pattern": "^#[0-9A-Fa-f]{6}$" - }, - "permission": { - "type": "object", - "properties": { - "edit": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "bash": { - "anyOf": [ - { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - ] - }, - "webfetch": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "doom_loop": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "external_directory": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - }, - "maxTokens": { - "type": "number" - }, - "thinking": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "enabled", - "disabled" - ] - }, - "budgetTokens": { - "type": "number" - } - }, - "required": [ - "type" - ] - }, - "reasoningEffort": { - "type": "string", - "enum": [ - "low", - "medium", - "high", - "xhigh" - ] - }, - "textVerbosity": { - "type": "string", - "enum": [ - "low", - "medium", - "high" - ] - }, - "providerOptions": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - } - }, - "multimodal-looker": { - "type": "object", - "properties": { - "model": { - "type": "string" - }, - "variant": { - "type": "string" - }, - "category": { - "type": "string" - }, - "skills": { - "type": "array", - "items": { - "type": "string" - } - }, - "temperature": { - "type": "number", - "minimum": 0, - "maximum": 2 - }, - "top_p": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "prompt": { - "type": "string" - }, - "prompt_append": { - "type": "string" - }, - "tools": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "boolean" - } - }, - "disable": { - "type": "boolean" - }, - "description": { - "type": "string" - }, - "mode": { - "type": "string", - "enum": [ - "subagent", - "primary", - "all" - ] - }, - "color": { - "type": "string", - "pattern": "^#[0-9A-Fa-f]{6}$" - }, - "permission": { - "type": "object", - "properties": { - "edit": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "bash": { - "anyOf": [ - { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - ] - }, - "webfetch": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "doom_loop": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "external_directory": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - }, - "maxTokens": { - "type": "number" - }, - "thinking": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "enabled", - "disabled" - ] - }, - "budgetTokens": { - "type": "number" - } - }, - "required": [ - "type" - ] - }, - "reasoningEffort": { - "type": "string", - "enum": [ - "low", - "medium", - "high", - "xhigh" - ] - }, - "textVerbosity": { - "type": "string", - "enum": [ - "low", - "medium", - "high" - ] - }, - "providerOptions": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - } - }, - "atlas": { - "type": "object", - "properties": { - "model": { - "type": "string" - }, - "variant": { - "type": "string" - }, - "category": { - "type": "string" - }, - "skills": { - "type": "array", - "items": { - "type": "string" - } - }, - "temperature": { - "type": "number", - "minimum": 0, - "maximum": 2 - }, - "top_p": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "prompt": { - "type": "string" - }, - "prompt_append": { - "type": "string" - }, - "tools": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "boolean" - } - }, - "disable": { - "type": "boolean" - }, - "description": { - "type": "string" - }, - "mode": { - "type": "string", - "enum": [ - "subagent", - "primary", - "all" - ] - }, - "color": { - "type": "string", - "pattern": "^#[0-9A-Fa-f]{6}$" - }, - "permission": { - "type": "object", - "properties": { - "edit": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "bash": { - "anyOf": [ - { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - ] - }, - "webfetch": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "doom_loop": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - }, - "external_directory": { - "type": "string", - "enum": [ - "ask", - "allow", - "deny" - ] - } - } - }, - "maxTokens": { - "type": "number" - }, - "thinking": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "enabled", - "disabled" - ] - }, - "budgetTokens": { - "type": "number" - } - }, - "required": [ - "type" - ] - }, - "reasoningEffort": { - "type": "string", - "enum": [ - "low", - "medium", - "high", - "xhigh" - ] - }, - "textVerbosity": { - "type": "string", - "enum": [ - "low", - "medium", - "high" - ] - }, - "providerOptions": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - } - } - } - } - }, - "categories": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "model": { - "type": "string" - }, - "variant": { - "type": "string" - }, - "temperature": { - "type": "number", - "minimum": 0, - "maximum": 2 - }, - "top_p": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "maxTokens": { - "type": "number" - }, - "thinking": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "enabled", - "disabled" - ] - }, - "budgetTokens": { - "type": "number" - } - }, - "required": [ - "type" - ] - }, - "reasoningEffort": { - "type": "string", - "enum": [ - "low", - "medium", - "high", - "xhigh" - ] - }, - "textVerbosity": { - "type": "string", - "enum": [ - "low", - "medium", - "high" - ] - }, - "tools": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "boolean" - } - }, - "prompt_append": { - "type": "string" - }, - "is_unstable_agent": { - "type": "boolean" - } - } - } - }, - "claude_code": { - "type": "object", - "properties": { - "mcp": { - "type": "boolean" - }, - "commands": { - "type": "boolean" - }, - "skills": { - "type": "boolean" - }, - "agents": { - "type": "boolean" - }, - "hooks": { - "type": "boolean" - }, - "plugins": { - "type": "boolean" - }, - "plugins_override": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "boolean" - } - } - } - }, - "sisyphus_agent": { - "type": "object", - "properties": { - "disabled": { - "type": "boolean" - }, - "default_builder_enabled": { - "type": "boolean" - }, - "planner_enabled": { - "type": "boolean" - }, - "replace_plan": { - "type": "boolean" - } - } - }, - "comment_checker": { - "type": "object", - "properties": { - "custom_prompt": { - "type": "string" - } - } - }, - "experimental": { - "type": "object", - "properties": { - "aggressive_truncation": { - "type": "boolean" - }, - "auto_resume": { - "type": "boolean" - }, - "preemptive_compaction": { - "type": "boolean" - }, - "truncate_all_tool_outputs": { - "type": "boolean" - }, - "dynamic_context_pruning": { - "type": "object", - "properties": { - "enabled": { - "default": false, - "type": "boolean" - }, - "notification": { - "default": "detailed", - "type": "string", - "enum": [ - "off", - "minimal", - "detailed" - ] - }, - "turn_protection": { - "type": "object", - "properties": { - "enabled": { - "default": true, - "type": "boolean" - }, - "turns": { - "default": 3, - "type": "number", - "minimum": 1, - "maximum": 10 - } - } - }, - "protected_tools": { - "default": [ - "task", - "todowrite", - "todoread", - "lsp_rename", - "session_read", - "session_write", - "session_search" - ], - "type": "array", - "items": { - "type": "string" - } - }, - "strategies": { - "type": "object", - "properties": { - "deduplication": { - "type": "object", - "properties": { - "enabled": { - "default": true, - "type": "boolean" - } - } - }, - "supersede_writes": { - "type": "object", - "properties": { - "enabled": { - "default": true, - "type": "boolean" - }, - "aggressive": { - "default": false, - "type": "boolean" - } - } - }, - "purge_errors": { - "type": "object", - "properties": { - "enabled": { - "default": true, - "type": "boolean" - }, - "turns": { - "default": 5, - "type": "number", - "minimum": 1, - "maximum": 20 - } - } - } - } - } - } - }, - "task_system": { - "type": "boolean" - } - } - }, - "auto_update": { - "type": "boolean" - }, - "skills": { - "anyOf": [ - { - "type": "array", - "items": { - "type": "string" - } - }, - { - "allOf": [ - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "template": { - "type": "string" - }, - "from": { - "type": "string" - }, - "model": { - "type": "string" - }, - "agent": { - "type": "string" - }, - "subtask": { - "type": "boolean" - }, - "argument-hint": { - "type": "string" - }, - "license": { - "type": "string" - }, - "compatibility": { - "type": "string" - }, - "metadata": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": {} - }, - "allowed-tools": { - "type": "array", - "items": { - "type": "string" - } - }, - "disable": { - "type": "boolean" - } - } - } - ] - } - }, - { - "type": "object", - "properties": { - "sources": { - "type": "array", - "items": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "object", - "properties": { - "path": { - "type": "string" - }, - "recursive": { - "type": "boolean" - }, - "glob": { - "type": "string" - } - }, - "required": [ - "path" - ] - } - ] - } - }, - "enable": { - "type": "array", - "items": { - "type": "string" - } - }, - "disable": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - ] - } - ] - }, - "ralph_loop": { - "type": "object", - "properties": { - "enabled": { - "default": false, - "type": "boolean" - }, - "default_max_iterations": { - "default": 100, - "type": "number", - "minimum": 1, - "maximum": 1000 - }, - "state_dir": { - "type": "string" - } - } - }, - "background_task": { - "type": "object", - "properties": { - "defaultConcurrency": { - "type": "number", - "minimum": 1 - }, - "providerConcurrency": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "number", - "minimum": 0 - } - }, - "modelConcurrency": { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "type": "number", - "minimum": 0 - } - }, - "staleTimeoutMs": { - "type": "number", - "minimum": 60000 - } - } - }, - "notification": { - "type": "object", - "properties": { - "force_enable": { - "type": "boolean" - } - } - }, - "babysitting": { - "type": "object", - "properties": { - "timeout_ms": { - "default": 120000, - "type": "number" - } - } - }, - "git_master": { - "type": "object", - "properties": { - "commit_footer": { - "default": true, - "type": "boolean" - }, - "include_co_authored_by": { - "default": true, - "type": "boolean" - } - } - }, - "browser_automation_engine": { - "type": "object", - "properties": { - "provider": { - "default": "playwright", - "type": "string", - "enum": [ - "playwright", - "agent-browser", - "dev-browser" - ] - } - } - }, - "websearch": { - "type": "object", - "properties": { - "provider": { - "type": "string", - "enum": [ - "exa", - "tavily" - ] - } - } - }, - "tmux": { - "type": "object", - "properties": { - "enabled": { - "default": false, - "type": "boolean" - }, - "layout": { - "default": "main-vertical", - "type": "string", - "enum": [ - "main-horizontal", - "main-vertical", - "tiled", - "even-horizontal", - "even-vertical" - ] - }, - "main_pane_size": { - "default": 60, - "type": "number", - "minimum": 20, - "maximum": 80 - }, - "main_pane_min_width": { - "default": 120, - "type": "number", - "minimum": 40 - }, - "agent_pane_min_width": { - "default": 40, - "type": "number", - "minimum": 20 - } - } - }, - "sisyphus": { - "type": "object", - "properties": { - "tasks": { - "type": "object", - "properties": { - "storage_path": { - "default": ".sisyphus/tasks", - "type": "string" - }, - "claude_code_compat": { - "default": false, - "type": "boolean" - } - } - } - } - } - } + "description": "Configuration schema for oh-my-opencode plugin" } \ No newline at end of file diff --git a/script/build-schema.ts b/script/build-schema.ts index e2b0aa66..d0ed70d3 100644 --- a/script/build-schema.ts +++ b/script/build-schema.ts @@ -1,5 +1,6 @@ #!/usr/bin/env bun import * as z from "zod" +import { zodToJsonSchema } from "zod-to-json-schema" import { OhMyOpenCodeConfigSchema } from "../src/config/schema" const SCHEMA_OUTPUT_PATH = "assets/oh-my-opencode.schema.json" @@ -7,9 +8,8 @@ const SCHEMA_OUTPUT_PATH = "assets/oh-my-opencode.schema.json" async function main() { console.log("Generating JSON Schema...") - const jsonSchema = z.toJSONSchema(OhMyOpenCodeConfigSchema, { - io: "input", - target: "draft-7", + const jsonSchema = zodToJsonSchema(OhMyOpenCodeConfigSchema, { + target: "draft7", }) const finalSchema = { diff --git a/src/config/schema.ts b/src/config/schema.ts index d0aca627..b7441055 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -368,8 +368,10 @@ export const TmuxConfigSchema = z.object({ }) export const SisyphusTasksConfigSchema = z.object({ - /** Storage path for tasks (default: .sisyphus/tasks) */ - storage_path: z.string().default(".sisyphus/tasks"), + /** Absolute or relative storage path override. When set, bypasses global config dir. */ + storage_path: z.string().optional(), + /** Force task list ID (alternative to env ULTRAWORK_TASK_LIST_ID) */ + task_list_id: z.string().optional(), /** Enable Claude Code path compatibility mode */ claude_code_compat: z.boolean().default(false), }) diff --git a/src/features/claude-tasks/AGENTS.md b/src/features/claude-tasks/AGENTS.md index 356cdd65..4b152a2d 100644 --- a/src/features/claude-tasks/AGENTS.md +++ b/src/features/claude-tasks/AGENTS.md @@ -54,7 +54,26 @@ The task system includes a sync layer (`todo-sync.ts`) that automatically mirror ### getTaskDir(config) -Returns: `.sisyphus/tasks` (or custom path from config) +Returns the task storage directory path. + +**Default behavior (no config override):** +Returns `~/.config/opencode/tasks//` where: +- Task list ID is resolved via `resolveTaskListId()` + +**With `storage_path` config:** +- Absolute paths (starting with `/`) are returned as-is +- Relative paths are joined with `process.cwd()` + +### resolveTaskListId(config) + +Resolves the task list ID for directory scoping. + +**Priority order:** +1. `ULTRAWORK_TASK_LIST_ID` environment variable +2. `config.sisyphus?.tasks?.task_list_id` config option +3. Sanitized `basename(process.cwd())` as fallback + +**Sanitization:** Replaces non-alphanumeric characters (except `-` and `_`) with `-` ### readJsonSafe(filePath, schema) diff --git a/src/features/claude-tasks/storage.test.ts b/src/features/claude-tasks/storage.test.ts index 9fbc9b4a..8ceda380 100644 --- a/src/features/claude-tasks/storage.test.ts +++ b/src/features/claude-tasks/storage.test.ts @@ -1,26 +1,99 @@ import { describe, test, expect, beforeEach, afterEach } from "bun:test" import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs" -import { join } from "path" +import { join, basename } from "path" import { z } from "zod" -import { getTaskDir, readJsonSafe, writeJsonAtomic, acquireLock, generateTaskId, listTaskFiles } from "./storage" +import { getOpenCodeConfigDir } from "../../shared/opencode-config-dir" +import { + getTaskDir, + readJsonSafe, + writeJsonAtomic, + acquireLock, + generateTaskId, + listTaskFiles, + resolveTaskListId, + sanitizePathSegment, +} from "./storage" import type { OhMyOpenCodeConfig } from "../../config/schema" const TEST_DIR = ".test-claude-tasks" const TEST_DIR_ABS = join(process.cwd(), TEST_DIR) describe("getTaskDir", () => { - test("returns correct path for default config", () => { + const originalTaskListId = process.env.ULTRAWORK_TASK_LIST_ID + + beforeEach(() => { + if (originalTaskListId === undefined) { + delete process.env.ULTRAWORK_TASK_LIST_ID + } else { + process.env.ULTRAWORK_TASK_LIST_ID = originalTaskListId + } + }) + + afterEach(() => { + if (originalTaskListId === undefined) { + delete process.env.ULTRAWORK_TASK_LIST_ID + } else { + process.env.ULTRAWORK_TASK_LIST_ID = originalTaskListId + } + }) + + test("returns global config path for default config", () => { //#given const config: Partial = {} + const configDir = getOpenCodeConfigDir({ binary: "opencode" }) + const expectedListId = sanitizePathSegment(basename(process.cwd())) //#when const result = getTaskDir(config) //#then - expect(result).toBe(join(process.cwd(), ".sisyphus/tasks")) + expect(result).toBe(join(configDir, "tasks", expectedListId)) }) - test("returns correct path with custom storage_path", () => { + test("respects ULTRAWORK_TASK_LIST_ID env var", () => { + //#given + process.env.ULTRAWORK_TASK_LIST_ID = "custom list/id" + const configDir = getOpenCodeConfigDir({ binary: "opencode" }) + + //#when + const result = getTaskDir() + + //#then + expect(result).toBe(join(configDir, "tasks", "custom-list-id")) + }) + + test("falls back to sanitized cwd basename when env var not set", () => { + //#given + delete process.env.ULTRAWORK_TASK_LIST_ID + const configDir = getOpenCodeConfigDir({ binary: "opencode" }) + const expectedListId = sanitizePathSegment(basename(process.cwd())) + + //#when + const result = getTaskDir() + + //#then + expect(result).toBe(join(configDir, "tasks", expectedListId)) + }) + + test("returns absolute storage_path without joining cwd", () => { + //#given + const config: Partial = { + sisyphus: { + tasks: { + storage_path: "/tmp/custom-task-path", + claude_code_compat: false, + }, + }, + } + + //#when + const result = getTaskDir(config) + + //#then + expect(result).toBe("/tmp/custom-task-path") + }) + + test("joins relative storage_path with cwd", () => { //#given const config: Partial = { sisyphus: { @@ -37,13 +110,59 @@ describe("getTaskDir", () => { //#then expect(result).toBe(join(process.cwd(), ".custom/tasks")) }) +}) + +describe("resolveTaskListId", () => { + const originalTaskListId = process.env.ULTRAWORK_TASK_LIST_ID + + beforeEach(() => { + if (originalTaskListId === undefined) { + delete process.env.ULTRAWORK_TASK_LIST_ID + } else { + process.env.ULTRAWORK_TASK_LIST_ID = originalTaskListId + } + }) + + afterEach(() => { + if (originalTaskListId === undefined) { + delete process.env.ULTRAWORK_TASK_LIST_ID + } else { + process.env.ULTRAWORK_TASK_LIST_ID = originalTaskListId + } + }) + + test("returns env var when set", () => { + //#given + process.env.ULTRAWORK_TASK_LIST_ID = "custom-list" - test("returns correct path with default config parameter", () => { //#when - const result = getTaskDir() + const result = resolveTaskListId() //#then - expect(result).toBe(join(process.cwd(), ".sisyphus/tasks")) + expect(result).toBe("custom-list") + }) + + test("sanitizes special characters", () => { + //#given + process.env.ULTRAWORK_TASK_LIST_ID = "custom list/id" + + //#when + const result = resolveTaskListId() + + //#then + expect(result).toBe("custom-list-id") + }) + + test("returns sanitized cwd basename when env var not set", () => { + //#given + delete process.env.ULTRAWORK_TASK_LIST_ID + const expected = sanitizePathSegment(basename(process.cwd())) + + //#when + const result = resolveTaskListId() + + //#then + expect(result).toBe(expected) }) }) diff --git a/src/features/claude-tasks/storage.ts b/src/features/claude-tasks/storage.ts index e889c1df..b8916d4e 100644 --- a/src/features/claude-tasks/storage.ts +++ b/src/features/claude-tasks/storage.ts @@ -1,13 +1,35 @@ -import { join, dirname } from "path" +import { join, dirname, basename, isAbsolute } from "path" import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, unlinkSync, readdirSync } from "fs" import { randomUUID } from "crypto" +import { getOpenCodeConfigDir } from "../../shared/opencode-config-dir" import type { z } from "zod" import type { OhMyOpenCodeConfig } from "../../config/schema" export function getTaskDir(config: Partial = {}): string { const tasksConfig = config.sisyphus?.tasks - const storagePath = tasksConfig?.storage_path ?? ".sisyphus/tasks" - return join(process.cwd(), storagePath) + const storagePath = tasksConfig?.storage_path + + if (storagePath) { + return isAbsolute(storagePath) ? storagePath : join(process.cwd(), storagePath) + } + + const configDir = getOpenCodeConfigDir({ binary: "opencode" }) + const listId = resolveTaskListId(config) + return join(configDir, "tasks", listId) +} + +export function sanitizePathSegment(value: string): string { + return value.replace(/[^a-zA-Z0-9_-]/g, "-") || "default" +} + +export function resolveTaskListId(config: Partial = {}): string { + const envId = process.env.ULTRAWORK_TASK_LIST_ID?.trim() + if (envId) return sanitizePathSegment(envId) + + const configId = config.sisyphus?.tasks?.task_list_id?.trim() + if (configId) return sanitizePathSegment(configId) + + return sanitizePathSegment(basename(process.cwd())) } export function ensureDir(dirPath: string): void { diff --git a/src/tools/task/task-list.ts b/src/tools/task/task-list.ts index d7ba921b..2fbdb55b 100644 --- a/src/tools/task/task-list.ts +++ b/src/tools/task/task-list.ts @@ -4,7 +4,7 @@ import { existsSync, readdirSync } from "fs" import type { OhMyOpenCodeConfig } from "../../config/schema" import type { TaskObject, TaskStatus } from "./types" import { TaskObjectSchema } from "./types" -import { readJsonSafe } from "../../features/claude-tasks/storage" +import { readJsonSafe, getTaskDir } from "../../features/claude-tasks/storage" interface TaskSummary { id: string @@ -23,9 +23,7 @@ For each task's blockedBy field, filters to only include unresolved (non-complet Returns summary format: id, subject, status, owner, blockedBy (not full description).`, args: {}, execute: async (): Promise => { - const tasksConfig = config.sisyphus?.tasks - const storagePath = tasksConfig?.storage_path ?? ".sisyphus/tasks" - const taskDir = storagePath.startsWith("/") ? storagePath : join(process.cwd(), storagePath) + const taskDir = getTaskDir(config) if (!existsSync(taskDir)) { return JSON.stringify({ tasks: [] })