.takt/tasks/ に TASK-FORMAT ドキュメントを配置、dotgitignore リネーム対応
This commit is contained in:
parent
a4e793c070
commit
73c2d6b381
37
resources/project/tasks/TASK-FORMAT
Normal file
37
resources/project/tasks/TASK-FORMAT
Normal file
@ -0,0 +1,37 @@
|
||||
TAKT Task File Format
|
||||
=====================
|
||||
|
||||
Tasks placed in this directory (.takt/tasks/) will be processed by TAKT.
|
||||
|
||||
## YAML Format (Recommended)
|
||||
|
||||
# .takt/tasks/my-task.yaml
|
||||
task: "Task description"
|
||||
worktree: true # (optional) true | "/path/to/dir"
|
||||
branch: "feat/my-feature" # (optional) branch name
|
||||
workflow: "default" # (optional) workflow name
|
||||
|
||||
Fields:
|
||||
task (required) Task description (string)
|
||||
worktree (optional) true: create shared clone, "/path": clone at path
|
||||
branch (optional) Branch name (auto-generated if omitted: takt/{timestamp}-{slug})
|
||||
workflow (optional) Workflow name (uses current workflow if omitted)
|
||||
|
||||
## Markdown Format (Simple)
|
||||
|
||||
# .takt/tasks/my-task.md
|
||||
|
||||
Entire file content becomes the task description.
|
||||
Supports multiline. No structured options available.
|
||||
|
||||
## Supported Extensions
|
||||
|
||||
.yaml, .yml -> YAML format (parsed and validated)
|
||||
.md -> Markdown format (plain text, backward compatible)
|
||||
|
||||
## Commands
|
||||
|
||||
takt /add-task Add a task interactively
|
||||
takt /run-tasks Run all pending tasks
|
||||
takt /watch Watch and auto-run tasks
|
||||
takt /list-tasks List task branches (merge/delete)
|
||||
@ -27,7 +27,7 @@ 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, getLanguageResourcesDir } = await import('../resources/index.js');
|
||||
const { copyLanguageResourcesToDir, copyProjectResourcesToDir, getLanguageResourcesDir, getProjectResourcesDir } = await import('../resources/index.js');
|
||||
|
||||
describe('initialization', () => {
|
||||
beforeEach(() => {
|
||||
@ -87,6 +87,43 @@ describe('initialization', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('copyProjectResourcesToDir', () => {
|
||||
const testProjectDir = join(tmpdir(), `takt-project-test-${Date.now()}`);
|
||||
|
||||
beforeEach(() => {
|
||||
mkdirSync(testProjectDir, { recursive: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (existsSync(testProjectDir)) {
|
||||
rmSync(testProjectDir, { recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('should rename dotgitignore to .gitignore during copy', () => {
|
||||
const resourcesDir = getProjectResourcesDir();
|
||||
if (!existsSync(join(resourcesDir, 'dotgitignore'))) {
|
||||
return; // Skip if resource file doesn't exist
|
||||
}
|
||||
|
||||
copyProjectResourcesToDir(testProjectDir);
|
||||
|
||||
expect(existsSync(join(testProjectDir, '.gitignore'))).toBe(true);
|
||||
expect(existsSync(join(testProjectDir, 'dotgitignore'))).toBe(false);
|
||||
});
|
||||
|
||||
it('should copy tasks/TASK-FORMAT to target directory', () => {
|
||||
const resourcesDir = getProjectResourcesDir();
|
||||
if (!existsSync(join(resourcesDir, 'tasks', 'TASK-FORMAT'))) {
|
||||
return; // Skip if resource file doesn't exist
|
||||
}
|
||||
|
||||
copyProjectResourcesToDir(testProjectDir);
|
||||
|
||||
expect(existsSync(join(testProjectDir, 'tasks', 'TASK-FORMAT'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLanguageResourcesDir', () => {
|
||||
it('should return correct path for English', () => {
|
||||
const path = getLanguageResourcesDir('en');
|
||||
|
||||
@ -6,6 +6,52 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { mkdirSync, writeFileSync, existsSync, rmSync, readFileSync, readdirSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { TaskRunner } from '../task/runner.js';
|
||||
import { isTaskFile, parseTaskFiles } from '../task/parser.js';
|
||||
|
||||
describe('isTaskFile', () => {
|
||||
it('should accept .yaml files', () => {
|
||||
expect(isTaskFile('task.yaml')).toBe(true);
|
||||
});
|
||||
|
||||
it('should accept .yml files', () => {
|
||||
expect(isTaskFile('task.yml')).toBe(true);
|
||||
});
|
||||
|
||||
it('should accept .md files', () => {
|
||||
expect(isTaskFile('task.md')).toBe(true);
|
||||
});
|
||||
|
||||
it('should reject extensionless files like TASK-FORMAT', () => {
|
||||
expect(isTaskFile('TASK-FORMAT')).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject .txt files', () => {
|
||||
expect(isTaskFile('readme.txt')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseTaskFiles', () => {
|
||||
const testDir = `/tmp/takt-parse-test-${Date.now()}`;
|
||||
|
||||
beforeEach(() => {
|
||||
mkdirSync(testDir, { recursive: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (existsSync(testDir)) {
|
||||
rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('should ignore extensionless files like TASK-FORMAT', () => {
|
||||
writeFileSync(join(testDir, 'TASK-FORMAT'), 'Format documentation');
|
||||
writeFileSync(join(testDir, 'real-task.md'), 'Real task');
|
||||
|
||||
const tasks = parseTaskFiles(testDir);
|
||||
expect(tasks).toHaveLength(1);
|
||||
expect(tasks[0]?.name).toBe('real-task');
|
||||
});
|
||||
});
|
||||
|
||||
describe('TaskRunner', () => {
|
||||
const testDir = `/tmp/takt-task-test-${Date.now()}`;
|
||||
|
||||
@ -67,7 +67,9 @@ export function copyProjectResourcesToDir(targetDir: string): void {
|
||||
if (!existsSync(resourcesDir)) {
|
||||
return;
|
||||
}
|
||||
copyDirRecursive(resourcesDir, targetDir);
|
||||
copyDirRecursive(resourcesDir, targetDir, {
|
||||
renameMap: { dotgitignore: '.gitignore' },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -146,6 +148,8 @@ interface CopyOptions {
|
||||
overwrite?: boolean;
|
||||
/** Collect copied file paths into this array */
|
||||
copiedFiles?: string[];
|
||||
/** Rename files during copy (source name → dest name) */
|
||||
renameMap?: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -154,7 +158,7 @@ interface CopyOptions {
|
||||
* If true, overwrites existing files.
|
||||
*/
|
||||
function copyDirRecursive(srcDir: string, destDir: string, options: CopyOptions = {}): void {
|
||||
const { skipDirs = [], overwrite = false, copiedFiles } = options;
|
||||
const { skipDirs = [], overwrite = false, copiedFiles, renameMap } = options;
|
||||
|
||||
if (!existsSync(destDir)) {
|
||||
mkdirSync(destDir, { recursive: true });
|
||||
@ -165,7 +169,8 @@ function copyDirRecursive(srcDir: string, destDir: string, options: CopyOptions
|
||||
if (skipDirs.includes(entry)) continue;
|
||||
|
||||
const srcPath = join(srcDir, entry);
|
||||
const destPath = join(destDir, entry);
|
||||
const destName = renameMap?.[entry] ?? entry;
|
||||
const destPath = join(destDir, destName);
|
||||
const stat = statSync(srcPath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user