.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
|
// Import after mocks are set up
|
||||||
const { needsLanguageSetup } = await import('../config/initialization.js');
|
const { needsLanguageSetup } = await import('../config/initialization.js');
|
||||||
const { getGlobalAgentsDir, getGlobalWorkflowsDir } = await import('../config/paths.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', () => {
|
describe('initialization', () => {
|
||||||
beforeEach(() => {
|
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', () => {
|
describe('getLanguageResourcesDir', () => {
|
||||||
it('should return correct path for English', () => {
|
it('should return correct path for English', () => {
|
||||||
const path = getLanguageResourcesDir('en');
|
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 { mkdirSync, writeFileSync, existsSync, rmSync, readFileSync, readdirSync } from 'node:fs';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { TaskRunner } from '../task/runner.js';
|
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', () => {
|
describe('TaskRunner', () => {
|
||||||
const testDir = `/tmp/takt-task-test-${Date.now()}`;
|
const testDir = `/tmp/takt-task-test-${Date.now()}`;
|
||||||
|
|||||||
@ -67,7 +67,9 @@ export function copyProjectResourcesToDir(targetDir: string): void {
|
|||||||
if (!existsSync(resourcesDir)) {
|
if (!existsSync(resourcesDir)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
copyDirRecursive(resourcesDir, targetDir);
|
copyDirRecursive(resourcesDir, targetDir, {
|
||||||
|
renameMap: { dotgitignore: '.gitignore' },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -146,6 +148,8 @@ interface CopyOptions {
|
|||||||
overwrite?: boolean;
|
overwrite?: boolean;
|
||||||
/** Collect copied file paths into this array */
|
/** Collect copied file paths into this array */
|
||||||
copiedFiles?: string[];
|
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.
|
* If true, overwrites existing files.
|
||||||
*/
|
*/
|
||||||
function copyDirRecursive(srcDir: string, destDir: string, options: CopyOptions = {}): void {
|
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)) {
|
if (!existsSync(destDir)) {
|
||||||
mkdirSync(destDir, { recursive: true });
|
mkdirSync(destDir, { recursive: true });
|
||||||
@ -165,7 +169,8 @@ function copyDirRecursive(srcDir: string, destDir: string, options: CopyOptions
|
|||||||
if (skipDirs.includes(entry)) continue;
|
if (skipDirs.includes(entry)) continue;
|
||||||
|
|
||||||
const srcPath = join(srcDir, entry);
|
const srcPath = join(srcDir, entry);
|
||||||
const destPath = join(destDir, entry);
|
const destName = renameMap?.[entry] ?? entry;
|
||||||
|
const destPath = join(destDir, destName);
|
||||||
const stat = statSync(srcPath);
|
const stat = statSync(srcPath);
|
||||||
|
|
||||||
if (stat.isDirectory()) {
|
if (stat.isDirectory()) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user