@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.
- package/README.md +195 -3
- package/dist/cli.js +70 -10
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +60 -0
- package/dist/commands/pipeline.d.ts +26 -0
- package/dist/commands/pipeline.js +168 -0
- package/dist/commands/schemas.d.ts +4 -0
- package/dist/commands/schemas.js +57 -0
- package/dist/commands/skills.d.ts +1 -1
- package/dist/commands/skills.js +247 -162
- package/dist/commands/validate.d.ts +9 -0
- package/dist/commands/validate.js +179 -0
- package/dist/commands/view.d.ts +2 -0
- package/dist/commands/view.js +65 -0
- package/dist/tmd-skills +0 -0
- package/dist/types.d.ts +21 -1
- package/dist/utils/github.d.ts +18 -0
- package/dist/utils/github.js +165 -0
- package/dist/utils/paths.d.ts +1 -0
- package/dist/utils/paths.js +4 -0
- package/dist/utils/pipeline-config.d.ts +37 -0
- package/dist/utils/pipeline-config.js +117 -0
- package/dist/utils/pipeline.d.ts +81 -0
- package/dist/utils/pipeline.js +580 -0
- package/dist/utils/skills.d.ts +10 -2
- package/dist/utils/skills.js +152 -150
- package/dist/utils/skillssh.d.ts +1 -1
- package/dist/utils/skillssh.js +39 -27
- package/dist/utils/step-executor.d.ts +58 -0
- package/dist/utils/step-executor.js +374 -0
- package/dist/utils/templates.d.ts +1 -0
- package/dist/utils/templates.js +30 -0
- package/package.json +22 -21
- package/scripts/postinstall.js +11 -8
|
@@ -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,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
|
package/dist/tmd-skills
ADDED
|
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
|
package/dist/utils/paths.d.ts
CHANGED
|
@@ -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
|
package/dist/utils/paths.js
CHANGED
|
@@ -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
|