feat: Builtin管理をバンドル埋め込み方式に移行し、/ejectコマンドを追加 (#4)

- ローダーがユーザーファイル優先、なければdist/resources/からbuiltinを読む方式に変更
- /ejectコマンドを追加(builtinを~/.takt/にコピーしてカスタマイズ可能に)
- /refresh-builtinを簡素化(ejectへの移行案内)
- config.yamlにdisabled_builtinsフィールドを追加
- ワークフローYAMLをrules形式に統一
This commit is contained in:
nrslib 2026-01-30 20:03:38 +09:00
parent dba25a539b
commit 5265cc0059
27 changed files with 466 additions and 332 deletions

View File

@ -29,7 +29,7 @@ initial_step: plan
steps:
- name: plan
edit: false
agent: ~/.takt/agents/default/planner.md
agent: ../agents/default/planner.md
report:
name: 00-plan.md
format: |
@ -89,7 +89,7 @@ steps:
- name: implement
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
report:
- Scope: 01-coder-scope.md
- Decisions: 02-coder-decisions.md
@ -144,7 +144,7 @@ steps:
- name: ai_review
edit: false
agent: ~/.takt/agents/default/ai-antipattern-reviewer.md
agent: ../agents/default/ai-antipattern-reviewer.md
report:
name: 03-ai-review.md
format: |
@ -194,7 +194,7 @@ steps:
- name: ai_fix
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -224,7 +224,7 @@ steps:
- name: review
edit: false
agent: ~/.takt/agents/default/architecture-reviewer.md
agent: ../agents/default/architecture-reviewer.md
report:
name: 04-architect-review.md
format: |
@ -275,7 +275,7 @@ steps:
- name: improve
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -307,7 +307,7 @@ steps:
- name: fix
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -334,7 +334,7 @@ steps:
- name: security_review
edit: false
agent: ~/.takt/agents/default/security-reviewer.md
agent: ../agents/default/security-reviewer.md
report:
name: 05-security-review.md
format: |
@ -387,7 +387,7 @@ steps:
- name: security_fix
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -413,7 +413,7 @@ steps:
- name: supervise
edit: false
agent: ~/.takt/agents/default/supervisor.md
agent: ../agents/default/supervisor.md
report:
- Validation: 06-supervisor-validation.md
- Summary: summary.md

View File

@ -32,7 +32,7 @@ steps:
# ===========================================
- name: plan
edit: false
agent: ~/.takt/agents/default/planner.md
agent: ../agents/default/planner.md
report:
name: 00-plan.md
format: |
@ -89,7 +89,7 @@ steps:
# ===========================================
- name: implement
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
report:
- Scope: 01-coder-scope.md
- Decisions: 02-coder-decisions.md
@ -146,7 +146,7 @@ steps:
# ===========================================
- name: ai_review
edit: false
agent: ~/.takt/agents/default/ai-antipattern-reviewer.md
agent: ../agents/default/ai-antipattern-reviewer.md
report:
name: 03-ai-review.md
format: |
@ -196,7 +196,7 @@ steps:
- name: ai_fix
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -228,7 +228,7 @@ steps:
# ===========================================
- name: cqrs_es_review
edit: false
agent: ~/.takt/agents/expert-cqrs/cqrs-es-reviewer.md
agent: ../agents/expert-cqrs/cqrs-es-reviewer.md
report:
name: 04-cqrs-es-review.md
format: |
@ -282,7 +282,7 @@ steps:
- name: fix_cqrs_es
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -316,7 +316,7 @@ steps:
# ===========================================
- name: frontend_review
edit: false
agent: ~/.takt/agents/expert/frontend-reviewer.md
agent: ../agents/expert/frontend-reviewer.md
report:
name: 05-frontend-review.md
format: |
@ -370,7 +370,7 @@ steps:
- name: fix_frontend
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -404,7 +404,7 @@ steps:
# ===========================================
- name: security_review
edit: false
agent: ~/.takt/agents/expert/security-reviewer.md
agent: ../agents/expert/security-reviewer.md
report:
name: 06-security-review.md
format: |
@ -455,7 +455,7 @@ steps:
- name: fix_security
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -499,7 +499,7 @@ steps:
# ===========================================
- name: qa_review
edit: false
agent: ~/.takt/agents/expert/qa-reviewer.md
agent: ../agents/expert/qa-reviewer.md
report:
name: 07-qa-review.md
format: |
@ -550,7 +550,7 @@ steps:
- name: fix_qa
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -598,7 +598,7 @@ steps:
# ===========================================
- name: supervise
edit: false
agent: ~/.takt/agents/expert/supervisor.md
agent: ../agents/expert/supervisor.md
report:
- Validation: 08-supervisor-validation.md
- Summary: summary.md
@ -691,7 +691,7 @@ steps:
- name: fix_supervisor
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob

View File

@ -44,7 +44,7 @@ steps:
# ===========================================
- name: plan
edit: false
agent: ~/.takt/agents/default/planner.md
agent: ../agents/default/planner.md
report:
name: 00-plan.md
format: |
@ -101,7 +101,7 @@ steps:
# ===========================================
- name: implement
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
report:
- Scope: 01-coder-scope.md
- Decisions: 02-coder-decisions.md
@ -158,7 +158,7 @@ steps:
# ===========================================
- name: ai_review
edit: false
agent: ~/.takt/agents/default/ai-antipattern-reviewer.md
agent: ../agents/default/ai-antipattern-reviewer.md
report:
name: 03-ai-review.md
format: |
@ -208,7 +208,7 @@ steps:
- name: ai_fix
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -240,7 +240,7 @@ steps:
# ===========================================
- name: architect_review
edit: false
agent: ~/.takt/agents/default/architecture-reviewer.md
agent: ../agents/default/architecture-reviewer.md
report:
name: 04-architect-review.md
format: |
@ -300,7 +300,7 @@ steps:
- name: fix_architect
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -330,7 +330,7 @@ steps:
# ===========================================
- name: frontend_review
edit: false
agent: ~/.takt/agents/expert/frontend-reviewer.md
agent: ../agents/expert/frontend-reviewer.md
report:
name: 05-frontend-review.md
format: |
@ -384,7 +384,7 @@ steps:
- name: fix_frontend
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -418,7 +418,7 @@ steps:
# ===========================================
- name: security_review
edit: false
agent: ~/.takt/agents/expert/security-reviewer.md
agent: ../agents/expert/security-reviewer.md
report:
name: 06-security-review.md
format: |
@ -469,7 +469,7 @@ steps:
- name: fix_security
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -513,7 +513,7 @@ steps:
# ===========================================
- name: qa_review
edit: false
agent: ~/.takt/agents/expert/qa-reviewer.md
agent: ../agents/expert/qa-reviewer.md
report:
name: 07-qa-review.md
format: |
@ -564,7 +564,7 @@ steps:
- name: fix_qa
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -612,7 +612,7 @@ steps:
# ===========================================
- name: supervise
edit: false
agent: ~/.takt/agents/expert/supervisor.md
agent: ../agents/expert/supervisor.md
report:
- Validation: 08-supervisor-validation.md
- Summary: summary.md
@ -705,7 +705,7 @@ steps:
- name: fix_supervisor
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob

View File

@ -18,7 +18,7 @@ max_iterations: 5
steps:
- name: melchior
agent: ~/.takt/agents/magi/melchior.md
agent: ../agents/magi/melchior.md
allowed_tools:
- Read
- Glob
@ -55,7 +55,7 @@ steps:
next: balthasar
- name: balthasar
agent: ~/.takt/agents/magi/balthasar.md
agent: ../agents/magi/balthasar.md
allowed_tools:
- Read
- Glob
@ -97,7 +97,7 @@ steps:
next: casper
- name: casper
agent: ~/.takt/agents/magi/casper.md
agent: ../agents/magi/casper.md
allowed_tools:
- Read
- Glob

View File

@ -22,7 +22,7 @@ max_iterations: 10
steps:
- name: plan
agent: ~/.takt/agents/research/planner.md
agent: ../agents/research/planner.md
allowed_tools:
- Read
- Glob
@ -59,7 +59,7 @@ steps:
next: ABORT
- name: dig
agent: ~/.takt/agents/research/digger.md
agent: ../agents/research/digger.md
allowed_tools:
- Read
- Glob
@ -101,7 +101,7 @@ steps:
next: ABORT
- name: supervise
agent: ~/.takt/agents/research/supervisor.md
agent: ../agents/research/supervisor.md
allowed_tools:
- Read
- Glob

View File

@ -26,7 +26,7 @@ initial_step: plan
steps:
- name: plan
edit: false
agent: ~/.takt/agents/default/planner.md
agent: ../agents/default/planner.md
report:
name: 00-plan.md
format: |
@ -82,7 +82,7 @@ steps:
- name: implement
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
report:
- Scope: 01-coder-scope.md
- Decisions: 02-coder-decisions.md
@ -137,7 +137,7 @@ steps:
- name: ai_review
edit: false
agent: ~/.takt/agents/default/ai-antipattern-reviewer.md
agent: ../agents/default/ai-antipattern-reviewer.md
report:
name: 03-ai-review.md
format: |
@ -187,7 +187,7 @@ steps:
- name: review
edit: false
agent: ~/.takt/agents/default/architecture-reviewer.md
agent: ../agents/default/architecture-reviewer.md
report:
name: 04-architect-review.md
format: |
@ -239,7 +239,7 @@ steps:
- name: supervise
edit: false
agent: ~/.takt/agents/default/supervisor.md
agent: ../agents/default/supervisor.md
report:
- Validation: 05-supervisor-validation.md
- Summary: summary.md

View File

@ -20,7 +20,7 @@ initial_step: plan
steps:
- name: plan
edit: false
agent: ~/.takt/agents/default/planner.md
agent: ../agents/default/planner.md
report:
name: 00-plan.md
format: |
@ -80,7 +80,7 @@ steps:
- name: implement
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
report:
- Scope: 01-coder-scope.md
- Decisions: 02-coder-decisions.md
@ -140,7 +140,7 @@ steps:
- name: ai_review
edit: false
agent: ~/.takt/agents/default/ai-antipattern-reviewer.md
agent: ../agents/default/ai-antipattern-reviewer.md
report:
name: 03-ai-review.md
format: |
@ -190,7 +190,7 @@ steps:
- name: ai_fix
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -220,7 +220,7 @@ steps:
- name: review
edit: false
agent: ~/.takt/agents/default/architecture-reviewer.md
agent: ../agents/default/architecture-reviewer.md
report:
name: 04-architect-review.md
format: |
@ -282,7 +282,7 @@ steps:
- name: improve
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -314,7 +314,7 @@ steps:
- name: fix
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -340,7 +340,7 @@ steps:
- name: security_review
edit: false
agent: ~/.takt/agents/default/security-reviewer.md
agent: ../agents/default/security-reviewer.md
report:
name: 05-security-review.md
format: |
@ -393,7 +393,7 @@ steps:
- name: security_fix
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -419,7 +419,7 @@ steps:
- name: supervise
edit: false
agent: ~/.takt/agents/default/supervisor.md
agent: ../agents/default/supervisor.md
report:
- Validation: 06-supervisor-validation.md
- Summary: summary.md

View File

@ -41,7 +41,7 @@ steps:
# ===========================================
- name: plan
edit: false
agent: ~/.takt/agents/default/planner.md
agent: ../agents/default/planner.md
report:
name: 00-plan.md
format: |
@ -98,7 +98,7 @@ steps:
# ===========================================
- name: implement
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
report:
- Scope: 01-coder-scope.md
- Decisions: 02-coder-decisions.md
@ -155,7 +155,7 @@ steps:
# ===========================================
- name: ai_review
edit: false
agent: ~/.takt/agents/default/ai-antipattern-reviewer.md
agent: ../agents/default/ai-antipattern-reviewer.md
report:
name: 03-ai-review.md
format: |
@ -205,7 +205,7 @@ steps:
- name: ai_fix
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -237,7 +237,7 @@ steps:
# ===========================================
- name: cqrs_es_review
edit: false
agent: ~/.takt/agents/expert-cqrs/cqrs-es-reviewer.md
agent: ../agents/expert-cqrs/cqrs-es-reviewer.md
report:
name: 04-cqrs-es-review.md
format: |
@ -291,7 +291,7 @@ steps:
- name: fix_cqrs_es
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -325,7 +325,7 @@ steps:
# ===========================================
- name: frontend_review
edit: false
agent: ~/.takt/agents/expert/frontend-reviewer.md
agent: ../agents/expert/frontend-reviewer.md
report:
name: 05-frontend-review.md
format: |
@ -379,7 +379,7 @@ steps:
- name: fix_frontend
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -413,7 +413,7 @@ steps:
# ===========================================
- name: security_review
edit: false
agent: ~/.takt/agents/expert/security-reviewer.md
agent: ../agents/expert/security-reviewer.md
report:
name: 06-security-review.md
format: |
@ -464,7 +464,7 @@ steps:
- name: fix_security
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -508,7 +508,7 @@ steps:
# ===========================================
- name: qa_review
edit: false
agent: ~/.takt/agents/expert/qa-reviewer.md
agent: ../agents/expert/qa-reviewer.md
report:
name: 07-qa-review.md
format: |
@ -559,7 +559,7 @@ steps:
- name: fix_qa
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -607,7 +607,7 @@ steps:
# ===========================================
- name: supervise
edit: false
agent: ~/.takt/agents/expert/supervisor.md
agent: ../agents/expert/supervisor.md
report:
- Validation: 08-supervisor-validation.md
- Summary: summary.md
@ -700,7 +700,7 @@ steps:
- name: fix_supervisor
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob

View File

@ -32,7 +32,7 @@ steps:
# ===========================================
- name: plan
edit: false
agent: ~/.takt/agents/default/planner.md
agent: ../agents/default/planner.md
report:
name: 00-plan.md
format: |
@ -89,7 +89,7 @@ steps:
# ===========================================
- name: implement
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
report:
- Scope: 01-coder-scope.md
- Decisions: 02-coder-decisions.md
@ -146,7 +146,7 @@ steps:
# ===========================================
- name: ai_review
edit: false
agent: ~/.takt/agents/default/ai-antipattern-reviewer.md
agent: ../agents/default/ai-antipattern-reviewer.md
report:
name: 03-ai-review.md
format: |
@ -196,7 +196,7 @@ steps:
- name: ai_fix
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -228,7 +228,7 @@ steps:
# ===========================================
- name: architect_review
edit: false
agent: ~/.takt/agents/default/architecture-reviewer.md
agent: ../agents/default/architecture-reviewer.md
report:
name: 04-architect-review.md
format: |
@ -288,7 +288,7 @@ steps:
- name: fix_architect
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -318,7 +318,7 @@ steps:
# ===========================================
- name: frontend_review
edit: false
agent: ~/.takt/agents/expert/frontend-reviewer.md
agent: ../agents/expert/frontend-reviewer.md
report:
name: 05-frontend-review.md
format: |
@ -372,7 +372,7 @@ steps:
- name: fix_frontend
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -406,7 +406,7 @@ steps:
# ===========================================
- name: security_review
edit: false
agent: ~/.takt/agents/expert/security-reviewer.md
agent: ../agents/expert/security-reviewer.md
report:
name: 06-security-review.md
format: |
@ -457,7 +457,7 @@ steps:
- name: fix_security
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -501,7 +501,7 @@ steps:
# ===========================================
- name: qa_review
edit: false
agent: ~/.takt/agents/expert/qa-reviewer.md
agent: ../agents/expert/qa-reviewer.md
report:
name: 07-qa-review.md
format: |
@ -552,7 +552,7 @@ steps:
- name: fix_qa
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob
@ -600,7 +600,7 @@ steps:
# ===========================================
- name: supervise
edit: false
agent: ~/.takt/agents/expert/supervisor.md
agent: ../agents/expert/supervisor.md
report:
- Validation: 08-supervisor-validation.md
- Summary: summary.md
@ -693,7 +693,7 @@ steps:
- name: fix_supervisor
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
allowed_tools:
- Read
- Glob

View File

@ -18,7 +18,7 @@ max_iterations: 5
steps:
- name: melchior
agent: ~/.takt/agents/magi/melchior.md
agent: ../agents/magi/melchior.md
allowed_tools:
- Read
- Glob
@ -55,7 +55,7 @@ steps:
next: balthasar
- name: balthasar
agent: ~/.takt/agents/magi/balthasar.md
agent: ../agents/magi/balthasar.md
allowed_tools:
- Read
- Glob
@ -97,7 +97,7 @@ steps:
next: casper
- name: casper
agent: ~/.takt/agents/magi/casper.md
agent: ../agents/magi/casper.md
allowed_tools:
- Read
- Glob

View File

@ -22,7 +22,7 @@ max_iterations: 10
steps:
- name: plan
agent: ~/.takt/agents/research/planner.md
agent: ../agents/research/planner.md
allowed_tools:
- Read
- Glob
@ -59,7 +59,7 @@ steps:
next: ABORT
- name: dig
agent: ~/.takt/agents/research/digger.md
agent: ../agents/research/digger.md
allowed_tools:
- Read
- Glob
@ -101,7 +101,7 @@ steps:
next: ABORT
- name: supervise
agent: ~/.takt/agents/research/supervisor.md
agent: ../agents/research/supervisor.md
allowed_tools:
- Read
- Glob

View File

@ -21,7 +21,7 @@ initial_step: plan
steps:
- name: plan
edit: false
agent: ~/.takt/agents/default/planner.md
agent: ../agents/default/planner.md
report:
name: 00-plan.md
format: |
@ -81,7 +81,7 @@ steps:
- name: implement
edit: true
agent: ~/.takt/agents/default/coder.md
agent: ../agents/default/coder.md
report:
- Scope: 01-coder-scope.md
- Decisions: 02-coder-decisions.md
@ -136,7 +136,7 @@ steps:
- name: ai_review
edit: false
agent: ~/.takt/agents/default/ai-antipattern-reviewer.md
agent: ../agents/default/ai-antipattern-reviewer.md
report:
name: 03-ai-review.md
format: |
@ -186,7 +186,7 @@ steps:
- name: review
edit: false
agent: ~/.takt/agents/default/architecture-reviewer.md
agent: ../agents/default/architecture-reviewer.md
report:
name: 04-architect-review.md
format: |
@ -238,7 +238,7 @@ steps:
- name: supervise
edit: false
agent: ~/.takt/agents/default/supervisor.md
agent: ../agents/default/supervisor.md
report:
- Validation: 05-supervisor-validation.md
- Summary: summary.md

View File

@ -10,11 +10,15 @@ import { randomUUID } from 'node:crypto';
import {
getBuiltinWorkflow,
loadAllWorkflows,
loadWorkflow,
listWorkflows,
loadAgentPromptFromPath,
} from '../config/loader.js';
import {
getCurrentWorkflow,
setCurrentWorkflow,
getProjectConfigDir,
getBuiltinAgentsDir,
loadInputHistory,
saveInputHistory,
addToInputHistory,
@ -27,11 +31,17 @@ import {
loadWorktreeSessions,
updateWorktreeSession,
} from '../config/paths.js';
import { getLanguage } from '../config/globalConfig.js';
import { loadProjectConfig } from '../config/projectConfig.js';
describe('getBuiltinWorkflow', () => {
it('should return null for all workflow names (no built-in workflows)', () => {
expect(getBuiltinWorkflow('default')).toBeNull();
it('should return builtin workflow when it exists in resources', () => {
const workflow = getBuiltinWorkflow('default');
expect(workflow).not.toBeNull();
expect(workflow!.name).toBe('default');
});
it('should return null for non-existent workflow names', () => {
expect(getBuiltinWorkflow('passthrough')).toBeNull();
expect(getBuiltinWorkflow('unknown')).toBeNull();
expect(getBuiltinWorkflow('')).toBeNull();
@ -78,6 +88,65 @@ steps:
});
});
describe('loadWorkflow (builtin fallback)', () => {
it('should load builtin workflow when user workflow does not exist', () => {
const workflow = loadWorkflow('default');
expect(workflow).not.toBeNull();
expect(workflow!.name).toBe('default');
});
it('should return null for non-existent workflow', () => {
const workflow = loadWorkflow('does-not-exist');
expect(workflow).toBeNull();
});
it('should load builtin workflows like simple, research', () => {
const simple = loadWorkflow('simple');
expect(simple).not.toBeNull();
expect(simple!.name).toBe('simple');
const research = loadWorkflow('research');
expect(research).not.toBeNull();
expect(research!.name).toBe('research');
});
});
describe('listWorkflows (builtin fallback)', () => {
it('should include builtin workflows', () => {
const workflows = listWorkflows();
expect(workflows).toContain('default');
expect(workflows).toContain('simple');
});
it('should return sorted list', () => {
const workflows = listWorkflows();
const sorted = [...workflows].sort();
expect(workflows).toEqual(sorted);
});
});
describe('loadAllWorkflows (builtin fallback)', () => {
it('should include builtin workflows in the map', () => {
const workflows = loadAllWorkflows();
expect(workflows.has('default')).toBe(true);
expect(workflows.has('simple')).toBe(true);
});
});
describe('loadAgentPromptFromPath (builtin paths)', () => {
it('should load agent prompt from builtin resources path', () => {
const lang = getLanguage();
const builtinAgentsDir = getBuiltinAgentsDir(lang);
const agentPath = join(builtinAgentsDir, 'default', 'coder.md');
if (existsSync(agentPath)) {
const prompt = loadAgentPromptFromPath(agentPath);
expect(prompt).toBeTruthy();
expect(typeof prompt).toBe('string');
}
});
});
describe('getCurrentWorkflow', () => {
let testDir: string;

View File

@ -26,8 +26,8 @@ vi.mock('../prompt/index.js', () => ({
// Import after mocks are set up
const { needsLanguageSetup } = await import('../config/initialization.js');
const { getGlobalAgentsDir, getGlobalWorkflowsDir } = await import('../config/paths.js');
const { copyLanguageResourcesToDir, copyProjectResourcesToDir, getLanguageResourcesDir, getProjectResourcesDir } = await import('../resources/index.js');
const { getGlobalConfigPath } = await import('../config/paths.js');
const { copyProjectResourcesToDir, getLanguageResourcesDir, getProjectResourcesDir } = await import('../resources/index.js');
describe('initialization', () => {
beforeEach(() => {
@ -43,48 +43,17 @@ describe('initialization', () => {
});
describe('needsLanguageSetup', () => {
it('should return true when neither agents nor workflows exist', () => {
it('should return true when config.yaml does not exist', () => {
expect(needsLanguageSetup()).toBe(true);
});
it('should return true when only agents exists', () => {
mkdirSync(getGlobalAgentsDir(), { recursive: true });
expect(needsLanguageSetup()).toBe(true);
});
it('should return true when only workflows exists', () => {
mkdirSync(getGlobalWorkflowsDir(), { recursive: true });
expect(needsLanguageSetup()).toBe(true);
});
it('should return false when both agents and workflows exist', () => {
mkdirSync(getGlobalAgentsDir(), { recursive: true });
mkdirSync(getGlobalWorkflowsDir(), { recursive: true });
it('should return false when config.yaml exists', () => {
mkdirSync(testTaktDir, { recursive: true });
writeFileSync(getGlobalConfigPath(), 'language: en\n', 'utf-8');
expect(needsLanguageSetup()).toBe(false);
});
});
describe('copyLanguageResourcesToDir', () => {
it('should throw error when language directory does not exist', () => {
const nonExistentLang = 'xx' as 'en' | 'ja';
expect(() => copyLanguageResourcesToDir(testTaktDir, nonExistentLang)).toThrow(
/Language resources not found/
);
});
it('should copy language resources to target directory', () => {
// This test requires actual language resources to exist
const langDir = getLanguageResourcesDir('ja');
if (existsSync(langDir)) {
mkdirSync(testTaktDir, { recursive: true });
copyLanguageResourcesToDir(testTaktDir, 'ja');
// Verify that agents and workflows directories were created
expect(existsSync(join(testTaktDir, 'agents'))).toBe(true);
expect(existsSync(join(testTaktDir, 'workflows'))).toBe(true);
}
});
});
});
describe('copyProjectResourcesToDir', () => {

View File

@ -32,6 +32,7 @@ import {
switchConfig,
addTask,
refreshBuiltin,
ejectBuiltin,
watchTasks,
listTasks,
} from './commands/index.js';
@ -171,6 +172,10 @@ program
await refreshBuiltin();
return;
case 'eject':
await ejectBuiltin(args[0]);
return;
case 'watch':
await watchTasks(cwd);
return;
@ -182,7 +187,7 @@ program
default:
error(`Unknown command: /${command}`);
info('Available: /run-tasks (/run), /watch, /add-task (/add), /list-tasks (/list), /switch (/sw), /clear, /refresh-builtin, /help, /config');
info('Available: /run-tasks (/run), /watch, /add-task (/add), /list-tasks (/list), /switch (/sw), /clear, /eject, /help, /config');
process.exit(1);
}
}

124
src/commands/eject.ts Normal file
View File

@ -0,0 +1,124 @@
/**
* /eject command implementation
*
* Copies a builtin workflow (and its agents) to ~/.takt/ for user customization.
* Once ejected, the user copy takes priority over the builtin version.
*/
import { existsSync, readdirSync, statSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
import { join, basename, dirname } from 'node:path';
import { getGlobalWorkflowsDir, getGlobalAgentsDir, getBuiltinWorkflowsDir, getBuiltinAgentsDir } from '../config/paths.js';
import { getLanguage } from '../config/globalConfig.js';
import { header, success, info, warn, error } from '../utils/ui.js';
/**
* Eject a builtin workflow to user space for customization.
* Copies the workflow YAML and related agent .md files to ~/.takt/.
* Agent paths in the ejected workflow are rewritten from ../agents/ to ~/.takt/agents/.
*/
export async function ejectBuiltin(name?: string): Promise<void> {
header('Eject Builtin');
const lang = getLanguage();
const builtinWorkflowsDir = getBuiltinWorkflowsDir(lang);
if (!name) {
// List available builtins
listAvailableBuiltins(builtinWorkflowsDir);
return;
}
const builtinPath = join(builtinWorkflowsDir, `${name}.yaml`);
if (!existsSync(builtinPath)) {
error(`Builtin workflow not found: ${name}`);
info('Run "takt /eject" to see available builtins.');
return;
}
const userWorkflowsDir = getGlobalWorkflowsDir();
const userAgentsDir = getGlobalAgentsDir();
const builtinAgentsDir = getBuiltinAgentsDir(lang);
// Copy workflow YAML (rewrite agent paths)
const workflowDest = join(userWorkflowsDir, `${name}.yaml`);
if (existsSync(workflowDest)) {
warn(`User workflow already exists: ${workflowDest}`);
warn('Skipping workflow copy (user version takes priority).');
} else {
mkdirSync(dirname(workflowDest), { recursive: true });
const content = readFileSync(builtinPath, 'utf-8');
// Rewrite relative agent paths to ~/.takt/agents/
const rewritten = content.replace(
/agent:\s*\.\.\/agents\//g,
'agent: ~/.takt/agents/',
);
writeFileSync(workflowDest, rewritten, 'utf-8');
success(`Ejected workflow: ${workflowDest}`);
}
// Copy related agent files
const agentPaths = extractAgentRelativePaths(builtinPath);
let copiedAgents = 0;
for (const relPath of agentPaths) {
const srcPath = join(builtinAgentsDir, relPath);
const destPath = join(userAgentsDir, relPath);
if (!existsSync(srcPath)) continue;
if (existsSync(destPath)) {
info(` Agent already exists: ${destPath}`);
continue;
}
mkdirSync(dirname(destPath), { recursive: true });
writeFileSync(destPath, readFileSync(srcPath));
info(`${destPath}`);
copiedAgents++;
}
if (copiedAgents > 0) {
success(`${copiedAgents} agent file(s) ejected.`);
}
}
/** List available builtin workflows for ejection */
function listAvailableBuiltins(builtinWorkflowsDir: string): void {
if (!existsSync(builtinWorkflowsDir)) {
warn('No builtin workflows found.');
return;
}
info('Available builtin workflows:');
console.log();
for (const entry of readdirSync(builtinWorkflowsDir).sort()) {
if (!entry.endsWith('.yaml') && !entry.endsWith('.yml')) continue;
if (!statSync(join(builtinWorkflowsDir, entry)).isFile()) continue;
const name = entry.replace(/\.ya?ml$/, '');
info(` ${name}`);
}
console.log();
info('Usage: takt /eject {name}');
}
/**
* Extract agent relative paths from a builtin workflow YAML.
* Matches `agent: ../agents/{path}` and returns the {path} portions.
*/
function extractAgentRelativePaths(workflowPath: string): string[] {
const content = readFileSync(workflowPath, 'utf-8');
const paths = new Set<string>();
const regex = /agent:\s*\.\.\/agents\/(.+)/g;
let match: RegExpExecArray | null;
while ((match = regex.exec(content)) !== null) {
if (match[1]) {
paths.add(match[1].trim());
}
}
return Array.from(paths);
}

View File

@ -21,7 +21,8 @@ Usage:
takt /list-tasks (/list) List task branches (merge/delete)
takt /switch Switch workflow interactively
takt /clear Clear agent conversation sessions (reset to initial state)
takt /refresh-builtin Overwrite builtin agents/workflows with latest version
takt /eject Copy builtin workflow/agents to ~/.takt/ for customization
takt /eject {name} Eject a specific builtin workflow
takt /help Show this help
Examples:
@ -34,7 +35,8 @@ Examples:
takt /add-task # Interactive task creation
takt /clear # Clear sessions, start fresh
takt /watch # Watch & auto-execute tasks
takt /refresh-builtin # Update builtin resources
takt /eject # List available builtins
takt /eject default # Eject default workflow for customization
takt /list-tasks # List & merge task branches
takt /switch
takt /run-tasks

View File

@ -6,6 +6,7 @@ export { executeWorkflow, type WorkflowExecutionResult, type WorkflowExecutionOp
export { executeTask, runAllTasks } from './taskExecution.js';
export { addTask } from './addTask.js';
export { refreshBuiltin } from './refreshBuiltin.js';
export { ejectBuiltin } from './eject.js';
export { watchTasks } from './watchTasks.js';
export { showHelp } from './help.js';
export { withAgentSession } from './session.js';

View File

@ -1,43 +1,22 @@
/**
* /refresh-builtin command implementation
* /refresh-builtin command DEPRECATED
*
* Overwrites builtin workflow and agent files in ~/.takt/ with the latest
* embedded resources. Does NOT touch config.yaml or user-added files.
* Builtin resources are now loaded directly from the package bundle.
* Use /eject to copy individual builtins to ~/.takt/ for customization.
*/
import { getGlobalConfigDir } from '../config/paths.js';
import { getLanguage } from '../config/globalConfig.js';
import { forceRefreshLanguageResources } from '../resources/index.js';
import { header, success, info, error } from '../utils/ui.js';
import { createLogger } from '../utils/debug.js';
const log = createLogger('refresh-builtin');
import { warn, info } from '../utils/ui.js';
/**
* Refresh builtin agents and workflows to latest version.
* Show deprecation notice and guide user to /eject.
*/
export async function refreshBuiltin(): Promise<void> {
const globalDir = getGlobalConfigDir();
const lang = getLanguage();
header('Refresh Builtin Resources');
info(`Language: ${lang}`);
info(`Target: ${globalDir}`);
try {
const overwritten = forceRefreshLanguageResources(globalDir, lang);
log.info('Builtin resources refreshed', { count: overwritten.length, lang });
console.log();
success(`${overwritten.length} files refreshed.`);
for (const filePath of overwritten) {
info(`${filePath}`);
}
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
log.error('Failed to refresh builtin resources', { error: message });
error(`Failed to refresh: ${message}`);
}
warn('/refresh-builtin is deprecated.');
console.log();
info('Builtin workflows and agents are now loaded directly from the package.');
info('They no longer need to be copied to ~/.takt/.');
console.log();
info('To customize a builtin, use:');
info(' takt /eject List available builtins');
info(' takt /eject {name} Copy a builtin to ~/.takt/ for editing');
}

View File

@ -1,7 +1,9 @@
/**
* Agent configuration loader
*
* Loads agents from ~/.takt/agents/ directory only.
* Loads agents with user builtin fallback:
* 1. User agents: ~/.takt/agents/*.md
* 2. Builtin agents: resources/global/{lang}/agents/*.md
*/
import { readFileSync, existsSync, readdirSync } from 'node:fs';
@ -10,8 +12,22 @@ import type { CustomAgentConfig } from '../models/types.js';
import {
getGlobalAgentsDir,
getGlobalWorkflowsDir,
getBuiltinAgentsDir,
getBuiltinWorkflowsDir,
isPathSafe,
} from './paths.js';
import { getLanguage } from './globalConfig.js';
/** Get all allowed base directories for agent prompt files */
function getAllowedAgentBases(): string[] {
const lang = getLanguage();
return [
getGlobalAgentsDir(),
getGlobalWorkflowsDir(),
getBuiltinAgentsDir(lang),
getBuiltinWorkflowsDir(lang),
];
}
/** Load agents from markdown files in a directory */
export function loadAgentsFromDir(dirPath: string): CustomAgentConfig[] {
@ -61,19 +77,7 @@ export function loadAgentPrompt(agent: CustomAgentConfig): string {
}
if (agent.promptFile) {
const allowedBases = [
getGlobalAgentsDir(),
getGlobalWorkflowsDir(),
];
let isValid = false;
for (const base of allowedBases) {
if (isPathSafe(base, agent.promptFile)) {
isValid = true;
break;
}
}
const isValid = getAllowedAgentBases().some((base) => isPathSafe(base, agent.promptFile!));
if (!isValid) {
throw new Error(`Agent prompt file path is not allowed: ${agent.promptFile}`);
}
@ -93,19 +97,7 @@ export function loadAgentPrompt(agent: CustomAgentConfig): string {
* Used by workflow engine when agentPath is already resolved.
*/
export function loadAgentPromptFromPath(agentPath: string): string {
const allowedBases = [
getGlobalAgentsDir(),
getGlobalWorkflowsDir(),
];
let isValid = false;
for (const base of allowedBases) {
if (isPathSafe(base, agentPath)) {
isValid = true;
break;
}
}
const isValid = getAllowedAgentBases().some((base) => isPathSafe(base, agentPath));
if (!isValid) {
throw new Error(`Agent prompt file path is not allowed: ${agentPath}`);
}

View File

@ -36,6 +36,7 @@ export function loadGlobalConfig(): GlobalConfig {
logFile: parsed.debug.log_file,
} : undefined,
worktreeDir: parsed.worktree_dir,
disabledBuiltins: parsed.disabled_builtins,
};
}
@ -61,9 +62,22 @@ export function saveGlobalConfig(config: GlobalConfig): void {
if (config.worktreeDir) {
raw.worktree_dir = config.worktreeDir;
}
if (config.disabledBuiltins && config.disabledBuiltins.length > 0) {
raw.disabled_builtins = config.disabledBuiltins;
}
writeFileSync(configPath, stringifyYaml(raw), 'utf-8');
}
/** Get list of disabled builtin names */
export function getDisabledBuiltins(): string[] {
try {
const config = loadGlobalConfig();
return config.disabledBuiltins ?? [];
} catch {
return [];
}
}
/** Get current language setting */
export function getLanguage(): Language {
try {

View File

@ -1,37 +1,32 @@
/**
* Initialization module for first-time setup
*
* Handles language selection and initial resource setup.
* Separated from paths.ts to avoid UI dependencies in utility modules.
* Handles language selection and initial config.yaml creation.
* Builtin agents/workflows are loaded via fallback from resources/
* and no longer copied to ~/.takt/ on setup.
*/
import { existsSync } from 'node:fs';
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import type { Language } from '../models/types.js';
import { DEFAULT_LANGUAGE } from '../constants.js';
import { selectOptionWithDefault } from '../prompt/index.js';
import {
getGlobalConfigDir,
getGlobalAgentsDir,
getGlobalWorkflowsDir,
getGlobalConfigPath,
getGlobalLogsDir,
getProjectConfigDir,
ensureDir,
} from './paths.js';
import {
copyGlobalResourcesToDir,
copyLanguageResourcesToDir,
copyProjectResourcesToDir,
} from '../resources/index.js';
import { copyProjectResourcesToDir, getLanguageResourcesDir } from '../resources/index.js';
import { setLanguage, setProvider } from './globalConfig.js';
/**
* Check if language-specific resources need to be initialized.
* Returns true if agents or workflows directories don't exist.
* Check if initial setup is needed.
* Returns true if config.yaml doesn't exist yet.
*/
export function needsLanguageSetup(): boolean {
const agentsDir = getGlobalAgentsDir();
const workflowsDir = getGlobalWorkflowsDir();
return !existsSync(agentsDir) || !existsSync(workflowsDir);
return !existsSync(getGlobalConfigPath());
}
/**
@ -83,29 +78,32 @@ export async function promptProviderSelection(): Promise<'claude' | 'codex'> {
/**
* Initialize global takt directory structure with language selection.
* If agents/workflows don't exist, prompts user for language preference.
* On first run, creates config.yaml from language template.
* Agents/workflows are NOT copied they are loaded via builtin fallback.
*/
export async function initGlobalDirs(): Promise<void> {
ensureDir(getGlobalConfigDir());
ensureDir(getGlobalLogsDir());
// Check if we need to set up language-specific resources
const needsSetup = needsLanguageSetup();
if (needsSetup) {
// Ask user for language preference
if (needsLanguageSetup()) {
const lang = await promptLanguageSelection();
const provider = await promptProviderSelection();
// Copy language-specific resources (agents, workflows, config.yaml)
copyLanguageResourcesToDir(getGlobalConfigDir(), lang);
// Copy only config.yaml from language resources
copyLanguageConfigYaml(lang);
// Explicitly save the selected language (handles case where config.yaml existed)
setLanguage(lang);
setProvider(provider);
} else {
// Just copy base global resources (won't overwrite existing)
copyGlobalResourcesToDir(getGlobalConfigDir());
}
}
/** Copy config.yaml from language resources to ~/.takt/ (if not already present) */
function copyLanguageConfigYaml(lang: Language): void {
const langDir = getLanguageResourcesDir(lang);
const srcPath = join(langDir, 'config.yaml');
const destPath = getGlobalConfigPath();
if (existsSync(srcPath) && !existsSync(destPath)) {
writeFileSync(destPath, readFileSync(srcPath));
}
}

View File

@ -8,6 +8,8 @@
import { homedir } from 'node:os';
import { join, resolve } from 'node:path';
import { existsSync, mkdirSync } from 'node:fs';
import type { Language } from '../models/types.js';
import { getLanguageResourcesDir } from '../resources/index.js';
/** Get takt global config directory (~/.takt) */
export function getGlobalConfigDir(): string {
@ -34,6 +36,16 @@ export function getGlobalConfigPath(): string {
return join(getGlobalConfigDir(), 'config.yaml');
}
/** Get builtin workflows directory (resources/global/{lang}/workflows) */
export function getBuiltinWorkflowsDir(lang: Language): string {
return join(getLanguageResourcesDir(lang), 'workflows');
}
/** Get builtin agents directory (resources/global/{lang}/agents) */
export function getBuiltinAgentsDir(lang: Language): string {
return join(getLanguageResourcesDir(lang), 'agents');
}
/** Get project takt config directory (.takt in project) */
export function getProjectConfigDir(projectDir: string): string {
return join(resolve(projectDir), '.takt');

View File

@ -1,7 +1,9 @@
/**
* Workflow configuration loader
*
* Loads workflows from ~/.takt/workflows/ directory only.
* Loads workflows with user builtin fallback:
* 1. User workflows: ~/.takt/workflows/{name}.yaml
* 2. Builtin workflows: resources/global/{lang}/workflows/{name}.yaml
*/
import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
@ -9,12 +11,20 @@ import { join, dirname, basename } from 'node:path';
import { parse as parseYaml } from 'yaml';
import { WorkflowConfigRawSchema } from '../models/schemas.js';
import type { WorkflowConfig, WorkflowStep, WorkflowRule, ReportConfig, ReportObjectConfig } from '../models/types.js';
import { getGlobalWorkflowsDir } from './paths.js';
import { getGlobalWorkflowsDir, getBuiltinWorkflowsDir } from './paths.js';
import { getLanguage, getDisabledBuiltins } from './globalConfig.js';
/** Get builtin workflow by name */
export function getBuiltinWorkflow(name: string): WorkflowConfig | null {
// No built-in workflows - all workflows must be defined in ~/.takt/workflows/
void name;
const lang = getLanguage();
const disabled = getDisabledBuiltins();
if (disabled.includes(name)) return null;
const builtinDir = getBuiltinWorkflowsDir(lang);
const yamlPath = join(builtinDir, `${name}.yaml`);
if (existsSync(yamlPath)) {
return loadWorkflowFromFile(yamlPath);
}
return null;
}
@ -231,24 +241,47 @@ export function loadWorkflowFromFile(filePath: string): WorkflowConfig {
}
/**
* Load workflow by name from global directory.
* Looks for ~/.takt/workflows/{name}.yaml
* Load workflow by name.
* Priority: user (~/.takt/workflows/) builtin (resources/global/{lang}/workflows/)
*/
export function loadWorkflow(name: string): WorkflowConfig | null {
// 1. User workflow
const globalWorkflowsDir = getGlobalWorkflowsDir();
const workflowYamlPath = join(globalWorkflowsDir, `${name}.yaml`);
if (existsSync(workflowYamlPath)) {
return loadWorkflowFromFile(workflowYamlPath);
}
return null;
// 2. Builtin fallback
return getBuiltinWorkflow(name);
}
/** Load all workflows with descriptions (for switch command) */
export function loadAllWorkflows(): Map<string, WorkflowConfig> {
const workflows = new Map<string, WorkflowConfig>();
const disabled = getDisabledBuiltins();
// Global workflows (~/.takt/workflows/{name}.yaml)
// 1. Builtin workflows (lower priority — will be overridden by user)
const lang = getLanguage();
const builtinDir = getBuiltinWorkflowsDir(lang);
if (existsSync(builtinDir)) {
for (const entry of readdirSync(builtinDir)) {
if (!entry.endsWith('.yaml') && !entry.endsWith('.yml')) continue;
const entryPath = join(builtinDir, entry);
if (statSync(entryPath).isFile()) {
const workflowName = entry.replace(/\.ya?ml$/, '');
if (disabled.includes(workflowName)) continue;
try {
workflows.set(workflowName, loadWorkflowFromFile(entryPath));
} catch {
// Skip invalid workflows
}
}
}
}
// 2. User workflows (higher priority — overrides builtins)
const globalWorkflowsDir = getGlobalWorkflowsDir();
if (existsSync(globalWorkflowsDir)) {
for (const entry of readdirSync(globalWorkflowsDir)) {
@ -270,22 +303,34 @@ export function loadAllWorkflows(): Map<string, WorkflowConfig> {
return workflows;
}
/** List available workflows from global directory (~/.takt/workflows/) */
/** List available workflows (user + builtin, excluding disabled) */
export function listWorkflows(): string[] {
const workflows = new Set<string>();
const disabled = getDisabledBuiltins();
// 1. Builtin workflows
const lang = getLanguage();
const builtinDir = getBuiltinWorkflowsDir(lang);
scanWorkflowDir(builtinDir, workflows, disabled);
// 2. User workflows
const globalWorkflowsDir = getGlobalWorkflowsDir();
if (existsSync(globalWorkflowsDir)) {
for (const entry of readdirSync(globalWorkflowsDir)) {
if (!entry.endsWith('.yaml') && !entry.endsWith('.yml')) continue;
const entryPath = join(globalWorkflowsDir, entry);
if (statSync(entryPath).isFile()) {
const workflowName = entry.replace(/\.ya?ml$/, '');
workflows.add(workflowName);
}
}
}
scanWorkflowDir(globalWorkflowsDir, workflows);
return Array.from(workflows).sort();
}
/** Scan a directory for .yaml/.yml files and add names to the set */
function scanWorkflowDir(dir: string, target: Set<string>, disabled?: string[]): void {
if (!existsSync(dir)) return;
for (const entry of readdirSync(dir)) {
if (!entry.endsWith('.yaml') && !entry.endsWith('.yml')) continue;
const entryPath = join(dir, entry);
if (statSync(entryPath).isFile()) {
const workflowName = entry.replace(/\.ya?ml$/, '');
if (disabled?.includes(workflowName)) continue;
target.add(workflowName);
}
}
}

View File

@ -170,6 +170,8 @@ export const GlobalConfigSchema = z.object({
debug: DebugConfigSchema.optional(),
/** Directory for shared clones (worktree_dir in config). If empty, uses ../{clone-name} relative to project */
worktree_dir: z.string().optional(),
/** List of builtin workflow/agent names to exclude from fallback loading */
disabled_builtins: z.array(z.string()).optional().default([]),
});
/** Project config schema */

View File

@ -189,6 +189,8 @@ export interface GlobalConfig {
debug?: DebugConfig;
/** Directory for shared clones (worktree_dir in config). If empty, uses ../{clone-name} relative to project */
worktreeDir?: string;
/** List of builtin workflow/agent names to exclude from fallback loading */
disabledBuiltins?: string[];
}
/** Project-level configuration */

View File

@ -3,9 +3,9 @@
*
* Contains default workflow definitions and resource paths.
* Resources are organized into:
* - resources/global/ - Files to copy to ~/.takt
* - resources/global/en/ - English resources
* - resources/global/ja/ - Japanese resources
* - resources/global/{lang}/workflows/ - Builtin workflows (loaded via fallback)
* - resources/global/{lang}/agents/ - Builtin agents (loaded via fallback)
* - resources/project/ - Project-level template files (.gitignore)
*/
import { readFileSync, readdirSync, existsSync, statSync, mkdirSync, writeFileSync } from 'fs';
@ -44,20 +44,6 @@ export function getLanguageResourcesDir(lang: Language): string {
return join(getGlobalResourcesDir(), lang);
}
/**
* Copy global resources directory to ~/.takt.
* Only copies files that don't exist in target.
* Skips language-specific directories (en/, ja/) which are handled by copyLanguageResourcesToDir.
*/
export function copyGlobalResourcesToDir(targetDir: string): void {
const resourcesDir = getGlobalResourcesDir();
if (!existsSync(resourcesDir)) {
return;
}
// Skip language directories (they are handled by copyLanguageResourcesToDir)
copyDirRecursive(resourcesDir, targetDir, { skipDirs: ['en', 'ja'] });
}
/**
* Copy project resources directory to .takt in project.
* Only copies files that don't exist in target (e.g., .gitignore).
@ -72,72 +58,6 @@ export function copyProjectResourcesToDir(targetDir: string): void {
});
}
/**
* Copy language-specific resources (agents and workflows) to ~/.takt.
* Copies from resources/global/{lang}/agents to ~/.takt/agents
* and resources/global/{lang}/workflows to ~/.takt/workflows.
* Also copies config.yaml from language directory.
* @throws Error if language directory doesn't exist
*/
export function copyLanguageResourcesToDir(targetDir: string, lang: Language): void {
const langDir = getLanguageResourcesDir(lang);
if (!existsSync(langDir)) {
throw new Error(`Language resources not found: ${langDir}`);
}
// Copy agents directory
const langAgentsDir = join(langDir, 'agents');
const targetAgentsDir = join(targetDir, 'agents');
if (existsSync(langAgentsDir)) {
copyDirRecursive(langAgentsDir, targetAgentsDir);
}
// Copy workflows directory
const langWorkflowsDir = join(langDir, 'workflows');
const targetWorkflowsDir = join(targetDir, 'workflows');
if (existsSync(langWorkflowsDir)) {
copyDirRecursive(langWorkflowsDir, targetWorkflowsDir);
}
// Copy config.yaml if exists
const langConfigPath = join(langDir, 'config.yaml');
const targetConfigPath = join(targetDir, 'config.yaml');
if (existsSync(langConfigPath) && !existsSync(targetConfigPath)) {
const content = readFileSync(langConfigPath);
writeFileSync(targetConfigPath, content);
}
}
/**
* Force-refresh language-specific resources (agents and workflows) to ~/.takt.
* Overwrites existing builtin files. Does NOT touch config.yaml.
*/
export function forceRefreshLanguageResources(targetDir: string, lang: Language): string[] {
const langDir = getLanguageResourcesDir(lang);
if (!existsSync(langDir)) {
throw new Error(`Language resources not found: ${langDir}`);
}
const copiedFiles: string[] = [];
const forceOptions = { overwrite: true, copiedFiles };
// Overwrite agents directory
const langAgentsDir = join(langDir, 'agents');
const targetAgentsDir = join(targetDir, 'agents');
if (existsSync(langAgentsDir)) {
copyDirRecursive(langAgentsDir, targetAgentsDir, forceOptions);
}
// Overwrite workflows directory
const langWorkflowsDir = join(langDir, 'workflows');
const targetWorkflowsDir = join(targetDir, 'workflows');
if (existsSync(langWorkflowsDir)) {
copyDirRecursive(langWorkflowsDir, targetWorkflowsDir, forceOptions);
}
return copiedFiles;
}
/** Files to skip during resource copy (OS-generated files) */
const SKIP_FILES = ['.DS_Store', 'Thumbs.db'];