From cbae9fa1c931162dc9ece8c1ee0ff7d104d7e223 Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Tue, 7 Apr 2026 13:10:35 -0400 Subject: [PATCH] style: standardize formatting in opencode tooling files Reformat JSON configs and TypeScript scripts to use consistent tab indentation, semicolons, and double quotes. --- .../zod-validation-expert/.openskills.json | 10 +- .opencode/config/agent-metadata.json | 700 +++++++------- .opencode/context/core/config/paths.json | 10 +- .../task-management/scripts/task-cli.ts | 859 ++++++++++-------- .opencode/tool/env/index.ts | 236 ++--- 5 files changed, 936 insertions(+), 879 deletions(-) diff --git a/.claude/skills/zod-validation-expert/.openskills.json b/.claude/skills/zod-validation-expert/.openskills.json index 5896062..6814ca3 100644 --- a/.claude/skills/zod-validation-expert/.openskills.json +++ b/.claude/skills/zod-validation-expert/.openskills.json @@ -1,6 +1,6 @@ { - "source": "/tmp/skill-selector-curated-1953505229", - "sourceType": "local", - "localPath": "/tmp/skill-selector-curated-1953505229/zod-validation-expert", - "installedAt": "2026-04-07T15:11:20.921Z" -} \ No newline at end of file + "source": "/tmp/skill-selector-curated-1953505229", + "sourceType": "local", + "localPath": "/tmp/skill-selector-curated-1953505229/zod-validation-expert", + "installedAt": "2026-04-07T15:11:20.921Z" +} diff --git a/.opencode/config/agent-metadata.json b/.opencode/config/agent-metadata.json index 91ca208..e120d17 100644 --- a/.opencode/config/agent-metadata.json +++ b/.opencode/config/agent-metadata.json @@ -1,363 +1,341 @@ { - "$schema": "https://opencode.ai/schemas/agent-metadata.json", - "schema_version": "1.0.0", - "description": "Centralized metadata for OpenAgents Control agents. This file stores metadata that is not part of the OpenCode agent schema but is needed for registry management, installation, and documentation.", - "agents": { - "openagent": { - "id": "openagent", - "name": "OpenAgent", - "category": "core", - "type": "agent", - "version": "1.0.0", - "author": "opencode", - "tags": ["universal", "coordination", "primary"], - "dependencies": [ - "subagent:task-manager", - "subagent:batch-executor", - "subagent:documentation", - "subagent:contextscout", - "subagent:externalscout", - "context:standards-code", - "context:standards-docs", - "context:standards-tests", - "context:review-ref", - "context:delegation-ref", - "context:external-libraries-workflow" - ] - }, - "opencoder": { - "id": "opencoder", - "name": "OpenCoder", - "category": "core", - "type": "agent", - "version": "1.0.0", - "author": "opencode", - "tags": ["development", "coding", "implementation"], - "dependencies": [ - "subagent:documentation", - "subagent:task-manager", - "subagent:batch-executor", - "subagent:coder-agent", - "subagent:tester", - "subagent:reviewer", - "subagent:build-agent", - "subagent:contextscout", - "subagent:externalscout", - "context:standards-code", - "context:task-delegation-basics", - "context:component-planning", - "context:external-libraries-workflow" - ] - }, - "repo-manager": { - "id": "repo-manager", - "name": "Repo Manager", - "category": "meta", - "type": "agent", - "version": "1.0.0", - "author": "opencode", - "tags": ["repository", "management", "orchestration"], - "dependencies": [ - "subagent:task-manager", - "subagent:contextscout", - "subagent:documentation", - "subagent:coder-agent", - "subagent:tester", - "subagent:reviewer", - "subagent:build-agent" - ] - }, - "system-builder": { - "id": "system-builder", - "name": "System Builder", - "category": "meta", - "type": "agent", - "version": "1.0.0", - "author": "opencode", - "tags": ["system-generation", "architecture", "scaffolding"], - "dependencies": [ - "subagent:agent-generator", - "subagent:command-creator", - "subagent:domain-analyzer", - "subagent:context-organizer", - "subagent:workflow-designer" - ] - }, - "copywriter": { - "id": "copywriter", - "name": "Copywriter", - "category": "content", - "type": "agent", - "version": "1.0.0", - "author": "opencode", - "tags": ["content", "marketing", "writing"], - "dependencies": [ - "context:standards-docs" - ] - }, - "technical-writer": { - "id": "technical-writer", - "name": "Technical Writer", - "category": "content", - "type": "agent", - "version": "1.0.0", - "author": "opencode", - "tags": ["documentation", "technical", "writing"], - "dependencies": [ - "context:standards-docs" - ] - }, - "data-analyst": { - "id": "data-analyst", - "name": "Data Analyst", - "category": "data", - "type": "agent", - "version": "1.0.0", - "author": "opencode", - "tags": ["data", "analysis", "visualization"], - "dependencies": [] - }, - "eval-runner": { - "id": "eval-runner", - "name": "Eval Runner", - "category": "testing", - "type": "agent", - "version": "1.0.0", - "author": "opencode", - "tags": ["testing", "evaluation", "quality"], - "dependencies": [ - "context:standards-tests" - ] - }, - "task-manager": { - "id": "task-manager", - "name": "TaskManager", - "category": "subagents/core", - "type": "subagent", - "version": "2.0.0", - "author": "opencode", - "tags": ["task-breakdown", "planning", "coordination"], - "dependencies": [ - "context:task-delegation-basics" - ] - }, - "batch-executor": { - "id": "batch-executor", - "name": "BatchExecutor", - "category": "subagents/core", - "type": "subagent", - "version": "1.0.0", - "author": "opencode", - "tags": ["parallel-execution", "batch-management", "coordination"], - "dependencies": [ - "subagent:coder-agent", - "subagent:task-manager" - ] - }, - "documentation": { - "id": "documentation", - "name": "DocWriter", - "category": "subagents/core", - "type": "subagent", - "version": "1.0.0", - "author": "opencode", - "tags": ["documentation", "writing"], - "dependencies": [ - "context:standards-docs" - ] - }, - "contextscout": { - "id": "contextscout", - "name": "ContextScout", - "category": "subagents/core", - "type": "subagent", - "version": "1.0.0", - "author": "opencode", - "tags": ["context", "discovery", "search"], - "dependencies": [] - }, - "externalscout": { - "id": "externalscout", - "name": "ExternalScout", - "category": "subagents/core", - "type": "subagent", - "version": "1.0.0", - "author": "opencode", - "tags": ["external", "documentation", "search"], - "dependencies": [] - }, - "context-manager": { - "id": "context-manager", - "name": "ContextManager", - "category": "subagents/core", - "type": "subagent", - "version": "1.0.0", - "author": "opencode", - "tags": ["context", "management", "organization"], - "dependencies": [] - }, - "context-retriever": { - "id": "context-retriever", - "name": "Context Retriever", - "category": "subagents/core", - "type": "subagent", - "version": "1.0.0", - "author": "opencode", - "tags": ["context", "retrieval", "search"], - "dependencies": [] - }, - "coder-agent": { - "id": "coder-agent", - "name": "CoderAgent", - "category": "subagents/code", - "type": "subagent", - "version": "1.0.0", - "author": "opencode", - "tags": ["coding", "implementation"], - "dependencies": [ - "context:standards-code" - ] - }, - "tester": { - "id": "tester", - "name": "TestEngineer", - "category": "subagents/code", - "type": "subagent", - "version": "1.0.0", - "author": "opencode", - "tags": ["testing", "tdd", "quality"], - "dependencies": [ - "context:standards-tests" - ] - }, - "reviewer": { - "id": "reviewer", - "name": "CodeReviewer", - "category": "subagents/code", - "type": "subagent", - "version": "1.0.0", - "author": "opencode", - "tags": ["review", "security", "quality"], - "dependencies": [ - "context:standards-code", - "context:review-ref" - ] - }, - "build-agent": { - "id": "build-agent", - "name": "BuildAgent", - "category": "subagents/code", - "type": "subagent", - "version": "1.0.0", - "author": "opencode", - "tags": ["build", "validation", "type-checking"], - "dependencies": [] - }, - "frontend-specialist": { - "id": "frontend-specialist", - "name": "OpenFrontendSpecialist", - "category": "subagents/development", - "type": "subagent", - "version": "1.0.0", - "author": "opencode", - "tags": ["frontend", "ui", "design"], - "dependencies": [ - "context:standards-code" - ] - }, - "devops-specialist": { - "id": "devops-specialist", - "name": "OpenDevopsSpecialist", - "category": "subagents/development", - "type": "subagent", - "version": "1.0.0", - "author": "opencode", - "tags": ["devops", "ci-cd", "infrastructure"], - "dependencies": [] - }, - "agent-generator": { - "id": "agent-generator", - "name": "AgentGenerator", - "category": "subagents/system-builder", - "type": "subagent", - "version": "1.0.0", - "author": "opencode", - "tags": ["generation", "agents", "scaffolding"], - "dependencies": [] - }, - "command-creator": { - "id": "command-creator", - "name": "CommandCreator", - "category": "subagents/system-builder", - "type": "subagent", - "version": "1.0.0", - "author": "opencode", - "tags": ["commands", "generation", "scaffolding"], - "dependencies": [] - }, - "domain-analyzer": { - "id": "domain-analyzer", - "name": "DomainAnalyzer", - "category": "subagents/system-builder", - "type": "subagent", - "version": "1.0.0", - "author": "opencode", - "tags": ["analysis", "domain", "architecture"], - "dependencies": [] - }, - "context-organizer": { - "id": "context-organizer", - "name": "ContextOrganizer", - "category": "subagents/system-builder", - "type": "subagent", - "version": "1.0.0", - "author": "opencode", - "tags": ["context", "organization", "structure"], - "dependencies": [] - }, - "workflow-designer": { - "id": "workflow-designer", - "name": "WorkflowDesigner", - "category": "subagents/system-builder", - "type": "subagent", - "version": "1.0.0", - "author": "opencode", - "tags": ["workflow", "design", "architecture"], - "dependencies": [] - }, - "image-specialist": { - "id": "image-specialist", - "name": "Image Specialist", - "category": "subagents/utils", - "type": "subagent", - "version": "1.0.0", - "author": "opencode", - "tags": ["images", "editing", "generation"], - "dependencies": [] - }, - "simple-responder": { - "id": "simple-responder", - "name": "Simple Responder", - "category": "subagents/test", - "type": "subagent", - "version": "1.0.0", - "author": "opencode", - "tags": ["testing", "evaluation"], - "dependencies": [] - } - }, - "defaults": { - "agent": { - "version": "1.0.0", - "author": "opencode", - "type": "agent", - "tags": [] - }, - "subagent": { - "version": "1.0.0", - "author": "opencode", - "type": "subagent", - "tags": [] - } - } + "$schema": "https://opencode.ai/schemas/agent-metadata.json", + "schema_version": "1.0.0", + "description": "Centralized metadata for OpenAgents Control agents. This file stores metadata that is not part of the OpenCode agent schema but is needed for registry management, installation, and documentation.", + "agents": { + "openagent": { + "id": "openagent", + "name": "OpenAgent", + "category": "core", + "type": "agent", + "version": "1.0.0", + "author": "opencode", + "tags": ["universal", "coordination", "primary"], + "dependencies": [ + "subagent:task-manager", + "subagent:batch-executor", + "subagent:documentation", + "subagent:contextscout", + "subagent:externalscout", + "context:standards-code", + "context:standards-docs", + "context:standards-tests", + "context:review-ref", + "context:delegation-ref", + "context:external-libraries-workflow" + ] + }, + "opencoder": { + "id": "opencoder", + "name": "OpenCoder", + "category": "core", + "type": "agent", + "version": "1.0.0", + "author": "opencode", + "tags": ["development", "coding", "implementation"], + "dependencies": [ + "subagent:documentation", + "subagent:task-manager", + "subagent:batch-executor", + "subagent:coder-agent", + "subagent:tester", + "subagent:reviewer", + "subagent:build-agent", + "subagent:contextscout", + "subagent:externalscout", + "context:standards-code", + "context:task-delegation-basics", + "context:component-planning", + "context:external-libraries-workflow" + ] + }, + "repo-manager": { + "id": "repo-manager", + "name": "Repo Manager", + "category": "meta", + "type": "agent", + "version": "1.0.0", + "author": "opencode", + "tags": ["repository", "management", "orchestration"], + "dependencies": [ + "subagent:task-manager", + "subagent:contextscout", + "subagent:documentation", + "subagent:coder-agent", + "subagent:tester", + "subagent:reviewer", + "subagent:build-agent" + ] + }, + "system-builder": { + "id": "system-builder", + "name": "System Builder", + "category": "meta", + "type": "agent", + "version": "1.0.0", + "author": "opencode", + "tags": ["system-generation", "architecture", "scaffolding"], + "dependencies": [ + "subagent:agent-generator", + "subagent:command-creator", + "subagent:domain-analyzer", + "subagent:context-organizer", + "subagent:workflow-designer" + ] + }, + "copywriter": { + "id": "copywriter", + "name": "Copywriter", + "category": "content", + "type": "agent", + "version": "1.0.0", + "author": "opencode", + "tags": ["content", "marketing", "writing"], + "dependencies": ["context:standards-docs"] + }, + "technical-writer": { + "id": "technical-writer", + "name": "Technical Writer", + "category": "content", + "type": "agent", + "version": "1.0.0", + "author": "opencode", + "tags": ["documentation", "technical", "writing"], + "dependencies": ["context:standards-docs"] + }, + "data-analyst": { + "id": "data-analyst", + "name": "Data Analyst", + "category": "data", + "type": "agent", + "version": "1.0.0", + "author": "opencode", + "tags": ["data", "analysis", "visualization"], + "dependencies": [] + }, + "eval-runner": { + "id": "eval-runner", + "name": "Eval Runner", + "category": "testing", + "type": "agent", + "version": "1.0.0", + "author": "opencode", + "tags": ["testing", "evaluation", "quality"], + "dependencies": ["context:standards-tests"] + }, + "task-manager": { + "id": "task-manager", + "name": "TaskManager", + "category": "subagents/core", + "type": "subagent", + "version": "2.0.0", + "author": "opencode", + "tags": ["task-breakdown", "planning", "coordination"], + "dependencies": ["context:task-delegation-basics"] + }, + "batch-executor": { + "id": "batch-executor", + "name": "BatchExecutor", + "category": "subagents/core", + "type": "subagent", + "version": "1.0.0", + "author": "opencode", + "tags": ["parallel-execution", "batch-management", "coordination"], + "dependencies": ["subagent:coder-agent", "subagent:task-manager"] + }, + "documentation": { + "id": "documentation", + "name": "DocWriter", + "category": "subagents/core", + "type": "subagent", + "version": "1.0.0", + "author": "opencode", + "tags": ["documentation", "writing"], + "dependencies": ["context:standards-docs"] + }, + "contextscout": { + "id": "contextscout", + "name": "ContextScout", + "category": "subagents/core", + "type": "subagent", + "version": "1.0.0", + "author": "opencode", + "tags": ["context", "discovery", "search"], + "dependencies": [] + }, + "externalscout": { + "id": "externalscout", + "name": "ExternalScout", + "category": "subagents/core", + "type": "subagent", + "version": "1.0.0", + "author": "opencode", + "tags": ["external", "documentation", "search"], + "dependencies": [] + }, + "context-manager": { + "id": "context-manager", + "name": "ContextManager", + "category": "subagents/core", + "type": "subagent", + "version": "1.0.0", + "author": "opencode", + "tags": ["context", "management", "organization"], + "dependencies": [] + }, + "context-retriever": { + "id": "context-retriever", + "name": "Context Retriever", + "category": "subagents/core", + "type": "subagent", + "version": "1.0.0", + "author": "opencode", + "tags": ["context", "retrieval", "search"], + "dependencies": [] + }, + "coder-agent": { + "id": "coder-agent", + "name": "CoderAgent", + "category": "subagents/code", + "type": "subagent", + "version": "1.0.0", + "author": "opencode", + "tags": ["coding", "implementation"], + "dependencies": ["context:standards-code"] + }, + "tester": { + "id": "tester", + "name": "TestEngineer", + "category": "subagents/code", + "type": "subagent", + "version": "1.0.0", + "author": "opencode", + "tags": ["testing", "tdd", "quality"], + "dependencies": ["context:standards-tests"] + }, + "reviewer": { + "id": "reviewer", + "name": "CodeReviewer", + "category": "subagents/code", + "type": "subagent", + "version": "1.0.0", + "author": "opencode", + "tags": ["review", "security", "quality"], + "dependencies": ["context:standards-code", "context:review-ref"] + }, + "build-agent": { + "id": "build-agent", + "name": "BuildAgent", + "category": "subagents/code", + "type": "subagent", + "version": "1.0.0", + "author": "opencode", + "tags": ["build", "validation", "type-checking"], + "dependencies": [] + }, + "frontend-specialist": { + "id": "frontend-specialist", + "name": "OpenFrontendSpecialist", + "category": "subagents/development", + "type": "subagent", + "version": "1.0.0", + "author": "opencode", + "tags": ["frontend", "ui", "design"], + "dependencies": ["context:standards-code"] + }, + "devops-specialist": { + "id": "devops-specialist", + "name": "OpenDevopsSpecialist", + "category": "subagents/development", + "type": "subagent", + "version": "1.0.0", + "author": "opencode", + "tags": ["devops", "ci-cd", "infrastructure"], + "dependencies": [] + }, + "agent-generator": { + "id": "agent-generator", + "name": "AgentGenerator", + "category": "subagents/system-builder", + "type": "subagent", + "version": "1.0.0", + "author": "opencode", + "tags": ["generation", "agents", "scaffolding"], + "dependencies": [] + }, + "command-creator": { + "id": "command-creator", + "name": "CommandCreator", + "category": "subagents/system-builder", + "type": "subagent", + "version": "1.0.0", + "author": "opencode", + "tags": ["commands", "generation", "scaffolding"], + "dependencies": [] + }, + "domain-analyzer": { + "id": "domain-analyzer", + "name": "DomainAnalyzer", + "category": "subagents/system-builder", + "type": "subagent", + "version": "1.0.0", + "author": "opencode", + "tags": ["analysis", "domain", "architecture"], + "dependencies": [] + }, + "context-organizer": { + "id": "context-organizer", + "name": "ContextOrganizer", + "category": "subagents/system-builder", + "type": "subagent", + "version": "1.0.0", + "author": "opencode", + "tags": ["context", "organization", "structure"], + "dependencies": [] + }, + "workflow-designer": { + "id": "workflow-designer", + "name": "WorkflowDesigner", + "category": "subagents/system-builder", + "type": "subagent", + "version": "1.0.0", + "author": "opencode", + "tags": ["workflow", "design", "architecture"], + "dependencies": [] + }, + "image-specialist": { + "id": "image-specialist", + "name": "Image Specialist", + "category": "subagents/utils", + "type": "subagent", + "version": "1.0.0", + "author": "opencode", + "tags": ["images", "editing", "generation"], + "dependencies": [] + }, + "simple-responder": { + "id": "simple-responder", + "name": "Simple Responder", + "category": "subagents/test", + "type": "subagent", + "version": "1.0.0", + "author": "opencode", + "tags": ["testing", "evaluation"], + "dependencies": [] + } + }, + "defaults": { + "agent": { + "version": "1.0.0", + "author": "opencode", + "type": "agent", + "tags": [] + }, + "subagent": { + "version": "1.0.0", + "author": "opencode", + "type": "subagent", + "tags": [] + } + } } diff --git a/.opencode/context/core/config/paths.json b/.opencode/context/core/config/paths.json index 9f18491..56c27ea 100644 --- a/.opencode/context/core/config/paths.json +++ b/.opencode/context/core/config/paths.json @@ -1,7 +1,7 @@ { - "description": "Additional context file paths - agents load this via @ reference for dynamic pathing", - "paths": { - "local": ".opencode/context", - "global": "~/.config/opencode/context" - } + "description": "Additional context file paths - agents load this via @ reference for dynamic pathing", + "paths": { + "local": ".opencode/context", + "global": "~/.config/opencode/context" + } } diff --git a/.opencode/skills/task-management/scripts/task-cli.ts b/.opencode/skills/task-management/scripts/task-cli.ts index 957e1bd..1ef979c 100644 --- a/.opencode/skills/task-management/scripts/task-cli.ts +++ b/.opencode/skills/task-management/scripts/task-cli.ts @@ -19,517 +19,576 @@ * .tmp/tasks/completed/{feature-slug}/ */ -const fs = require('fs'); -const path = require('path'); +const fs = require("fs"); +const path = require("path"); // Find project root (look for .git or package.json) function findProjectRoot(): string { - let dir = process.cwd(); - while (dir !== path.dirname(dir)) { - if (fs.existsSync(path.join(dir, '.git')) || fs.existsSync(path.join(dir, 'package.json'))) { - return dir; - } - dir = path.dirname(dir); - } - return process.cwd(); + let dir = process.cwd(); + while (dir !== path.dirname(dir)) { + if ( + fs.existsSync(path.join(dir, ".git")) || + fs.existsSync(path.join(dir, "package.json")) + ) { + return dir; + } + dir = path.dirname(dir); + } + return process.cwd(); } const PROJECT_ROOT = findProjectRoot(); -const TASKS_DIR = path.join(PROJECT_ROOT, '.tmp', 'tasks'); -const COMPLETED_DIR = path.join(TASKS_DIR, 'completed'); +const TASKS_DIR = path.join(PROJECT_ROOT, ".tmp", "tasks"); +const COMPLETED_DIR = path.join(TASKS_DIR, "completed"); interface Task { - id: string; - name: string; - status: 'active' | 'completed' | 'blocked' | 'archived'; - objective: string; - context_files: string[]; - reference_files?: string[]; - exit_criteria: string[]; - subtask_count: number; - completed_count: number; - created_at: string; - completed_at: string | null; + id: string; + name: string; + status: "active" | "completed" | "blocked" | "archived"; + objective: string; + context_files: string[]; + reference_files?: string[]; + exit_criteria: string[]; + subtask_count: number; + completed_count: number; + created_at: string; + completed_at: string | null; } interface Subtask { - id: string; - seq: string; - title: string; - status: 'pending' | 'in_progress' | 'completed' | 'blocked'; - depends_on: string[]; - parallel: boolean; - context_files: string[]; - reference_files?: string[]; - acceptance_criteria: string[]; - deliverables: string[]; - agent_id: string | null; - suggested_agent?: string; - started_at: string | null; - completed_at: string | null; - completion_summary: string | null; + id: string; + seq: string; + title: string; + status: "pending" | "in_progress" | "completed" | "blocked"; + depends_on: string[]; + parallel: boolean; + context_files: string[]; + reference_files?: string[]; + acceptance_criteria: string[]; + deliverables: string[]; + agent_id: string | null; + suggested_agent?: string; + started_at: string | null; + completed_at: string | null; + completion_summary: string | null; } // Helpers function getFeatureDirs(): string[] { - if (!fs.existsSync(TASKS_DIR)) return []; - return fs.readdirSync(TASKS_DIR).filter((f: string) => { - const fullPath = path.join(TASKS_DIR, f); - return fs.statSync(fullPath).isDirectory() && f !== 'completed'; - }); + if (!fs.existsSync(TASKS_DIR)) return []; + return fs.readdirSync(TASKS_DIR).filter((f: string) => { + const fullPath = path.join(TASKS_DIR, f); + return fs.statSync(fullPath).isDirectory() && f !== "completed"; + }); } function loadTask(feature: string): Task | null { - const taskPath = path.join(TASKS_DIR, feature, 'task.json'); - if (!fs.existsSync(taskPath)) return null; - return JSON.parse(fs.readFileSync(taskPath, 'utf-8')); + const taskPath = path.join(TASKS_DIR, feature, "task.json"); + if (!fs.existsSync(taskPath)) return null; + return JSON.parse(fs.readFileSync(taskPath, "utf-8")); } function loadSubtasks(feature: string): Subtask[] { - const featureDir = path.join(TASKS_DIR, feature); - if (!fs.existsSync(featureDir)) return []; + const featureDir = path.join(TASKS_DIR, feature); + if (!fs.existsSync(featureDir)) return []; - const files = fs.readdirSync(featureDir) - .filter((f: string) => f.match(/^subtask_\d{2}\.json$/)) - .sort(); + const files = fs + .readdirSync(featureDir) + .filter((f: string) => f.match(/^subtask_\d{2}\.json$/)) + .sort(); - return files.map((f: string) => JSON.parse(fs.readFileSync(path.join(featureDir, f), 'utf-8'))); + return files.map((f: string) => + JSON.parse(fs.readFileSync(path.join(featureDir, f), "utf-8")), + ); } function saveSubtask(feature: string, subtask: Subtask): void { - const subtaskPath = path.join(TASKS_DIR, feature, `subtask_${subtask.seq}.json`); - fs.writeFileSync(subtaskPath, JSON.stringify(subtask, null, 2)); + const subtaskPath = path.join( + TASKS_DIR, + feature, + `subtask_${subtask.seq}.json`, + ); + fs.writeFileSync(subtaskPath, JSON.stringify(subtask, null, 2)); } function saveTask(feature: string, task: Task): void { - const taskPath = path.join(TASKS_DIR, feature, 'task.json'); - fs.writeFileSync(taskPath, JSON.stringify(task, null, 2)); + const taskPath = path.join(TASKS_DIR, feature, "task.json"); + fs.writeFileSync(taskPath, JSON.stringify(task, null, 2)); } // Commands function cmdStatus(feature?: string): void { - const features = feature ? [feature] : getFeatureDirs(); + const features = feature ? [feature] : getFeatureDirs(); - if (features.length === 0) { - console.log('No active features found.'); - return; - } + if (features.length === 0) { + console.log("No active features found."); + return; + } - for (const f of features) { - const task = loadTask(f); - const subtasks = loadSubtasks(f); + for (const f of features) { + const task = loadTask(f); + const subtasks = loadSubtasks(f); - if (!task) { - console.log(`\n[${f}] - No task.json found`); - continue; - } + if (!task) { + console.log(`\n[${f}] - No task.json found`); + continue; + } - const counts = { - pending: subtasks.filter(s => s.status === 'pending').length, - in_progress: subtasks.filter(s => s.status === 'in_progress').length, - completed: subtasks.filter(s => s.status === 'completed').length, - blocked: subtasks.filter(s => s.status === 'blocked').length, - }; + const counts = { + pending: subtasks.filter((s) => s.status === "pending").length, + in_progress: subtasks.filter((s) => s.status === "in_progress").length, + completed: subtasks.filter((s) => s.status === "completed").length, + blocked: subtasks.filter((s) => s.status === "blocked").length, + }; - const progress = subtasks.length > 0 - ? Math.round((counts.completed / subtasks.length) * 100) - : 0; + const progress = + subtasks.length > 0 + ? Math.round((counts.completed / subtasks.length) * 100) + : 0; - console.log(`\n[${f}] ${task.name}`); - console.log(` Status: ${task.status} | Progress: ${progress}% (${counts.completed}/${subtasks.length})`); - console.log(` Pending: ${counts.pending} | In Progress: ${counts.in_progress} | Completed: ${counts.completed} | Blocked: ${counts.blocked}`); - } + console.log(`\n[${f}] ${task.name}`); + console.log( + ` Status: ${task.status} | Progress: ${progress}% (${counts.completed}/${subtasks.length})`, + ); + console.log( + ` Pending: ${counts.pending} | In Progress: ${counts.in_progress} | Completed: ${counts.completed} | Blocked: ${counts.blocked}`, + ); + } } function cmdNext(feature?: string): void { - const features = feature ? [feature] : getFeatureDirs(); + const features = feature ? [feature] : getFeatureDirs(); - console.log('\n=== Ready Tasks (deps satisfied) ===\n'); + console.log("\n=== Ready Tasks (deps satisfied) ===\n"); - for (const f of features) { - const subtasks = loadSubtasks(f); - const completedSeqs = new Set(subtasks.filter(s => s.status === 'completed').map(s => s.seq)); + for (const f of features) { + const subtasks = loadSubtasks(f); + const completedSeqs = new Set( + subtasks.filter((s) => s.status === "completed").map((s) => s.seq), + ); - const ready = subtasks.filter(s => { - if (s.status !== 'pending') return false; - return s.depends_on.every(dep => completedSeqs.has(dep)); - }); + const ready = subtasks.filter((s) => { + if (s.status !== "pending") return false; + return s.depends_on.every((dep) => completedSeqs.has(dep)); + }); - if (ready.length > 0) { - console.log(`[${f}]`); - for (const s of ready) { - const parallel = s.parallel ? '[parallel]' : '[sequential]'; - console.log(` ${s.seq} - ${s.title} ${parallel}`); - } - console.log(); - } - } + if (ready.length > 0) { + console.log(`[${f}]`); + for (const s of ready) { + const parallel = s.parallel ? "[parallel]" : "[sequential]"; + console.log(` ${s.seq} - ${s.title} ${parallel}`); + } + console.log(); + } + } } function cmdParallel(feature?: string): void { - const features = feature ? [feature] : getFeatureDirs(); + const features = feature ? [feature] : getFeatureDirs(); - console.log('\n=== Parallelizable Tasks Ready Now ===\n'); + console.log("\n=== Parallelizable Tasks Ready Now ===\n"); - for (const f of features) { - const subtasks = loadSubtasks(f); - const completedSeqs = new Set(subtasks.filter(s => s.status === 'completed').map(s => s.seq)); + for (const f of features) { + const subtasks = loadSubtasks(f); + const completedSeqs = new Set( + subtasks.filter((s) => s.status === "completed").map((s) => s.seq), + ); - const parallel = subtasks.filter(s => { - if (s.status !== 'pending') return false; - if (!s.parallel) return false; - return s.depends_on.every(dep => completedSeqs.has(dep)); - }); + const parallel = subtasks.filter((s) => { + if (s.status !== "pending") return false; + if (!s.parallel) return false; + return s.depends_on.every((dep) => completedSeqs.has(dep)); + }); - if (parallel.length > 0) { - console.log(`[${f}] - ${parallel.length} parallel tasks:`); - for (const s of parallel) { - console.log(` ${s.seq} - ${s.title}`); - } - console.log(); - } - } + if (parallel.length > 0) { + console.log(`[${f}] - ${parallel.length} parallel tasks:`); + for (const s of parallel) { + console.log(` ${s.seq} - ${s.title}`); + } + console.log(); + } + } } function cmdDeps(feature: string, seq: string): void { - const subtasks = loadSubtasks(feature); - const target = subtasks.find(s => s.seq === seq); + const subtasks = loadSubtasks(feature); + const target = subtasks.find((s) => s.seq === seq); - if (!target) { - console.log(`Task ${seq} not found in ${feature}`); - return; - } + if (!target) { + console.log(`Task ${seq} not found in ${feature}`); + return; + } - console.log(`\n=== Dependency Tree: ${feature}/${seq} ===\n`); - console.log(`${seq} - ${target.title} [${target.status}]`); + console.log(`\n=== Dependency Tree: ${feature}/${seq} ===\n`); + console.log(`${seq} - ${target.title} [${target.status}]`); - if (target.depends_on.length === 0) { - console.log(' └── (no dependencies)'); - return; - } + if (target.depends_on.length === 0) { + console.log(" └── (no dependencies)"); + return; + } - const printDeps = (seqs: string[], indent: string = ' '): void => { - for (let i = 0; i < seqs.length; i++) { - const depSeq = seqs[i]; - const dep = subtasks.find(s => s.seq === depSeq); - const isLast = i === seqs.length - 1; - const branch = isLast ? '└──' : '├──'; + const printDeps = (seqs: string[], indent: string = " "): void => { + for (let i = 0; i < seqs.length; i++) { + const depSeq = seqs[i]; + const dep = subtasks.find((s) => s.seq === depSeq); + const isLast = i === seqs.length - 1; + const branch = isLast ? "└──" : "├──"; - if (dep) { - const statusIcon = dep.status === 'completed' ? '✓' : dep.status === 'in_progress' ? '~' : '○'; - console.log(`${indent}${branch} ${statusIcon} ${depSeq} - ${dep.title} [${dep.status}]`); - if (dep.depends_on.length > 0) { - const newIndent = indent + (isLast ? ' ' : '│ '); - printDeps(dep.depends_on, newIndent); - } - } else { - console.log(`${indent}${branch} ? ${depSeq} - NOT FOUND`); - } - } - }; + if (dep) { + const statusIcon = + dep.status === "completed" + ? "✓" + : dep.status === "in_progress" + ? "~" + : "○"; + console.log( + `${indent}${branch} ${statusIcon} ${depSeq} - ${dep.title} [${dep.status}]`, + ); + if (dep.depends_on.length > 0) { + const newIndent = indent + (isLast ? " " : "│ "); + printDeps(dep.depends_on, newIndent); + } + } else { + console.log(`${indent}${branch} ? ${depSeq} - NOT FOUND`); + } + } + }; - printDeps(target.depends_on); + printDeps(target.depends_on); } function cmdBlocked(feature?: string): void { - const features = feature ? [feature] : getFeatureDirs(); + const features = feature ? [feature] : getFeatureDirs(); - console.log('\n=== Blocked Tasks ===\n'); + console.log("\n=== Blocked Tasks ===\n"); - for (const f of features) { - const subtasks = loadSubtasks(f); - const completedSeqs = new Set(subtasks.filter(s => s.status === 'completed').map(s => s.seq)); + for (const f of features) { + const subtasks = loadSubtasks(f); + const completedSeqs = new Set( + subtasks.filter((s) => s.status === "completed").map((s) => s.seq), + ); - const blocked = subtasks.filter(s => { - if (s.status === 'blocked') return true; - if (s.status !== 'pending') return false; - return !s.depends_on.every(dep => completedSeqs.has(dep)); - }); + const blocked = subtasks.filter((s) => { + if (s.status === "blocked") return true; + if (s.status !== "pending") return false; + return !s.depends_on.every((dep) => completedSeqs.has(dep)); + }); - if (blocked.length > 0) { - console.log(`[${f}]`); - for (const s of blocked) { - const waitingFor = s.depends_on.filter(dep => !completedSeqs.has(dep)); - const reason = s.status === 'blocked' - ? 'explicitly blocked' - : `waiting: ${waitingFor.join(', ')}`; - console.log(` ${s.seq} - ${s.title} (${reason})`); - } - console.log(); - } - } + if (blocked.length > 0) { + console.log(`[${f}]`); + for (const s of blocked) { + const waitingFor = s.depends_on.filter( + (dep) => !completedSeqs.has(dep), + ); + const reason = + s.status === "blocked" + ? "explicitly blocked" + : `waiting: ${waitingFor.join(", ")}`; + console.log(` ${s.seq} - ${s.title} (${reason})`); + } + console.log(); + } + } } function cmdComplete(feature: string, seq: string, summary: string): void { - if (summary.length > 200) { - console.log('Error: Summary must be max 200 characters'); - process.exit(1); - } + if (summary.length > 200) { + console.log("Error: Summary must be max 200 characters"); + process.exit(1); + } - const subtasks = loadSubtasks(feature); - const subtask = subtasks.find(s => s.seq === seq); + const subtasks = loadSubtasks(feature); + const subtask = subtasks.find((s) => s.seq === seq); - if (!subtask) { - console.log(`Task ${seq} not found in ${feature}`); - process.exit(1); - } + if (!subtask) { + console.log(`Task ${seq} not found in ${feature}`); + process.exit(1); + } - subtask.status = 'completed'; - subtask.completed_at = new Date().toISOString(); - subtask.completion_summary = summary; + subtask.status = "completed"; + subtask.completed_at = new Date().toISOString(); + subtask.completion_summary = summary; - saveSubtask(feature, subtask); + saveSubtask(feature, subtask); - // Update task.json counts - const task = loadTask(feature); - if (task) { - const newSubtasks = loadSubtasks(feature); - task.completed_count = newSubtasks.filter(s => s.status === 'completed').length; - saveTask(feature, task); - } + // Update task.json counts + const task = loadTask(feature); + if (task) { + const newSubtasks = loadSubtasks(feature); + task.completed_count = newSubtasks.filter( + (s) => s.status === "completed", + ).length; + saveTask(feature, task); + } - console.log(`\n✓ Marked ${feature}/${seq} as completed`); - console.log(` Summary: ${summary}`); + console.log(`\n✓ Marked ${feature}/${seq} as completed`); + console.log(` Summary: ${summary}`); - if (task) { - console.log(` Progress: ${task.completed_count}/${task.subtask_count}`); - } + if (task) { + console.log(` Progress: ${task.completed_count}/${task.subtask_count}`); + } } function cmdValidate(feature?: string): void { - const features = feature ? [feature] : getFeatureDirs(); - let hasErrors = false; + const features = feature ? [feature] : getFeatureDirs(); + let hasErrors = false; - const validTaskStatuses = new Set(['active', 'completed', 'blocked', 'archived']); - const validSubtaskStatuses = new Set(['pending', 'in_progress', 'completed', 'blocked']); + const validTaskStatuses = new Set([ + "active", + "completed", + "blocked", + "archived", + ]); + const validSubtaskStatuses = new Set([ + "pending", + "in_progress", + "completed", + "blocked", + ]); - const requiredTaskFields = [ - 'id', - 'name', - 'status', - 'objective', - 'context_files', - 'exit_criteria', - 'subtask_count', - 'completed_count', - 'created_at', - 'completed_at', - ]; + const requiredTaskFields = [ + "id", + "name", + "status", + "objective", + "context_files", + "exit_criteria", + "subtask_count", + "completed_count", + "created_at", + "completed_at", + ]; - const requiredSubtaskFields = [ - 'id', - 'seq', - 'title', - 'status', - 'depends_on', - 'parallel', - 'context_files', - 'acceptance_criteria', - 'deliverables', - 'agent_id', - 'started_at', - 'completed_at', - 'completion_summary', - ]; + const requiredSubtaskFields = [ + "id", + "seq", + "title", + "status", + "depends_on", + "parallel", + "context_files", + "acceptance_criteria", + "deliverables", + "agent_id", + "started_at", + "completed_at", + "completion_summary", + ]; - const hasField = (obj: any, field: string): boolean => Object.prototype.hasOwnProperty.call(obj, field); - const isStringArray = (value: any): boolean => Array.isArray(value) && value.every(v => typeof v === 'string'); + const hasField = (obj: any, field: string): boolean => + Object.prototype.hasOwnProperty.call(obj, field); + const isStringArray = (value: any): boolean => + Array.isArray(value) && value.every((v) => typeof v === "string"); - console.log('\n=== Validation Results ===\n'); + console.log("\n=== Validation Results ===\n"); - for (const f of features) { - const errors: string[] = []; + for (const f of features) { + const errors: string[] = []; - // Check task.json exists - const task = loadTask(f); - if (!task) { - errors.push('Missing task.json'); - } + // Check task.json exists + const task = loadTask(f); + if (!task) { + errors.push("Missing task.json"); + } - // Load and validate subtasks - const subtasks = loadSubtasks(f); - const seqCounts = new Map(); - for (const s of subtasks) { - const seq = typeof s.seq === 'string' ? s.seq : ''; - seqCounts.set(seq, (seqCounts.get(seq) || 0) + 1); - } - const seqs = new Set(subtasks.map(s => s.seq)); + // Load and validate subtasks + const subtasks = loadSubtasks(f); + const seqCounts = new Map(); + for (const s of subtasks) { + const seq = typeof s.seq === "string" ? s.seq : ""; + seqCounts.set(seq, (seqCounts.get(seq) || 0) + 1); + } + const seqs = new Set(subtasks.map((s) => s.seq)); - if (task) { - // Required fields in task.json - for (const field of requiredTaskFields) { - if (!hasField(task, field)) { - errors.push(`task.json: missing required field '${field}'`); - } - } + if (task) { + // Required fields in task.json + for (const field of requiredTaskFields) { + if (!hasField(task, field)) { + errors.push(`task.json: missing required field '${field}'`); + } + } - // Task ID should match feature slug - if (task.id !== f) { - errors.push(`task.json id ('${task.id}') should match feature slug ('${f}')`); - } + // Task ID should match feature slug + if (task.id !== f) { + errors.push( + `task.json id ('${task.id}') should match feature slug ('${f}')`, + ); + } - // Task status should be valid - if (!validTaskStatuses.has(task.status)) { - errors.push(`task.json: invalid status '${task.status}'`); - } + // Task status should be valid + if (!validTaskStatuses.has(task.status)) { + errors.push(`task.json: invalid status '${task.status}'`); + } - // Basic type checks for key task fields - if (!isStringArray(task.context_files)) { - errors.push('task.json: context_files must be string[]'); - } - if (hasField(task, 'reference_files') && task.reference_files !== undefined && !isStringArray(task.reference_files)) { - errors.push('task.json: reference_files must be string[] when present'); - } - if (!isStringArray(task.exit_criteria)) { - errors.push('task.json: exit_criteria must be string[]'); - } - if (typeof task.subtask_count !== 'number') { - errors.push('task.json: subtask_count must be number'); - } - if (typeof task.completed_count !== 'number') { - errors.push('task.json: completed_count must be number'); - } - } + // Basic type checks for key task fields + if (!isStringArray(task.context_files)) { + errors.push("task.json: context_files must be string[]"); + } + if ( + hasField(task, "reference_files") && + task.reference_files !== undefined && + !isStringArray(task.reference_files) + ) { + errors.push("task.json: reference_files must be string[] when present"); + } + if (!isStringArray(task.exit_criteria)) { + errors.push("task.json: exit_criteria must be string[]"); + } + if (typeof task.subtask_count !== "number") { + errors.push("task.json: subtask_count must be number"); + } + if (typeof task.completed_count !== "number") { + errors.push("task.json: completed_count must be number"); + } + } - for (const s of subtasks) { - // Required fields in subtask files - for (const field of requiredSubtaskFields) { - if (!hasField(s, field)) { - errors.push(`${s.seq || '??'}: missing required field '${field}'`); - } - } + for (const s of subtasks) { + // Required fields in subtask files + for (const field of requiredSubtaskFields) { + if (!hasField(s, field)) { + errors.push(`${s.seq || "??"}: missing required field '${field}'`); + } + } - // Sequence format and uniqueness - if (!/^\d{2}$/.test(s.seq)) { - errors.push(`${s.seq}: sequence must be 2 digits (e.g., 01, 02)`); - } - if ((seqCounts.get(s.seq) || 0) > 1) { - errors.push(`${s.seq}: duplicate sequence number`); - } + // Sequence format and uniqueness + if (!/^\d{2}$/.test(s.seq)) { + errors.push(`${s.seq}: sequence must be 2 digits (e.g., 01, 02)`); + } + if ((seqCounts.get(s.seq) || 0) > 1) { + errors.push(`${s.seq}: duplicate sequence number`); + } - // Check ID format - if (!s.id.startsWith(f)) { - errors.push(`${s.seq}: ID should start with feature name`); - } + // Check ID format + if (!s.id.startsWith(f)) { + errors.push(`${s.seq}: ID should start with feature name`); + } - // Status should be valid - if (!validSubtaskStatuses.has(s.status)) { - errors.push(`${s.seq}: invalid status '${s.status}'`); - } + // Status should be valid + if (!validSubtaskStatuses.has(s.status)) { + errors.push(`${s.seq}: invalid status '${s.status}'`); + } - // Type checks - if (!isStringArray(s.depends_on)) { - errors.push(`${s.seq}: depends_on must be string[]`); - } - if (typeof s.parallel !== 'boolean') { - errors.push(`${s.seq}: parallel must be boolean`); - } - if (!isStringArray(s.context_files)) { - errors.push(`${s.seq}: context_files must be string[]`); - } - if (hasField(s, 'reference_files') && s.reference_files !== undefined && !isStringArray(s.reference_files)) { - errors.push(`${s.seq}: reference_files must be string[] when present`); - } - if (!isStringArray(s.acceptance_criteria)) { - errors.push(`${s.seq}: acceptance_criteria must be string[]`); - } else if (s.acceptance_criteria.length === 0) { - errors.push(`${s.seq}: No acceptance criteria defined`); - } - if (!isStringArray(s.deliverables)) { - errors.push(`${s.seq}: deliverables must be string[]`); - } else if (s.deliverables.length === 0) { - errors.push(`${s.seq}: No deliverables defined`); - } + // Type checks + if (!isStringArray(s.depends_on)) { + errors.push(`${s.seq}: depends_on must be string[]`); + } + if (typeof s.parallel !== "boolean") { + errors.push(`${s.seq}: parallel must be boolean`); + } + if (!isStringArray(s.context_files)) { + errors.push(`${s.seq}: context_files must be string[]`); + } + if ( + hasField(s, "reference_files") && + s.reference_files !== undefined && + !isStringArray(s.reference_files) + ) { + errors.push(`${s.seq}: reference_files must be string[] when present`); + } + if (!isStringArray(s.acceptance_criteria)) { + errors.push(`${s.seq}: acceptance_criteria must be string[]`); + } else if (s.acceptance_criteria.length === 0) { + errors.push(`${s.seq}: No acceptance criteria defined`); + } + if (!isStringArray(s.deliverables)) { + errors.push(`${s.seq}: deliverables must be string[]`); + } else if (s.deliverables.length === 0) { + errors.push(`${s.seq}: No deliverables defined`); + } - // Self dependency is invalid - if (Array.isArray(s.depends_on) && s.depends_on.includes(s.seq)) { - errors.push(`${s.seq}: task cannot depend on itself`); - } + // Self dependency is invalid + if (Array.isArray(s.depends_on) && s.depends_on.includes(s.seq)) { + errors.push(`${s.seq}: task cannot depend on itself`); + } - // Check for missing dependencies - for (const dep of (Array.isArray(s.depends_on) ? s.depends_on : [])) { - if (!seqs.has(dep)) { - errors.push(`${s.seq}: depends on non-existent task ${dep}`); - } - } + // Check for missing dependencies + for (const dep of Array.isArray(s.depends_on) ? s.depends_on : []) { + if (!seqs.has(dep)) { + errors.push(`${s.seq}: depends on non-existent task ${dep}`); + } + } - // Check for circular dependencies - const visited = new Set(); - const checkCircular = (seq: string, path: string[]): boolean => { - if (path.includes(seq)) { - errors.push(`${s.seq}: circular dependency detected: ${[...path, seq].join(' -> ')}`); - return true; - } - if (visited.has(seq)) return false; - visited.add(seq); + // Check for circular dependencies + const visited = new Set(); + const checkCircular = (seq: string, path: string[]): boolean => { + if (path.includes(seq)) { + errors.push( + `${s.seq}: circular dependency detected: ${[...path, seq].join(" -> ")}`, + ); + return true; + } + if (visited.has(seq)) return false; + visited.add(seq); - const task = subtasks.find(t => t.seq === seq); - if (task) { - for (const dep of task.depends_on) { - if (checkCircular(dep, [...path, seq])) return true; - } - } - return false; - }; - checkCircular(s.seq, []); - } + const task = subtasks.find((t) => t.seq === seq); + if (task) { + for (const dep of task.depends_on) { + if (checkCircular(dep, [...path, seq])) return true; + } + } + return false; + }; + checkCircular(s.seq, []); + } - // Check counts match - if (task && task.subtask_count !== subtasks.length) { - errors.push(`task.json subtask_count (${task.subtask_count}) doesn't match actual count (${subtasks.length})`); - } + // Check counts match + if (task && task.subtask_count !== subtasks.length) { + errors.push( + `task.json subtask_count (${task.subtask_count}) doesn't match actual count (${subtasks.length})`, + ); + } - // Print results - console.log(`[${f}]`); - if (errors.length === 0) { - console.log(' ✓ All checks passed'); - } else { - for (const e of errors) { - console.log(` ✗ ERROR: ${e}`); - hasErrors = true; - } - } - console.log(); - } + // Print results + console.log(`[${f}]`); + if (errors.length === 0) { + console.log(" ✓ All checks passed"); + } else { + for (const e of errors) { + console.log(` ✗ ERROR: ${e}`); + hasErrors = true; + } + } + console.log(); + } - process.exit(hasErrors ? 1 : 0); + process.exit(hasErrors ? 1 : 0); } // Main -const [,, command, ...args] = process.argv; +const [, , command, ...args] = process.argv; switch (command) { - case 'status': - cmdStatus(args[0]); - break; - case 'next': - cmdNext(args[0]); - break; - case 'parallel': - cmdParallel(args[0]); - break; - case 'deps': - if (args.length < 2) { - console.log('Usage: deps '); - process.exit(1); - } - cmdDeps(args[0], args[1]); - break; - case 'blocked': - cmdBlocked(args[0]); - break; - case 'complete': - if (args.length < 3) { - console.log('Usage: complete "summary"'); - process.exit(1); - } - cmdComplete(args[0], args[1], args.slice(2).join(' ')); - break; - case 'validate': - cmdValidate(args[0]); - break; - default: - console.log(` + case "status": + cmdStatus(args[0]); + break; + case "next": + cmdNext(args[0]); + break; + case "parallel": + cmdParallel(args[0]); + break; + case "deps": + if (args.length < 2) { + console.log("Usage: deps "); + process.exit(1); + } + cmdDeps(args[0], args[1]); + break; + case "blocked": + cmdBlocked(args[0]); + break; + case "complete": + if (args.length < 3) { + console.log('Usage: complete "summary"'); + process.exit(1); + } + cmdComplete(args[0], args[1], args.slice(2).join(" ")); + break; + case "validate": + cmdValidate(args[0]); + break; + default: + console.log(` Task Management CLI Usage: bunx --bun ts-node task-cli.ts [feature] [args...] diff --git a/.opencode/tool/env/index.ts b/.opencode/tool/env/index.ts index 8ff2ac9..a479867 100644 --- a/.opencode/tool/env/index.ts +++ b/.opencode/tool/env/index.ts @@ -1,168 +1,188 @@ -import { readFile } from "fs/promises" -import { resolve } from "path" +import { readFile } from "fs/promises"; +import { resolve } from "path"; /** * Configuration for environment variable loading */ export interface EnvLoaderConfig { - /** Custom paths to search for .env files (relative to current working directory) */ - searchPaths?: string[] - /** Whether to log when environment variables are loaded */ - verbose?: boolean - /** Whether to override existing environment variables */ - override?: boolean + /** Custom paths to search for .env files (relative to current working directory) */ + searchPaths?: string[]; + /** Whether to log when environment variables are loaded */ + verbose?: boolean; + /** Whether to override existing environment variables */ + override?: boolean; } /** * Default search paths for .env files */ const DEFAULT_ENV_PATHS = [ - './.env', - '../.env', - '../../.env', - '../plugin/.env', - '../../../.env' -] + "./.env", + "../.env", + "../../.env", + "../plugin/.env", + "../../../.env", +]; /** * Load environment variables from .env files * Searches multiple common locations for .env files and loads them into process.env - * + * * @param config Configuration options * @returns Object containing loaded environment variables */ -export async function loadEnvVariables(config: EnvLoaderConfig = {}): Promise> { - const { - searchPaths = DEFAULT_ENV_PATHS, - verbose = false, - override = false - } = config - - const loadedVars: Record = {} - - for (const envPath of searchPaths) { - try { - const fullPath = resolve(envPath) - const content = await readFile(fullPath, 'utf8') - - if (verbose) { - console.log(`Checking .env file: ${envPath}`) - } - - // Parse .env file content - const lines = content.split('\n') - for (const line of lines) { - const trimmed = line.trim() - if (trimmed && !trimmed.startsWith('#') && trimmed.includes('=')) { - const [key, ...valueParts] = trimmed.split('=') - const value = valueParts.join('=').trim() - - // Remove quotes if present - const cleanValue = value.replace(/^["']|["']$/g, '') - - if (key && cleanValue && (override || !process.env[key])) { - process.env[key] = cleanValue - loadedVars[key] = cleanValue - - if (verbose) { - console.log(`Loaded ${key} from ${envPath}`) - } - } - } - } - } catch (error) { - // File doesn't exist or can't be read, continue to next - if (verbose) { - console.log(`Could not read ${envPath}: ${error.message}`) - } - } - } - - return loadedVars +export async function loadEnvVariables( + config: EnvLoaderConfig = {}, +): Promise> { + const { + searchPaths = DEFAULT_ENV_PATHS, + verbose = false, + override = false, + } = config; + + const loadedVars: Record = {}; + + for (const envPath of searchPaths) { + try { + const fullPath = resolve(envPath); + const content = await readFile(fullPath, "utf8"); + + if (verbose) { + console.log(`Checking .env file: ${envPath}`); + } + + // Parse .env file content + const lines = content.split("\n"); + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith("#") && trimmed.includes("=")) { + const [key, ...valueParts] = trimmed.split("="); + const value = valueParts.join("=").trim(); + + // Remove quotes if present + const cleanValue = value.replace(/^["']|["']$/g, ""); + + if (key && cleanValue && (override || !process.env[key])) { + process.env[key] = cleanValue; + loadedVars[key] = cleanValue; + + if (verbose) { + console.log(`Loaded ${key} from ${envPath}`); + } + } + } + } + } catch (error) { + // File doesn't exist or can't be read, continue to next + if (verbose) { + console.log(`Could not read ${envPath}: ${error.message}`); + } + } + } + + return loadedVars; } /** * Get a specific environment variable with automatic .env file loading - * + * * @param varName Name of the environment variable * @param config Configuration options * @returns The environment variable value or null if not found */ -export async function getEnvVariable(varName: string, config: EnvLoaderConfig = {}): Promise { - // First check if it's already in the environment - let value = process.env[varName] - - if (!value) { - // Try to load from .env files - const loadedVars = await loadEnvVariables(config) - value = loadedVars[varName] || process.env[varName] - } - - return value || null +export async function getEnvVariable( + varName: string, + config: EnvLoaderConfig = {}, +): Promise { + // First check if it's already in the environment + let value = process.env[varName]; + + if (!value) { + // Try to load from .env files + const loadedVars = await loadEnvVariables(config); + value = loadedVars[varName] || process.env[varName]; + } + + return value || null; } /** * Get a required environment variable with automatic .env file loading * Throws an error if the variable is not found - * + * * @param varName Name of the environment variable * @param config Configuration options * @returns The environment variable value * @throws Error if the variable is not found */ -export async function getRequiredEnvVariable(varName: string, config: EnvLoaderConfig = {}): Promise { - const value = await getEnvVariable(varName, config) - - if (!value) { - const searchPaths = config.searchPaths || DEFAULT_ENV_PATHS - throw new Error(`${varName} not found. Please set it in your environment or .env file. +export async function getRequiredEnvVariable( + varName: string, + config: EnvLoaderConfig = {}, +): Promise { + const value = await getEnvVariable(varName, config); + + if (!value) { + const searchPaths = config.searchPaths || DEFAULT_ENV_PATHS; + throw new Error(`${varName} not found. Please set it in your environment or .env file. To fix this: 1. Add to .env file: ${varName}=your_value_here 2. Or export it: export ${varName}=your_value_here Current working directory: ${process.cwd()} -Searched paths: ${searchPaths.join(', ')} -Environment variables available: ${Object.keys(process.env).filter(k => k.includes(varName.split('_')[0])).join(', ') || 'none matching'}`) - } - - return value +Searched paths: ${searchPaths.join(", ")} +Environment variables available: ${ + Object.keys(process.env) + .filter((k) => k.includes(varName.split("_")[0])) + .join(", ") || "none matching" + }`); + } + + return value; } /** * Load multiple required environment variables at once - * + * * @param varNames Array of environment variable names * @param config Configuration options * @returns Object with variable names as keys and values as values * @throws Error if any variable is not found */ -export async function getRequiredEnvVariables(varNames: string[], config: EnvLoaderConfig = {}): Promise> { - const result: Record = {} - - // Load all .env files first - await loadEnvVariables(config) - - // Check each required variable - for (const varName of varNames) { - const value = process.env[varName] - if (!value) { - throw new Error(`Required environment variable ${varName} not found. Please set it in your environment or .env file.`) - } - result[varName] = value - } - - return result +export async function getRequiredEnvVariables( + varNames: string[], + config: EnvLoaderConfig = {}, +): Promise> { + const result: Record = {}; + + // Load all .env files first + await loadEnvVariables(config); + + // Check each required variable + for (const varName of varNames) { + const value = process.env[varName]; + if (!value) { + throw new Error( + `Required environment variable ${varName} not found. Please set it in your environment or .env file.`, + ); + } + result[varName] = value; + } + + return result; } /** * Utility function specifically for API keys - * + * * @param apiKeyName Name of the API key environment variable * @param config Configuration options * @returns The API key value * @throws Error if the API key is not found */ -export async function getApiKey(apiKeyName: string, config: EnvLoaderConfig = {}): Promise { - return getRequiredEnvVariable(apiKeyName, config) -} \ No newline at end of file +export async function getApiKey( + apiKeyName: string, + config: EnvLoaderConfig = {}, +): Promise { + return getRequiredEnvVariable(apiKeyName, config); +}