more refactor
This commit is contained in:
parent
a66eb24009
commit
f04a950c9e
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { buildPrBody } from '../github/pr.js';
|
import { buildPrBody } from '../github/pr.js';
|
||||||
import type { GitHubIssue } from '../github/issue.js';
|
import type { GitHubIssue } from '../github/types.js';
|
||||||
|
|
||||||
describe('buildPrBody', () => {
|
describe('buildPrBody', () => {
|
||||||
it('should build body with issue and report', () => {
|
it('should build body with issue and report', () => {
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|||||||
import { mkdirSync, writeFileSync, existsSync, rmSync } from 'node:fs';
|
import { mkdirSync, writeFileSync, existsSync, rmSync } from 'node:fs';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { TaskWatcher } from '../task/watcher.js';
|
import { TaskWatcher } from '../task/watcher.js';
|
||||||
import type { TaskInfo } from '../task/runner.js';
|
import type { TaskInfo } from '../task/types.js';
|
||||||
|
|
||||||
describe('TaskWatcher', () => {
|
describe('TaskWatcher', () => {
|
||||||
const testDir = `/tmp/takt-watcher-test-${Date.now()}`;
|
const testDir = `/tmp/takt-watcher-test-${Date.now()}`;
|
||||||
|
|||||||
@ -45,11 +45,8 @@ import { DEFAULT_WORKFLOW_NAME } from './constants.js';
|
|||||||
import { checkForUpdates } from './utils/updateNotifier.js';
|
import { checkForUpdates } from './utils/updateNotifier.js';
|
||||||
import { getErrorMessage } from './utils/error.js';
|
import { getErrorMessage } from './utils/error.js';
|
||||||
import { resolveIssueTask, isIssueReference } from './github/issue.js';
|
import { resolveIssueTask, isIssueReference } from './github/issue.js';
|
||||||
import {
|
import { selectAndExecuteTask } from './commands/execution/selectAndExecute.js';
|
||||||
selectAndExecuteTask,
|
import type { TaskExecutionOptions, SelectAndExecuteOptions } from './commands/execution/types.js';
|
||||||
type SelectAndExecuteOptions,
|
|
||||||
} from './commands/execution/selectAndExecute.js';
|
|
||||||
import type { TaskExecutionOptions } from './commands/execution/taskExecution.js';
|
|
||||||
import type { ProviderType } from './providers/index.js';
|
import type { ProviderType } from './providers/index.js';
|
||||||
|
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
|
|||||||
@ -2,13 +2,22 @@
|
|||||||
* Task/workflow execution commands.
|
* Task/workflow execution commands.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { executeWorkflow, type WorkflowExecutionResult, type WorkflowExecutionOptions } from './workflowExecution.js';
|
// Types
|
||||||
export { executeTask, runAllTasks, executeAndCompleteTask, resolveTaskExecution, type TaskExecutionOptions } from './taskExecution.js';
|
export type {
|
||||||
|
WorkflowExecutionResult,
|
||||||
|
WorkflowExecutionOptions,
|
||||||
|
TaskExecutionOptions,
|
||||||
|
ExecuteTaskOptions,
|
||||||
|
PipelineExecutionOptions,
|
||||||
|
WorktreeConfirmationResult,
|
||||||
|
SelectAndExecuteOptions,
|
||||||
|
} from './types.js';
|
||||||
|
|
||||||
|
export { executeWorkflow } from './workflowExecution.js';
|
||||||
|
export { executeTask, runAllTasks, executeAndCompleteTask, resolveTaskExecution } from './taskExecution.js';
|
||||||
export {
|
export {
|
||||||
selectAndExecuteTask,
|
selectAndExecuteTask,
|
||||||
confirmAndCreateWorktree,
|
confirmAndCreateWorktree,
|
||||||
type SelectAndExecuteOptions,
|
|
||||||
type WorktreeConfirmationResult,
|
|
||||||
} from './selectAndExecute.js';
|
} from './selectAndExecute.js';
|
||||||
export { executePipeline, type PipelineExecutionOptions } from './pipelineExecution.js';
|
export { executePipeline } from './pipelineExecution.js';
|
||||||
export { withAgentSession } from './session.js';
|
export { withAgentSession } from './session.js';
|
||||||
|
|||||||
@ -10,10 +10,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { execFileSync } from 'node:child_process';
|
import { execFileSync } from 'node:child_process';
|
||||||
import { fetchIssue, formatIssueAsTask, checkGhCli, type GitHubIssue } from '../../github/issue.js';
|
import { fetchIssue, formatIssueAsTask, checkGhCli } from '../../github/issue.js';
|
||||||
|
import type { GitHubIssue } from '../../github/types.js';
|
||||||
import { createPullRequest, pushBranch, buildPrBody } from '../../github/pr.js';
|
import { createPullRequest, pushBranch, buildPrBody } from '../../github/pr.js';
|
||||||
import { stageAndCommit } from '../../task/git.js';
|
import { stageAndCommit } from '../../task/git.js';
|
||||||
import { executeTask, type TaskExecutionOptions } from './taskExecution.js';
|
import { executeTask } from './taskExecution.js';
|
||||||
|
import type { TaskExecutionOptions, PipelineExecutionOptions } from './types.js';
|
||||||
import { loadGlobalConfig } from '../../config/global/globalConfig.js';
|
import { loadGlobalConfig } from '../../config/global/globalConfig.js';
|
||||||
import { info, error, success, status, blankLine } from '../../utils/ui.js';
|
import { info, error, success, status, blankLine } from '../../utils/ui.js';
|
||||||
import { createLogger } from '../../utils/debug.js';
|
import { createLogger } from '../../utils/debug.js';
|
||||||
@ -25,31 +27,11 @@ import {
|
|||||||
EXIT_GIT_OPERATION_FAILED,
|
EXIT_GIT_OPERATION_FAILED,
|
||||||
EXIT_PR_CREATION_FAILED,
|
EXIT_PR_CREATION_FAILED,
|
||||||
} from '../../exitCodes.js';
|
} from '../../exitCodes.js';
|
||||||
import type { ProviderType } from '../../providers/index.js';
|
|
||||||
|
export type { PipelineExecutionOptions };
|
||||||
|
|
||||||
const log = createLogger('pipeline');
|
const log = createLogger('pipeline');
|
||||||
|
|
||||||
export interface PipelineExecutionOptions {
|
|
||||||
/** GitHub issue number */
|
|
||||||
issueNumber?: number;
|
|
||||||
/** Task content (alternative to issue) */
|
|
||||||
task?: string;
|
|
||||||
/** Workflow name or path to workflow file */
|
|
||||||
workflow: string;
|
|
||||||
/** Branch name (auto-generated if omitted) */
|
|
||||||
branch?: string;
|
|
||||||
/** Whether to create a PR after successful execution */
|
|
||||||
autoPr: boolean;
|
|
||||||
/** Repository in owner/repo format */
|
|
||||||
repo?: string;
|
|
||||||
/** Skip branch creation, commit, and push (workflow-only execution) */
|
|
||||||
skipGit?: boolean;
|
|
||||||
/** Working directory */
|
|
||||||
cwd: string;
|
|
||||||
provider?: ProviderType;
|
|
||||||
model?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expand template variables in a string.
|
* Expand template variables in a string.
|
||||||
* Supported: {title}, {issue}, {issue_body}, {report}
|
* Supported: {title}, {issue}, {issue_body}, {report}
|
||||||
|
|||||||
@ -17,23 +17,12 @@ import { info, error, success } from '../../utils/ui.js';
|
|||||||
import { createLogger } from '../../utils/debug.js';
|
import { createLogger } from '../../utils/debug.js';
|
||||||
import { createPullRequest, buildPrBody } from '../../github/pr.js';
|
import { createPullRequest, buildPrBody } from '../../github/pr.js';
|
||||||
import { executeTask } from './taskExecution.js';
|
import { executeTask } from './taskExecution.js';
|
||||||
import type { TaskExecutionOptions } from './taskExecution.js';
|
import type { TaskExecutionOptions, WorktreeConfirmationResult, SelectAndExecuteOptions } from './types.js';
|
||||||
|
|
||||||
|
export type { WorktreeConfirmationResult, SelectAndExecuteOptions };
|
||||||
|
|
||||||
const log = createLogger('selectAndExecute');
|
const log = createLogger('selectAndExecute');
|
||||||
|
|
||||||
export interface WorktreeConfirmationResult {
|
|
||||||
execCwd: string;
|
|
||||||
isWorktree: boolean;
|
|
||||||
branch?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SelectAndExecuteOptions {
|
|
||||||
autoPr?: boolean;
|
|
||||||
repo?: string;
|
|
||||||
workflow?: string;
|
|
||||||
createWorktree?: boolean | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select a workflow interactively.
|
* Select a workflow interactively.
|
||||||
* Returns the selected workflow name, or null if cancelled.
|
* Returns the selected workflow name, or null if cancelled.
|
||||||
|
|||||||
@ -19,28 +19,12 @@ import { createLogger } from '../../utils/debug.js';
|
|||||||
import { getErrorMessage } from '../../utils/error.js';
|
import { getErrorMessage } from '../../utils/error.js';
|
||||||
import { executeWorkflow } from './workflowExecution.js';
|
import { executeWorkflow } from './workflowExecution.js';
|
||||||
import { DEFAULT_WORKFLOW_NAME } from '../../constants.js';
|
import { DEFAULT_WORKFLOW_NAME } from '../../constants.js';
|
||||||
import type { ProviderType } from '../../providers/index.js';
|
import type { TaskExecutionOptions, ExecuteTaskOptions } from './types.js';
|
||||||
|
|
||||||
|
export type { TaskExecutionOptions, ExecuteTaskOptions };
|
||||||
|
|
||||||
const log = createLogger('task');
|
const log = createLogger('task');
|
||||||
|
|
||||||
export interface TaskExecutionOptions {
|
|
||||||
provider?: ProviderType;
|
|
||||||
model?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ExecuteTaskOptions {
|
|
||||||
/** Task content */
|
|
||||||
task: string;
|
|
||||||
/** Working directory (may be a clone path) */
|
|
||||||
cwd: string;
|
|
||||||
/** Workflow name or path (auto-detected by isWorkflowPath) */
|
|
||||||
workflowIdentifier: string;
|
|
||||||
/** Project root (where .takt/ lives) */
|
|
||||||
projectCwd: string;
|
|
||||||
/** Agent provider/model overrides */
|
|
||||||
agentOverrides?: TaskExecutionOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a single task with workflow.
|
* Execute a single task with workflow.
|
||||||
*/
|
*/
|
||||||
|
|||||||
76
src/commands/execution/types.ts
Normal file
76
src/commands/execution/types.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* Execution module type definitions
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Language } from '../../models/types.js';
|
||||||
|
import type { ProviderType } from '../../providers/index.js';
|
||||||
|
|
||||||
|
/** Result of workflow execution */
|
||||||
|
export interface WorkflowExecutionResult {
|
||||||
|
success: boolean;
|
||||||
|
reason?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Options for workflow execution */
|
||||||
|
export interface WorkflowExecutionOptions {
|
||||||
|
/** Header prefix for display */
|
||||||
|
headerPrefix?: string;
|
||||||
|
/** Project root directory (where .takt/ lives). */
|
||||||
|
projectCwd: string;
|
||||||
|
/** Language for instruction metadata */
|
||||||
|
language?: Language;
|
||||||
|
provider?: ProviderType;
|
||||||
|
model?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TaskExecutionOptions {
|
||||||
|
provider?: ProviderType;
|
||||||
|
model?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExecuteTaskOptions {
|
||||||
|
/** Task content */
|
||||||
|
task: string;
|
||||||
|
/** Working directory (may be a clone path) */
|
||||||
|
cwd: string;
|
||||||
|
/** Workflow name or path (auto-detected by isWorkflowPath) */
|
||||||
|
workflowIdentifier: string;
|
||||||
|
/** Project root (where .takt/ lives) */
|
||||||
|
projectCwd: string;
|
||||||
|
/** Agent provider/model overrides */
|
||||||
|
agentOverrides?: TaskExecutionOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PipelineExecutionOptions {
|
||||||
|
/** GitHub issue number */
|
||||||
|
issueNumber?: number;
|
||||||
|
/** Task content (alternative to issue) */
|
||||||
|
task?: string;
|
||||||
|
/** Workflow name or path to workflow file */
|
||||||
|
workflow: string;
|
||||||
|
/** Branch name (auto-generated if omitted) */
|
||||||
|
branch?: string;
|
||||||
|
/** Whether to create a PR after successful execution */
|
||||||
|
autoPr: boolean;
|
||||||
|
/** Repository in owner/repo format */
|
||||||
|
repo?: string;
|
||||||
|
/** Skip branch creation, commit, and push (workflow-only execution) */
|
||||||
|
skipGit?: boolean;
|
||||||
|
/** Working directory */
|
||||||
|
cwd: string;
|
||||||
|
provider?: ProviderType;
|
||||||
|
model?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorktreeConfirmationResult {
|
||||||
|
execCwd: string;
|
||||||
|
isWorktree: boolean;
|
||||||
|
branch?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SelectAndExecuteOptions {
|
||||||
|
autoPr?: boolean;
|
||||||
|
repo?: string;
|
||||||
|
workflow?: string;
|
||||||
|
createWorktree?: boolean | undefined;
|
||||||
|
}
|
||||||
@ -4,9 +4,12 @@
|
|||||||
|
|
||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
import { WorkflowEngine } from '../../workflow/engine/WorkflowEngine.js';
|
import { WorkflowEngine } from '../../workflow/engine/WorkflowEngine.js';
|
||||||
import type { WorkflowConfig, Language } from '../../models/types.js';
|
import type { WorkflowConfig } from '../../models/types.js';
|
||||||
import type { IterationLimitRequest } from '../../workflow/types.js';
|
import type { IterationLimitRequest } from '../../workflow/types.js';
|
||||||
import type { ProviderType } from '../../providers/index.js';
|
import type { WorkflowExecutionResult, WorkflowExecutionOptions } from './types.js';
|
||||||
|
|
||||||
|
export type { WorkflowExecutionResult, WorkflowExecutionOptions };
|
||||||
|
|
||||||
import {
|
import {
|
||||||
loadAgentSessions,
|
loadAgentSessions,
|
||||||
updateAgentSession,
|
updateAgentSession,
|
||||||
@ -62,24 +65,6 @@ function formatElapsedTime(startTime: string, endTime: string): string {
|
|||||||
return `${minutes}m ${seconds}s`;
|
return `${minutes}m ${seconds}s`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Result of workflow execution */
|
|
||||||
export interface WorkflowExecutionResult {
|
|
||||||
success: boolean;
|
|
||||||
reason?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Options for workflow execution */
|
|
||||||
export interface WorkflowExecutionOptions {
|
|
||||||
/** Header prefix for display */
|
|
||||||
headerPrefix?: string;
|
|
||||||
/** Project root directory (where .takt/ lives). */
|
|
||||||
projectCwd: string;
|
|
||||||
/** Language for instruction metadata */
|
|
||||||
language?: Language;
|
|
||||||
provider?: ProviderType;
|
|
||||||
model?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a workflow and handle all events
|
* Execute a workflow and handle all events
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import {
|
|||||||
import { selectOption, confirm } from '../../prompt/index.js';
|
import { selectOption, confirm } from '../../prompt/index.js';
|
||||||
import { info } from '../../utils/ui.js';
|
import { info } from '../../utils/ui.js';
|
||||||
import { createLogger } from '../../utils/debug.js';
|
import { createLogger } from '../../utils/debug.js';
|
||||||
import type { TaskExecutionOptions } from '../execution/taskExecution.js';
|
import type { TaskExecutionOptions } from '../execution/types.js';
|
||||||
import {
|
import {
|
||||||
type ListAction,
|
type ListAction,
|
||||||
showFullDiff,
|
showFullDiff,
|
||||||
|
|||||||
@ -22,7 +22,8 @@ import { selectOption, promptInput } from '../../prompt/index.js';
|
|||||||
import { info, success, error as logError, warn, header, blankLine } from '../../utils/ui.js';
|
import { info, success, error as logError, warn, header, blankLine } from '../../utils/ui.js';
|
||||||
import { createLogger } from '../../utils/debug.js';
|
import { createLogger } from '../../utils/debug.js';
|
||||||
import { getErrorMessage } from '../../utils/error.js';
|
import { getErrorMessage } from '../../utils/error.js';
|
||||||
import { executeTask, type TaskExecutionOptions } from '../execution/taskExecution.js';
|
import { executeTask } from '../execution/taskExecution.js';
|
||||||
|
import type { TaskExecutionOptions } from '../execution/types.js';
|
||||||
import { listWorkflows } from '../../config/loaders/workflowLoader.js';
|
import { listWorkflows } from '../../config/loaders/workflowLoader.js';
|
||||||
import { getCurrentWorkflow } from '../../config/paths.js';
|
import { getCurrentWorkflow } from '../../config/paths.js';
|
||||||
import { DEFAULT_WORKFLOW_NAME } from '../../constants.js';
|
import { DEFAULT_WORKFLOW_NAME } from '../../constants.js';
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import {
|
|||||||
} from '../../utils/ui.js';
|
} from '../../utils/ui.js';
|
||||||
import { executeAndCompleteTask } from '../execution/taskExecution.js';
|
import { executeAndCompleteTask } from '../execution/taskExecution.js';
|
||||||
import { DEFAULT_WORKFLOW_NAME } from '../../constants.js';
|
import { DEFAULT_WORKFLOW_NAME } from '../../constants.js';
|
||||||
import type { TaskExecutionOptions } from '../execution/taskExecution.js';
|
import type { TaskExecutionOptions } from '../execution/types.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Watch for tasks and execute them as they appear.
|
* Watch for tasks and execute them as they appear.
|
||||||
|
|||||||
@ -8,31 +8,9 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|||||||
import { join, resolve } from 'node:path';
|
import { join, resolve } from 'node:path';
|
||||||
import { parse, stringify } from 'yaml';
|
import { parse, stringify } from 'yaml';
|
||||||
import { copyProjectResourcesToDir } from '../../resources/index.js';
|
import { copyProjectResourcesToDir } from '../../resources/index.js';
|
||||||
|
import type { PermissionMode, ProjectPermissionMode, ProjectLocalConfig } from '../types.js';
|
||||||
|
|
||||||
/** Permission mode for the project
|
export type { PermissionMode, ProjectPermissionMode, ProjectLocalConfig };
|
||||||
* - default: Uses Agent SDK's acceptEdits mode (auto-accepts file edits, minimal prompts)
|
|
||||||
* - sacrifice-my-pc: Auto-approves all permission requests (bypassPermissions)
|
|
||||||
*
|
|
||||||
* Note: 'confirm' mode is planned but not yet implemented
|
|
||||||
*/
|
|
||||||
export type PermissionMode = 'default' | 'sacrifice-my-pc';
|
|
||||||
|
|
||||||
/** @deprecated Use PermissionMode instead */
|
|
||||||
export type ProjectPermissionMode = PermissionMode;
|
|
||||||
|
|
||||||
/** Project configuration stored in .takt/config.yaml */
|
|
||||||
export interface ProjectLocalConfig {
|
|
||||||
/** Current workflow name */
|
|
||||||
workflow?: string;
|
|
||||||
/** Provider selection for agent runtime */
|
|
||||||
provider?: 'claude' | 'codex';
|
|
||||||
/** Permission mode setting */
|
|
||||||
permissionMode?: PermissionMode;
|
|
||||||
/** Verbose output mode */
|
|
||||||
verbose?: boolean;
|
|
||||||
/** Custom settings */
|
|
||||||
[key: string]: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Default project configuration */
|
/** Default project configuration */
|
||||||
const DEFAULT_PROJECT_CONFIG: ProjectLocalConfig = {
|
const DEFAULT_PROJECT_CONFIG: ProjectLocalConfig = {
|
||||||
|
|||||||
@ -84,13 +84,9 @@ export function addToInputHistory(projectDir: string, input: string): void {
|
|||||||
|
|
||||||
// ============ Agent Sessions ============
|
// ============ Agent Sessions ============
|
||||||
|
|
||||||
/** Agent session data for persistence */
|
import type { AgentSessionData } from '../types.js';
|
||||||
export interface AgentSessionData {
|
|
||||||
agentSessions: Record<string, string>;
|
export type { AgentSessionData };
|
||||||
updatedAt: string;
|
|
||||||
/** Provider that created these sessions (claude, codex, etc.) */
|
|
||||||
provider?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get path for storing agent sessions */
|
/** Get path for storing agent sessions */
|
||||||
export function getAgentSessionsPath(projectDir: string): string {
|
export function getAgentSessionsPath(projectDir: string): string {
|
||||||
|
|||||||
36
src/config/types.ts
Normal file
36
src/config/types.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Config module type definitions
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Permission mode for the project
|
||||||
|
* - default: Uses Agent SDK's acceptEdits mode (auto-accepts file edits, minimal prompts)
|
||||||
|
* - sacrifice-my-pc: Auto-approves all permission requests (bypassPermissions)
|
||||||
|
*
|
||||||
|
* Note: 'confirm' mode is planned but not yet implemented
|
||||||
|
*/
|
||||||
|
export type PermissionMode = 'default' | 'sacrifice-my-pc';
|
||||||
|
|
||||||
|
/** @deprecated Use PermissionMode instead */
|
||||||
|
export type ProjectPermissionMode = PermissionMode;
|
||||||
|
|
||||||
|
/** Project configuration stored in .takt/config.yaml */
|
||||||
|
export interface ProjectLocalConfig {
|
||||||
|
/** Current workflow name */
|
||||||
|
workflow?: string;
|
||||||
|
/** Provider selection for agent runtime */
|
||||||
|
provider?: 'claude' | 'codex';
|
||||||
|
/** Permission mode setting */
|
||||||
|
permissionMode?: PermissionMode;
|
||||||
|
/** Verbose output mode */
|
||||||
|
verbose?: boolean;
|
||||||
|
/** Custom settings */
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Agent session data for persistence */
|
||||||
|
export interface AgentSessionData {
|
||||||
|
agentSessions: Record<string, string>;
|
||||||
|
updatedAt: string;
|
||||||
|
/** Provider that created these sessions (claude, codex, etc.) */
|
||||||
|
provider?: string;
|
||||||
|
}
|
||||||
@ -7,25 +7,15 @@
|
|||||||
|
|
||||||
import { execFileSync } from 'node:child_process';
|
import { execFileSync } from 'node:child_process';
|
||||||
import { createLogger } from '../utils/debug.js';
|
import { createLogger } from '../utils/debug.js';
|
||||||
|
import type { GitHubIssue, GhCliStatus } from './types.js';
|
||||||
|
|
||||||
|
export type { GitHubIssue, GhCliStatus };
|
||||||
|
|
||||||
const log = createLogger('github');
|
const log = createLogger('github');
|
||||||
|
|
||||||
/** Regex to match `#N` patterns (issue numbers) */
|
/** Regex to match `#N` patterns (issue numbers) */
|
||||||
const ISSUE_NUMBER_REGEX = /^#(\d+)$/;
|
const ISSUE_NUMBER_REGEX = /^#(\d+)$/;
|
||||||
|
|
||||||
export interface GitHubIssue {
|
|
||||||
number: number;
|
|
||||||
title: string;
|
|
||||||
body: string;
|
|
||||||
labels: string[];
|
|
||||||
comments: Array<{ author: string; body: string }>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GhCliStatus {
|
|
||||||
available: boolean;
|
|
||||||
error?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if `gh` CLI is available and authenticated.
|
* Check if `gh` CLI is available and authenticated.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -7,31 +7,13 @@
|
|||||||
import { execFileSync } from 'node:child_process';
|
import { execFileSync } from 'node:child_process';
|
||||||
import { createLogger } from '../utils/debug.js';
|
import { createLogger } from '../utils/debug.js';
|
||||||
import { getErrorMessage } from '../utils/error.js';
|
import { getErrorMessage } from '../utils/error.js';
|
||||||
import { checkGhCli, type GitHubIssue } from './issue.js';
|
import { checkGhCli } from './issue.js';
|
||||||
|
import type { GitHubIssue, CreatePrOptions, CreatePrResult } from './types.js';
|
||||||
|
|
||||||
|
export type { CreatePrOptions, CreatePrResult };
|
||||||
|
|
||||||
const log = createLogger('github-pr');
|
const log = createLogger('github-pr');
|
||||||
|
|
||||||
export interface CreatePrOptions {
|
|
||||||
/** Branch to create PR from */
|
|
||||||
branch: string;
|
|
||||||
/** PR title */
|
|
||||||
title: string;
|
|
||||||
/** PR body (markdown) */
|
|
||||||
body: string;
|
|
||||||
/** Base branch (default: repo default branch) */
|
|
||||||
base?: string;
|
|
||||||
/** Repository in owner/repo format (optional, uses current repo if omitted) */
|
|
||||||
repo?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreatePrResult {
|
|
||||||
success: boolean;
|
|
||||||
/** PR URL on success */
|
|
||||||
url?: string;
|
|
||||||
/** Error message on failure */
|
|
||||||
error?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Push a branch to origin.
|
* Push a branch to origin.
|
||||||
* Throws on failure.
|
* Throws on failure.
|
||||||
|
|||||||
37
src/github/types.ts
Normal file
37
src/github/types.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* GitHub module type definitions
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface GitHubIssue {
|
||||||
|
number: number;
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
labels: string[];
|
||||||
|
comments: Array<{ author: string; body: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GhCliStatus {
|
||||||
|
available: boolean;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreatePrOptions {
|
||||||
|
/** Branch to create PR from */
|
||||||
|
branch: string;
|
||||||
|
/** PR title */
|
||||||
|
title: string;
|
||||||
|
/** PR body (markdown) */
|
||||||
|
body: string;
|
||||||
|
/** Base branch (default: repo default branch) */
|
||||||
|
base?: string;
|
||||||
|
/** Repository in owner/repo format (optional, uses current repo if omitted) */
|
||||||
|
repo?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreatePrResult {
|
||||||
|
success: boolean;
|
||||||
|
/** PR URL on success */
|
||||||
|
url?: string;
|
||||||
|
/** Error message on failure */
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
@ -6,20 +6,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
import type { StreamCallback, StreamEvent } from '../claude/process.js';
|
import type { StreamEvent } from '../claude/process.js';
|
||||||
import type { AgentResponse } from '../models/types.js';
|
import type { AgentResponse } from '../models/types.js';
|
||||||
import { getScenarioQueue } from './scenario.js';
|
import { getScenarioQueue } from './scenario.js';
|
||||||
|
import type { MockCallOptions } from './types.js';
|
||||||
|
|
||||||
/** Options for mock calls */
|
export type { MockCallOptions };
|
||||||
export interface MockCallOptions {
|
|
||||||
cwd: string;
|
|
||||||
sessionId?: string;
|
|
||||||
onStream?: StreamCallback;
|
|
||||||
/** Fixed response content (optional, defaults to generic mock response) */
|
|
||||||
mockResponse?: string;
|
|
||||||
/** Fixed status to return (optional, defaults to 'done') */
|
|
||||||
mockStatus?: 'done' | 'blocked' | 'approved' | 'rejected' | 'improve';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a mock session ID
|
* Generate a mock session ID
|
||||||
|
|||||||
@ -7,16 +7,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { readFileSync, existsSync } from 'node:fs';
|
import { readFileSync, existsSync } from 'node:fs';
|
||||||
|
import type { ScenarioEntry } from './types.js';
|
||||||
|
|
||||||
/** A single entry in a mock scenario */
|
export type { ScenarioEntry };
|
||||||
export interface ScenarioEntry {
|
|
||||||
/** Agent name to match (optional — if omitted, consumed by call order) */
|
|
||||||
agent?: string;
|
|
||||||
/** Response status */
|
|
||||||
status: 'done' | 'blocked' | 'approved' | 'rejected' | 'improve';
|
|
||||||
/** Response content body */
|
|
||||||
content: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queue that dispenses scenario entries.
|
* Queue that dispenses scenario entries.
|
||||||
|
|||||||
26
src/mock/types.ts
Normal file
26
src/mock/types.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Mock module type definitions
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { StreamCallback } from '../claude/process.js';
|
||||||
|
|
||||||
|
/** Options for mock calls */
|
||||||
|
export interface MockCallOptions {
|
||||||
|
cwd: string;
|
||||||
|
sessionId?: string;
|
||||||
|
onStream?: StreamCallback;
|
||||||
|
/** Fixed response content (optional, defaults to generic mock response) */
|
||||||
|
mockResponse?: string;
|
||||||
|
/** Fixed status to return (optional, defaults to 'done') */
|
||||||
|
mockStatus?: 'done' | 'blocked' | 'approved' | 'rejected' | 'improve';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A single entry in a mock scenario */
|
||||||
|
export interface ScenarioEntry {
|
||||||
|
/** Agent name to match (optional — if omitted, consumed by call order) */
|
||||||
|
agent?: string;
|
||||||
|
/** Response status */
|
||||||
|
status: 'done' | 'blocked' | 'approved' | 'rejected' | 'improve';
|
||||||
|
/** Response content body */
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
@ -2,7 +2,8 @@
|
|||||||
* Mock provider implementation
|
* Mock provider implementation
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { callMock, callMockCustom, type MockCallOptions } from '../mock/client.js';
|
import { callMock, callMockCustom } from '../mock/client.js';
|
||||||
|
import type { MockCallOptions } from '../mock/types.js';
|
||||||
import type { AgentResponse } from '../models/types.js';
|
import type { AgentResponse } from '../models/types.js';
|
||||||
import type { Provider, ProviderCallOptions } from './types.js';
|
import type { Provider, ProviderCallOptions } from './types.js';
|
||||||
|
|
||||||
|
|||||||
@ -9,23 +9,12 @@
|
|||||||
import { execFileSync } from 'node:child_process';
|
import { execFileSync } from 'node:child_process';
|
||||||
import { createLogger } from '../utils/debug.js';
|
import { createLogger } from '../utils/debug.js';
|
||||||
|
|
||||||
|
import type { BranchInfo, BranchListItem } from './types.js';
|
||||||
|
|
||||||
|
export type { BranchInfo, BranchListItem };
|
||||||
|
|
||||||
const log = createLogger('branchList');
|
const log = createLogger('branchList');
|
||||||
|
|
||||||
/** Branch info from `git branch --list` */
|
|
||||||
export interface BranchInfo {
|
|
||||||
branch: string;
|
|
||||||
commit: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Branch with list metadata */
|
|
||||||
export interface BranchListItem {
|
|
||||||
info: BranchInfo;
|
|
||||||
filesChanged: number;
|
|
||||||
taskSlug: string;
|
|
||||||
/** Original task instruction extracted from first commit message */
|
|
||||||
originalInstruction: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TAKT_BRANCH_PREFIX = 'takt/';
|
const TAKT_BRANCH_PREFIX = 'takt/';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -13,27 +13,12 @@ import { execFileSync } from 'node:child_process';
|
|||||||
import { createLogger } from '../utils/debug.js';
|
import { createLogger } from '../utils/debug.js';
|
||||||
import { slugify } from '../utils/slug.js';
|
import { slugify } from '../utils/slug.js';
|
||||||
import { loadGlobalConfig } from '../config/global/globalConfig.js';
|
import { loadGlobalConfig } from '../config/global/globalConfig.js';
|
||||||
|
import type { WorktreeOptions, WorktreeResult } from './types.js';
|
||||||
|
|
||||||
|
export type { WorktreeOptions, WorktreeResult };
|
||||||
|
|
||||||
const log = createLogger('clone');
|
const log = createLogger('clone');
|
||||||
|
|
||||||
export interface WorktreeOptions {
|
|
||||||
/** worktree setting: true = auto path, string = custom path */
|
|
||||||
worktree: boolean | string;
|
|
||||||
/** Branch name (optional, auto-generated if omitted) */
|
|
||||||
branch?: string;
|
|
||||||
/** Task slug for auto-generated paths/branches */
|
|
||||||
taskSlug: string;
|
|
||||||
/** GitHub Issue number (optional, for formatting branch/path) */
|
|
||||||
issueNumber?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WorktreeResult {
|
|
||||||
/** Absolute path to the clone */
|
|
||||||
path: string;
|
|
||||||
/** Branch name used */
|
|
||||||
branch: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CLONE_META_DIR = 'clone-meta';
|
const CLONE_META_DIR = 'clone-meta';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -112,11 +97,41 @@ export class CloneManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the main repository path (handles git worktree case).
|
||||||
|
* If projectDir is a worktree, returns the main repo path.
|
||||||
|
* Otherwise, returns projectDir as-is.
|
||||||
|
*/
|
||||||
|
private static resolveMainRepo(projectDir: string): string {
|
||||||
|
const gitPath = path.join(projectDir, '.git');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stats = fs.statSync(gitPath);
|
||||||
|
if (stats.isFile()) {
|
||||||
|
const content = fs.readFileSync(gitPath, 'utf-8');
|
||||||
|
const match = content.match(/^gitdir:\s*(.+)$/m);
|
||||||
|
if (match && match[1]) {
|
||||||
|
const worktreePath = match[1].trim();
|
||||||
|
const gitDir = path.resolve(worktreePath, '..', '..');
|
||||||
|
const mainRepoPath = path.dirname(gitDir);
|
||||||
|
log.info('Detected worktree, using main repo', { worktree: projectDir, mainRepo: mainRepoPath });
|
||||||
|
return mainRepoPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
log.debug('Failed to resolve main repo, using projectDir as-is', { error: String(err) });
|
||||||
|
}
|
||||||
|
|
||||||
|
return projectDir;
|
||||||
|
}
|
||||||
|
|
||||||
/** Clone a repository and remove origin to isolate from the main repo */
|
/** Clone a repository and remove origin to isolate from the main repo */
|
||||||
private static cloneAndIsolate(projectDir: string, clonePath: string): void {
|
private static cloneAndIsolate(projectDir: string, clonePath: string): void {
|
||||||
|
const referenceRepo = CloneManager.resolveMainRepo(projectDir);
|
||||||
|
|
||||||
fs.mkdirSync(path.dirname(clonePath), { recursive: true });
|
fs.mkdirSync(path.dirname(clonePath), { recursive: true });
|
||||||
|
|
||||||
execFileSync('git', ['clone', '--reference', projectDir, '--dissociate', projectDir, clonePath], {
|
execFileSync('git', ['clone', '--reference', referenceRepo, '--dissociate', projectDir, clonePath], {
|
||||||
cwd: projectDir,
|
cwd: projectDir,
|
||||||
stdio: 'pipe',
|
stdio: 'pipe',
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,17 +2,24 @@
|
|||||||
* Task execution module
|
* Task execution module
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Types
|
||||||
|
export type {
|
||||||
|
TaskInfo,
|
||||||
|
TaskResult,
|
||||||
|
WorktreeOptions,
|
||||||
|
WorktreeResult,
|
||||||
|
BranchInfo,
|
||||||
|
BranchListItem,
|
||||||
|
SummarizeOptions,
|
||||||
|
} from './types.js';
|
||||||
|
|
||||||
// Classes
|
// Classes
|
||||||
export { CloneManager } from './clone.js';
|
export { CloneManager } from './clone.js';
|
||||||
export { AutoCommitter } from './autoCommit.js';
|
export { AutoCommitter } from './autoCommit.js';
|
||||||
export { TaskSummarizer } from './summarize.js';
|
export { TaskSummarizer } from './summarize.js';
|
||||||
export { BranchManager } from './branchList.js';
|
export { BranchManager } from './branchList.js';
|
||||||
|
|
||||||
export {
|
export { TaskRunner } from './runner.js';
|
||||||
TaskRunner,
|
|
||||||
type TaskInfo,
|
|
||||||
type TaskResult,
|
|
||||||
} from './runner.js';
|
|
||||||
|
|
||||||
export { showTaskList } from './display.js';
|
export { showTaskList } from './display.js';
|
||||||
|
|
||||||
@ -25,8 +32,6 @@ export {
|
|||||||
saveCloneMeta,
|
saveCloneMeta,
|
||||||
removeCloneMeta,
|
removeCloneMeta,
|
||||||
cleanupOrphanedClone,
|
cleanupOrphanedClone,
|
||||||
type WorktreeOptions,
|
|
||||||
type WorktreeResult,
|
|
||||||
} from './clone.js';
|
} from './clone.js';
|
||||||
export {
|
export {
|
||||||
detectDefaultBranch,
|
detectDefaultBranch,
|
||||||
@ -36,9 +41,7 @@ export {
|
|||||||
extractTaskSlug,
|
extractTaskSlug,
|
||||||
getOriginalInstruction,
|
getOriginalInstruction,
|
||||||
buildListItems,
|
buildListItems,
|
||||||
type BranchInfo,
|
|
||||||
type BranchListItem,
|
|
||||||
} from './branchList.js';
|
} from './branchList.js';
|
||||||
export { autoCommitAndPush, type AutoCommitResult } from './autoCommit.js';
|
export { autoCommitAndPush, type AutoCommitResult } from './autoCommit.js';
|
||||||
export { summarizeTaskName, type SummarizeOptions } from './summarize.js';
|
export { summarizeTaskName } from './summarize.js';
|
||||||
export { TaskWatcher, type TaskWatcherOptions } from './watcher.js';
|
export { TaskWatcher, type TaskWatcherOptions } from './watcher.js';
|
||||||
|
|||||||
@ -16,27 +16,9 @@
|
|||||||
import * as fs from 'node:fs';
|
import * as fs from 'node:fs';
|
||||||
import * as path from 'node:path';
|
import * as path from 'node:path';
|
||||||
import { parseTaskFiles, parseTaskFile, type ParsedTask } from './parser.js';
|
import { parseTaskFiles, parseTaskFile, type ParsedTask } from './parser.js';
|
||||||
import type { TaskFileData } from './schema.js';
|
import type { TaskInfo, TaskResult } from './types.js';
|
||||||
|
|
||||||
/** タスク情報 */
|
export type { TaskInfo, TaskResult };
|
||||||
export interface TaskInfo {
|
|
||||||
filePath: string;
|
|
||||||
name: string;
|
|
||||||
content: string;
|
|
||||||
createdAt: string;
|
|
||||||
/** Structured data from YAML files (null for .md files) */
|
|
||||||
data: TaskFileData | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** タスク実行結果 */
|
|
||||||
export interface TaskResult {
|
|
||||||
task: TaskInfo;
|
|
||||||
success: boolean;
|
|
||||||
response: string;
|
|
||||||
executionLog: string[];
|
|
||||||
startedAt: string;
|
|
||||||
completedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* タスク実行管理クラス
|
* タスク実行管理クラス
|
||||||
|
|||||||
@ -8,6 +8,9 @@ import * as wanakana from 'wanakana';
|
|||||||
import { loadGlobalConfig } from '../config/global/globalConfig.js';
|
import { loadGlobalConfig } from '../config/global/globalConfig.js';
|
||||||
import { getProvider, type ProviderType } from '../providers/index.js';
|
import { getProvider, type ProviderType } from '../providers/index.js';
|
||||||
import { createLogger } from '../utils/debug.js';
|
import { createLogger } from '../utils/debug.js';
|
||||||
|
import type { SummarizeOptions } from './types.js';
|
||||||
|
|
||||||
|
export type { SummarizeOptions };
|
||||||
|
|
||||||
const log = createLogger('summarize');
|
const log = createLogger('summarize');
|
||||||
|
|
||||||
@ -25,15 +28,6 @@ Fix the login bug → fix-login-bug
|
|||||||
worktreeを作るときブランチ名をAIで生成 → ai-branch-naming
|
worktreeを作るときブランチ名をAIで生成 → ai-branch-naming
|
||||||
レビュー画面に元の指示を表示する → show-original-instruction`;
|
レビュー画面に元の指示を表示する → show-original-instruction`;
|
||||||
|
|
||||||
export interface SummarizeOptions {
|
|
||||||
/** Working directory for Claude execution */
|
|
||||||
cwd: string;
|
|
||||||
/** Model to use (optional, defaults to config or haiku) */
|
|
||||||
model?: string;
|
|
||||||
/** Use LLM for summarization (default: true). If false, uses romanization. */
|
|
||||||
useLLM?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitize a string for use as git branch name and directory name.
|
* Sanitize a string for use as git branch name and directory name.
|
||||||
* Allows only: a-z, 0-9, hyphen.
|
* Allows only: a-z, 0-9, hyphen.
|
||||||
|
|||||||
67
src/task/types.ts
Normal file
67
src/task/types.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* Task module type definitions
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { TaskFileData } from './schema.js';
|
||||||
|
|
||||||
|
/** タスク情報 */
|
||||||
|
export interface TaskInfo {
|
||||||
|
filePath: string;
|
||||||
|
name: string;
|
||||||
|
content: string;
|
||||||
|
createdAt: string;
|
||||||
|
/** Structured data from YAML files (null for .md files) */
|
||||||
|
data: TaskFileData | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** タスク実行結果 */
|
||||||
|
export interface TaskResult {
|
||||||
|
task: TaskInfo;
|
||||||
|
success: boolean;
|
||||||
|
response: string;
|
||||||
|
executionLog: string[];
|
||||||
|
startedAt: string;
|
||||||
|
completedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorktreeOptions {
|
||||||
|
/** worktree setting: true = auto path, string = custom path */
|
||||||
|
worktree: boolean | string;
|
||||||
|
/** Branch name (optional, auto-generated if omitted) */
|
||||||
|
branch?: string;
|
||||||
|
/** Task slug for auto-generated paths/branches */
|
||||||
|
taskSlug: string;
|
||||||
|
/** GitHub Issue number (optional, for formatting branch/path) */
|
||||||
|
issueNumber?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorktreeResult {
|
||||||
|
/** Absolute path to the clone */
|
||||||
|
path: string;
|
||||||
|
/** Branch name used */
|
||||||
|
branch: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Branch info from `git branch --list` */
|
||||||
|
export interface BranchInfo {
|
||||||
|
branch: string;
|
||||||
|
commit: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Branch with list metadata */
|
||||||
|
export interface BranchListItem {
|
||||||
|
info: BranchInfo;
|
||||||
|
filesChanged: number;
|
||||||
|
taskSlug: string;
|
||||||
|
/** Original task instruction extracted from first commit message */
|
||||||
|
originalInstruction: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SummarizeOptions {
|
||||||
|
/** Working directory for Claude execution */
|
||||||
|
cwd: string;
|
||||||
|
/** Model to use (optional, defaults to config or haiku) */
|
||||||
|
model?: string;
|
||||||
|
/** Use LLM for summarization (default: true). If false, uses romanization. */
|
||||||
|
useLLM?: boolean;
|
||||||
|
}
|
||||||
@ -6,7 +6,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { createLogger } from '../utils/debug.js';
|
import { createLogger } from '../utils/debug.js';
|
||||||
import { TaskRunner, type TaskInfo } from './runner.js';
|
import { TaskRunner } from './runner.js';
|
||||||
|
import type { TaskInfo } from './types.js';
|
||||||
|
|
||||||
const log = createLogger('watcher');
|
const log = createLogger('watcher');
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user