@tmddev/tmd 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,179 @@
1
+ import { existsSync, readdirSync, readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import chalk from 'chalk';
4
+ import yaml from 'js-yaml';
5
+ import { getPlanDir, getDoDir, getCheckDir, getActDir, getSchemasDir, getTmdDir, } from '../utils/paths.js';
6
+ import { getTaskStatus } from '../utils/task-status.js';
7
+ function getBase() {
8
+ return process.cwd();
9
+ }
10
+ function validateTask(taskId, base, strict) {
11
+ const issues = [];
12
+ const planPath = join(base, getPlanDir(taskId), 'plan.md');
13
+ if (!existsSync(planPath)) {
14
+ issues.push({ level: 'error', path: 'plan.md', message: 'plan.md not found' });
15
+ return { taskId, valid: false, issues };
16
+ }
17
+ const required = ['Why', 'Goals', 'Tasks'];
18
+ const content = readFileSync(planPath, 'utf-8');
19
+ for (const h of required) {
20
+ if (!content.includes(`## ${h}`) && !content.includes(`##${h}`)) {
21
+ issues.push({ level: 'error', path: 'plan.md', message: `Missing required section: ## ${h}` });
22
+ }
23
+ }
24
+ const status = getTaskStatus(taskId);
25
+ if (status !== 'planning') {
26
+ const doPath = join(base, getDoDir(taskId), 'execution.md');
27
+ if (!existsSync(doPath)) {
28
+ issues.push({ level: 'warning', path: 'do/execution.md', message: 'execution.md missing for non-planning status' });
29
+ }
30
+ }
31
+ if (status === 'checking' || status === 'acting' || status === 'completed') {
32
+ const checkPath = join(base, getCheckDir(taskId), 'evaluation.md');
33
+ if (!existsSync(checkPath)) {
34
+ issues.push({ level: 'warning', path: 'check/evaluation.md', message: 'evaluation.md missing' });
35
+ }
36
+ }
37
+ if (status === 'acting' || status === 'completed') {
38
+ const actPath = join(base, getActDir(taskId), 'improvement.md');
39
+ if (!existsSync(actPath)) {
40
+ issues.push({ level: 'warning', path: 'act/improvement.md', message: 'improvement.md missing' });
41
+ }
42
+ }
43
+ if (strict) {
44
+ const tasksMatch = content.match(/##\s*Tasks[\s\S]*?(?=##|$)/i);
45
+ if (tasksMatch) {
46
+ const block = tasksMatch[0];
47
+ const lines = block.split('\n').filter((l) => /^\s*-\s+\[\s?[x ]?\s?]\s+.+/.test(l) || /^\s*-\s+.+/.test(l));
48
+ if (lines.length === 0) {
49
+ issues.push({ level: 'warning', path: 'plan.md', message: 'Tasks section has no checklist items' });
50
+ }
51
+ }
52
+ }
53
+ const hasError = issues.some((i) => i.level === 'error');
54
+ return { taskId, valid: !hasError, issues };
55
+ }
56
+ function validateSchema(name, base) {
57
+ const issues = [];
58
+ const schemaDir = join(base, getSchemasDir(), name);
59
+ const schemaPath = join(schemaDir, 'schema.yaml');
60
+ if (!existsSync(schemaPath)) {
61
+ issues.push({ level: 'error', path: 'schema.yaml', message: 'schema.yaml not found' });
62
+ return { name, valid: false, issues };
63
+ }
64
+ let data;
65
+ try {
66
+ data = yaml.load(readFileSync(schemaPath, 'utf-8'));
67
+ }
68
+ catch (e) {
69
+ issues.push({ level: 'error', path: 'schema.yaml', message: `Invalid YAML: ${e.message}` });
70
+ return { name, valid: false, issues };
71
+ }
72
+ if (data == null || typeof data !== 'object') {
73
+ issues.push({ level: 'error', path: 'schema.yaml', message: 'schema must be an object' });
74
+ return { name, valid: false, issues };
75
+ }
76
+ const obj = data;
77
+ if (!obj['name'] || typeof obj['name'] !== 'string') {
78
+ issues.push({ level: 'error', path: 'schema.yaml', message: 'Missing or invalid "name"' });
79
+ }
80
+ if (!obj['artifacts'] || !Array.isArray(obj['artifacts'])) {
81
+ issues.push({ level: 'error', path: 'schema.yaml', message: 'Missing or invalid "artifacts" array' });
82
+ }
83
+ else {
84
+ const templatesDir = join(schemaDir, 'templates');
85
+ for (const a of obj['artifacts']) {
86
+ const t = a.template;
87
+ if (t && existsSync(templatesDir)) {
88
+ const tPath = join(templatesDir, t);
89
+ if (!existsSync(tPath)) {
90
+ issues.push({ level: 'warning', path: `templates/${t}`, message: `Template not found: ${t}` });
91
+ }
92
+ }
93
+ }
94
+ }
95
+ const hasError = issues.some((i) => i.level === 'error');
96
+ return { name, valid: !hasError, issues };
97
+ }
98
+ function gatherTaskIds(base) {
99
+ const planDir = join(base, getTmdDir(), 'plan');
100
+ if (!existsSync(planDir))
101
+ return [];
102
+ return readdirSync(planDir, { withFileTypes: true })
103
+ .filter((d) => d.isDirectory())
104
+ .map((d) => d.name);
105
+ }
106
+ function gatherSchemaNames(base) {
107
+ const schemasDir = join(base, getSchemasDir());
108
+ if (!existsSync(schemasDir))
109
+ return [];
110
+ return readdirSync(schemasDir, { withFileTypes: true })
111
+ .filter((d) => d.isDirectory() && existsSync(join(schemasDir, d.name, 'schema.yaml')))
112
+ .map((d) => d.name);
113
+ }
114
+ export function validateCommand(taskId, opts) {
115
+ const base = getBase();
116
+ const options = opts ?? {};
117
+ const { schemas: schemasArg, all, strict = false, json: asJson } = options;
118
+ const taskResults = [];
119
+ const schemaResults = [];
120
+ if (all || taskId !== undefined) {
121
+ const ids = all ? gatherTaskIds(base) : (taskId ? [taskId] : []);
122
+ for (const id of ids) {
123
+ taskResults.push(validateTask(id, base, strict));
124
+ }
125
+ if (taskId && ids.length === 0) {
126
+ taskResults.push({ taskId: taskId, valid: false, issues: [{ level: 'error', path: 'plan', message: 'Task not found' }] });
127
+ }
128
+ }
129
+ if (all || schemasArg !== undefined) {
130
+ const names = schemasArg !== undefined
131
+ ? (typeof schemasArg === 'string' && schemasArg.length > 0 ? [schemasArg] : gatherSchemaNames(base))
132
+ : all
133
+ ? gatherSchemaNames(base)
134
+ : [];
135
+ for (const n of names) {
136
+ schemaResults.push(validateSchema(n, base));
137
+ }
138
+ if (schemasArg && schemaResults.length === 0 && !all) {
139
+ schemaResults.push({
140
+ name: schemasArg,
141
+ valid: false,
142
+ issues: [{ level: 'error', path: 'schemas', message: `Schema not found: ${schemasArg}` }],
143
+ });
144
+ }
145
+ }
146
+ if (taskResults.length === 0 && schemaResults.length === 0) {
147
+ if (asJson) {
148
+ console.log(JSON.stringify({ valid: true, tasks: [], schemas: [], message: 'Nothing to validate' }));
149
+ }
150
+ else {
151
+ console.log(chalk.yellow('Nothing to validate. Use: tmd validate <task-id>, --schemas [name], or --all'));
152
+ }
153
+ return;
154
+ }
155
+ const anyInvalid = [...taskResults, ...schemaResults].some((r) => !r.valid);
156
+ if (asJson) {
157
+ console.log(JSON.stringify({ valid: !anyInvalid, tasks: taskResults, schemas: schemaResults }));
158
+ process.exitCode = anyInvalid ? 1 : 0;
159
+ return;
160
+ }
161
+ console.log(chalk.bold('\nValidation'));
162
+ console.log('─'.repeat(60));
163
+ for (const r of taskResults) {
164
+ const badge = r.valid ? chalk.green('✓') : chalk.red('✗');
165
+ console.log(` ${badge} ${r.taskId}`);
166
+ for (const i of r.issues) {
167
+ console.log(` ${i.level}: ${i.message}`);
168
+ }
169
+ }
170
+ for (const r of schemaResults) {
171
+ const badge = r.valid ? chalk.green('✓') : chalk.red('✗');
172
+ console.log(` ${badge} schema: ${r.name}`);
173
+ for (const i of r.issues) {
174
+ console.log(` ${i.level}: ${i.message}`);
175
+ }
176
+ }
177
+ process.exitCode = anyInvalid ? 1 : 0;
178
+ }
179
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1,2 @@
1
+ export declare function viewCommand(): void;
2
+ //# sourceMappingURL=view.d.ts.map
@@ -0,0 +1,65 @@
1
+ import { existsSync, readdirSync } from 'fs';
2
+ import { join } from 'path';
3
+ import chalk from 'chalk';
4
+ import { getTmdDir } from '../utils/paths.js';
5
+ import { getTaskStatus } from '../utils/task-status.js';
6
+ import { getTaskMetadata } from '../utils/task-status.js';
7
+ export function viewCommand() {
8
+ const tmdDir = getTmdDir();
9
+ const planDir = join(tmdDir, 'plan');
10
+ if (!existsSync(planDir)) {
11
+ console.log(chalk.bold('\nTMD Dashboard\n'));
12
+ console.log('═'.repeat(60));
13
+ console.log(chalk.dim('No tasks found. Create one with: tmd plan "description"'));
14
+ console.log('═'.repeat(60));
15
+ return;
16
+ }
17
+ const taskIds = readdirSync(planDir, { withFileTypes: true })
18
+ .filter((d) => d.isDirectory())
19
+ .map((d) => d.name);
20
+ const byStatus = {
21
+ planning: [],
22
+ doing: [],
23
+ checking: [],
24
+ acting: [],
25
+ completed: [],
26
+ };
27
+ for (const id of taskIds) {
28
+ const s = getTaskStatus(id);
29
+ (byStatus[s] ??= []).push(id);
30
+ }
31
+ console.log(chalk.bold('\nTMD Dashboard\n'));
32
+ console.log('═'.repeat(60));
33
+ console.log(chalk.bold('Summary'));
34
+ console.log('─'.repeat(60));
35
+ console.log(` planning: ${String(byStatus['planning']?.length ?? 0)}`);
36
+ console.log(` doing: ${String(byStatus['doing']?.length ?? 0)}`);
37
+ console.log(` checking: ${String(byStatus['checking']?.length ?? 0)}`);
38
+ console.log(` acting: ${String(byStatus['acting']?.length ?? 0)}`);
39
+ console.log(` completed: ${String(byStatus['completed']?.length ?? 0)}`);
40
+ console.log('─'.repeat(60));
41
+ console.log(chalk.bold('Tasks'));
42
+ console.log('─'.repeat(60));
43
+ if (taskIds.length === 0) {
44
+ console.log(chalk.dim(' No tasks. Create one with: tmd plan "description"'));
45
+ }
46
+ else {
47
+ for (const id of taskIds) {
48
+ const status = getTaskStatus(id);
49
+ const c = status === 'completed'
50
+ ? chalk.green
51
+ : status === 'acting'
52
+ ? chalk.blue
53
+ : status === 'checking'
54
+ ? chalk.yellow
55
+ : status === 'doing'
56
+ ? chalk.cyan
57
+ : chalk.gray;
58
+ const meta = getTaskMetadata(id);
59
+ const ext = meta.openspecChange ? ` ${chalk.dim(`[${meta.openspecChange}]`)}` : '';
60
+ console.log(` ${chalk.cyan(id)} ${c(status)}${ext}`);
61
+ }
62
+ }
63
+ console.log('═'.repeat(60));
64
+ }
65
+ //# sourceMappingURL=view.js.map
Binary file
package/dist/types.d.ts CHANGED
@@ -34,6 +34,15 @@ export interface ListCommandOptions {
34
34
  }
35
35
  export interface SkillsCommandOptions {
36
36
  input?: string;
37
+ skill?: string;
38
+ description?: string;
39
+ tags?: string;
40
+ tag?: string[];
41
+ force?: boolean;
42
+ all?: boolean;
43
+ github?: boolean;
44
+ branch?: string;
45
+ path?: string;
37
46
  }
38
47
  export interface TaskMetadata {
39
48
  status?: string;
@@ -48,8 +57,9 @@ export interface SkillMetadata {
48
57
  version?: string;
49
58
  description?: string;
50
59
  tags?: string[];
51
- source?: 'skillssh' | 'local';
60
+ source?: 'skillssh' | 'local' | 'github';
52
61
  repo?: string;
62
+ branch?: string;
53
63
  importedAt?: string;
54
64
  createdFrom?: string;
55
65
  interface?: SkillInterface;
@@ -76,4 +86,14 @@ export interface SkillStep {
76
86
  depends?: string[] | number[];
77
87
  [key: string]: unknown;
78
88
  }
89
+ export interface PipelineInitOptions {
90
+ force?: boolean;
91
+ }
92
+ export interface PipelineRunOptions {
93
+ from?: string;
94
+ dryRun?: boolean;
95
+ }
96
+ export interface PipelineStatusOptions {
97
+ json?: boolean;
98
+ }
79
99
  //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,18 @@
1
+ interface GitHubImportResult {
2
+ success: boolean;
3
+ skillName?: string;
4
+ skillDir?: string;
5
+ error?: string;
6
+ }
7
+ interface GitHubImportOptions {
8
+ skillName?: string | undefined;
9
+ branch?: string | undefined;
10
+ path?: string | undefined;
11
+ force?: boolean | undefined;
12
+ }
13
+ /**
14
+ * Fetches and converts a skill from a GitHub repository to TMD format
15
+ */
16
+ export declare function addSkillFromGitHub(ownerRepo: string, options?: GitHubImportOptions): Promise<GitHubImportResult>;
17
+ export {};
18
+ //# sourceMappingURL=github.d.ts.map
@@ -0,0 +1,165 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { getSkillsDir, getSkillDir } from './paths.js';
4
+ import yaml from 'js-yaml';
5
+ /**
6
+ * Fetches and converts a skill from a GitHub repository to TMD format
7
+ */
8
+ export async function addSkillFromGitHub(ownerRepo, options) {
9
+ // Validate owner/repo format
10
+ const parts = ownerRepo.split('/');
11
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
12
+ return {
13
+ success: false,
14
+ error: `Invalid format: "${ownerRepo}". Expected: <owner>/<repo>`
15
+ };
16
+ }
17
+ const [owner, repo] = parts;
18
+ // Use provided skill name or derive from repo
19
+ const finalSkillName = options?.skillName ?? repo;
20
+ // Check if skill already exists
21
+ const skillDir = getSkillDir(finalSkillName);
22
+ if (existsSync(skillDir) && !options?.force) {
23
+ return {
24
+ success: false,
25
+ error: `Skill already exists: ${finalSkillName}. Use --force to overwrite.`
26
+ };
27
+ }
28
+ // Fetch skill content from GitHub
29
+ const skillContent = await fetchGitHubSkillContent(owner, repo, options);
30
+ if (!skillContent.success) {
31
+ return {
32
+ success: false,
33
+ error: skillContent.error ?? 'Failed to fetch skill'
34
+ };
35
+ }
36
+ // Create skill directory
37
+ const skillsDir = getSkillsDir();
38
+ if (!existsSync(skillsDir)) {
39
+ mkdirSync(skillsDir, { recursive: true });
40
+ }
41
+ // Remove existing if force is true
42
+ if (existsSync(skillDir) && options?.force) {
43
+ const { rmSync } = await import('fs');
44
+ rmSync(skillDir, { recursive: true });
45
+ }
46
+ mkdirSync(skillDir, { recursive: true });
47
+ // Convert and save skill files
48
+ saveGitHubSkillFiles(skillDir, finalSkillName, ownerRepo, skillContent.content ?? '', skillContent.branch ?? 'main');
49
+ return {
50
+ success: true,
51
+ skillName: finalSkillName,
52
+ skillDir
53
+ };
54
+ }
55
+ async function fetchGitHubSkillContent(owner, repo, options) {
56
+ const branches = options?.branch ? [options.branch] : ['main', 'master'];
57
+ const possiblePaths = [];
58
+ // If custom path is provided, use it directly
59
+ if (options?.path) {
60
+ for (const branch of branches) {
61
+ possiblePaths.push({
62
+ url: `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${options.path}`,
63
+ branch
64
+ });
65
+ }
66
+ }
67
+ else {
68
+ // Build possible paths based on conventions
69
+ for (const branch of branches) {
70
+ // If skill name is provided, try skills subdirectory
71
+ if (options?.skillName) {
72
+ possiblePaths.push({ url: `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/skills/${options.skillName}/SKILL.md`, branch }, { url: `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/skills/${options.skillName}/skill.md`, branch }, { url: `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/skills/${options.skillName}/README.md`, branch });
73
+ }
74
+ // Try root-level paths
75
+ possiblePaths.push({ url: `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/SKILL.md`, branch }, { url: `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/skill.md`, branch }, { url: `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/README.md`, branch });
76
+ }
77
+ }
78
+ for (const { url, branch } of possiblePaths) {
79
+ try {
80
+ const response = await fetch(url);
81
+ if (response.ok) {
82
+ const content = await response.text();
83
+ return { success: true, content, branch };
84
+ }
85
+ }
86
+ catch {
87
+ // Continue to next URL
88
+ }
89
+ }
90
+ const skillHint = options?.skillName ? ` (skill: ${options.skillName})` : '';
91
+ const pathHint = options?.path ? ` (path: ${options.path})` : '';
92
+ return {
93
+ success: false,
94
+ error: `Could not find skill in ${owner}/${repo}${skillHint}${pathHint}. Check that the repository exists and is public.`
95
+ };
96
+ }
97
+ function saveGitHubSkillFiles(skillDir, skillName, ownerRepo, content, branch) {
98
+ // Parse frontmatter if present
99
+ const { frontmatter, body } = parseFrontmatter(content);
100
+ // Extract name and description from frontmatter or use defaults
101
+ const skillNameFromFrontmatter = typeof frontmatter['name'] === 'string' ? frontmatter['name'] : skillName;
102
+ const skillDescription = typeof frontmatter['description'] === 'string'
103
+ ? frontmatter['description']
104
+ : `Skill imported from GitHub: ${ownerRepo}`;
105
+ // Create SKILL.md with frontmatter
106
+ const skillMd = `---
107
+ name: ${skillNameFromFrontmatter}
108
+ description: ${skillDescription}
109
+ ---
110
+
111
+ ${body || 'No description available.'}
112
+ `;
113
+ writeFileSync(join(skillDir, 'SKILL.md'), skillMd);
114
+ // Create metadata.yaml with source attribution
115
+ const version = typeof frontmatter['version'] === 'string' ? frontmatter['version'] : '1.0.0';
116
+ const description = typeof frontmatter['description'] === 'string'
117
+ ? frontmatter['description']
118
+ : `Skill imported from GitHub: ${ownerRepo}`;
119
+ const tags = Array.isArray(frontmatter['tags']) ? frontmatter['tags'] : [];
120
+ const metadata = {
121
+ name: skillNameFromFrontmatter,
122
+ version,
123
+ description,
124
+ tags,
125
+ source: 'github',
126
+ repo: ownerRepo,
127
+ branch,
128
+ importedAt: new Date().toISOString(),
129
+ interface: {
130
+ input: [],
131
+ output: {}
132
+ }
133
+ };
134
+ writeFileSync(join(skillDir, 'metadata.yaml'), yaml.dump(metadata));
135
+ // Create config.yaml
136
+ const config = {
137
+ // Default configuration
138
+ };
139
+ writeFileSync(join(skillDir, 'config.yaml'), yaml.dump(config));
140
+ // Create steps.yaml placeholder
141
+ const steps = {
142
+ steps: [
143
+ {
144
+ type: 'procedural',
145
+ description: 'This skill contains procedural knowledge. See SKILL.md for details.'
146
+ }
147
+ ]
148
+ };
149
+ writeFileSync(join(skillDir, 'steps.yaml'), yaml.dump(steps));
150
+ }
151
+ function parseFrontmatter(content) {
152
+ const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
153
+ const match = content.match(frontmatterRegex);
154
+ if (match?.[1] && match[2]) {
155
+ try {
156
+ const frontmatter = yaml.load(match[1]);
157
+ return { frontmatter: frontmatter ?? {}, body: match[2].trim() };
158
+ }
159
+ catch {
160
+ return { frontmatter: {}, body: content };
161
+ }
162
+ }
163
+ return { frontmatter: {}, body: content };
164
+ }
165
+ //# sourceMappingURL=github.js.map
@@ -7,4 +7,5 @@ export declare function getCheckDir(taskId: string): string;
7
7
  export declare function getActDir(taskId: string): string;
8
8
  export declare function getSkillsDir(): string;
9
9
  export declare function getSkillDir(skillName: string): string;
10
+ export declare function getSchemasDir(): string;
10
11
  //# sourceMappingURL=paths.d.ts.map
@@ -30,4 +30,8 @@ export function getSkillsDir() {
30
30
  export function getSkillDir(skillName) {
31
31
  return join(getSkillsDir(), skillName);
32
32
  }
33
+ const SCHEMAS_DIR = 'schemas';
34
+ export function getSchemasDir() {
35
+ return SCHEMAS_DIR;
36
+ }
33
37
  //# sourceMappingURL=paths.js.map
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Pipeline configuration schema and loader
3
+ */
4
+ export interface PhaseSuccessCriteria {
5
+ minTasksCompleted?: string | number;
6
+ allowUnmetGoals?: boolean;
7
+ allowPartialGoals?: boolean;
8
+ }
9
+ export interface PhaseConfig {
10
+ auto: boolean;
11
+ parallel?: boolean;
12
+ maxConcurrency?: number;
13
+ analyze?: boolean;
14
+ recommend?: boolean;
15
+ standardize?: boolean;
16
+ carryForward?: boolean;
17
+ completeOnSuccess?: boolean;
18
+ successCriteria?: PhaseSuccessCriteria;
19
+ }
20
+ export interface PipelineConfig {
21
+ version: string;
22
+ phases: {
23
+ do: PhaseConfig;
24
+ check: PhaseConfig;
25
+ act: PhaseConfig;
26
+ };
27
+ }
28
+ export declare const DEFAULT_PIPELINE_CONFIG: PipelineConfig;
29
+ export declare function getPipelineConfigPath(taskId: string): string;
30
+ export declare function loadPipelineConfig(taskId: string): PipelineConfig | null;
31
+ export declare function savePipelineConfig(taskId: string, config: PipelineConfig): void;
32
+ export declare function generatePipelineConfigTemplate(): string;
33
+ export declare function parseSuccessCriteria(criteria: PhaseSuccessCriteria | undefined, totalTasks: number, completedTasks: number): {
34
+ passed: boolean;
35
+ reason?: string;
36
+ };
37
+ //# sourceMappingURL=pipeline-config.d.ts.map
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Pipeline configuration schema and loader
3
+ */
4
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
5
+ import { join } from 'path';
6
+ import yaml from 'js-yaml';
7
+ import { getPlanDir } from './paths.js';
8
+ export const DEFAULT_PIPELINE_CONFIG = {
9
+ version: '1.0',
10
+ phases: {
11
+ do: {
12
+ auto: true,
13
+ parallel: false,
14
+ maxConcurrency: 4,
15
+ successCriteria: {
16
+ minTasksCompleted: '100%'
17
+ }
18
+ },
19
+ check: {
20
+ auto: true,
21
+ analyze: true,
22
+ recommend: true,
23
+ successCriteria: {
24
+ allowPartialGoals: true
25
+ }
26
+ },
27
+ act: {
28
+ auto: true,
29
+ standardize: true,
30
+ carryForward: true,
31
+ completeOnSuccess: true
32
+ }
33
+ }
34
+ };
35
+ export function getPipelineConfigPath(taskId) {
36
+ return join(getPlanDir(taskId), 'pipeline.yaml');
37
+ }
38
+ export function loadPipelineConfig(taskId) {
39
+ const configPath = getPipelineConfigPath(taskId);
40
+ if (!existsSync(configPath)) {
41
+ return null;
42
+ }
43
+ try {
44
+ const content = readFileSync(configPath, 'utf-8');
45
+ const config = yaml.load(content);
46
+ // Merge with defaults to ensure all fields exist
47
+ return {
48
+ ...DEFAULT_PIPELINE_CONFIG,
49
+ ...config,
50
+ phases: {
51
+ do: { ...DEFAULT_PIPELINE_CONFIG.phases.do, ...config.phases.do },
52
+ check: { ...DEFAULT_PIPELINE_CONFIG.phases.check, ...config.phases.check },
53
+ act: { ...DEFAULT_PIPELINE_CONFIG.phases.act, ...config.phases.act }
54
+ }
55
+ };
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
61
+ export function savePipelineConfig(taskId, config) {
62
+ const configPath = getPipelineConfigPath(taskId);
63
+ const content = yaml.dump(config, { indent: 2, lineWidth: 120 });
64
+ writeFileSync(configPath, content);
65
+ }
66
+ export function generatePipelineConfigTemplate() {
67
+ return `# Pipeline Configuration
68
+ # This file controls automated PDCA cycle execution
69
+
70
+ version: "1.0"
71
+
72
+ phases:
73
+ # Do phase: Execute tasks and collect data
74
+ do:
75
+ auto: true # Enable automatic execution
76
+ parallel: false # Execute tasks sequentially (set to true for concurrent)
77
+ maxConcurrency: 4 # Max concurrent tasks when parallel is true
78
+ successCriteria:
79
+ minTasksCompleted: "100%" # Percentage or absolute number required
80
+
81
+ # Check phase: Evaluate results and analyze deviations
82
+ check:
83
+ auto: true # Enable automatic comparison
84
+ analyze: true # Perform root cause analysis
85
+ recommend: true # Generate recommendations
86
+ successCriteria:
87
+ allowPartialGoals: true # Continue even if some goals are partial
88
+ allowUnmetGoals: false # Fail if any goals are unmet (set to true to allow)
89
+
90
+ # Act phase: Process results and take improvement actions
91
+ act:
92
+ auto: true # Enable automatic processing
93
+ standardize: true # Document successful practices
94
+ carryForward: true # Generate next cycle items for unresolved issues
95
+ completeOnSuccess: true # Mark task as completed when pipeline succeeds
96
+ `;
97
+ }
98
+ export function parseSuccessCriteria(criteria, totalTasks, completedTasks) {
99
+ if (!criteria) {
100
+ return { passed: true };
101
+ }
102
+ if (criteria.minTasksCompleted !== undefined) {
103
+ const minRequired = typeof criteria.minTasksCompleted === 'string'
104
+ ? criteria.minTasksCompleted.endsWith('%')
105
+ ? Math.ceil((parseFloat(criteria.minTasksCompleted) / 100) * totalTasks)
106
+ : parseInt(criteria.minTasksCompleted, 10)
107
+ : criteria.minTasksCompleted;
108
+ if (completedTasks < minRequired) {
109
+ return {
110
+ passed: false,
111
+ reason: `Only ${String(completedTasks)}/${String(totalTasks)} tasks completed (required: ${String(criteria.minTasksCompleted)})`
112
+ };
113
+ }
114
+ }
115
+ return { passed: true };
116
+ }
117
+ //# sourceMappingURL=pipeline-config.js.map