cdspec 0.1.1 → 0.1.3
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/dist/cli.js +1 -40
- package/dist/config/default.js +1 -48
- package/dist/config/loader.js +1 -30
- package/dist/config/path.js +1 -11
- package/dist/config/types.js +1 -1
- package/dist/skill-core/adapters/claudecode-adapter.js +1 -35
- package/dist/skill-core/adapters/codex-adapter.js +1 -28
- package/dist/skill-core/adapters/iflow-adapter.js +1 -39
- package/dist/skill-core/adapters/index.js +1 -34
- package/dist/skill-core/adapters/shared.js +1 -36
- package/dist/skill-core/manifest-loader.js +1 -60
- package/dist/skill-core/scaffold.js +1 -169
- package/dist/skill-core/service.js +1 -109
- package/dist/skill-core/tool-interactions.js +1 -70
- package/dist/skill-core/types.js +1 -1
- package/dist/skill-core/validator.js +1 -25
- package/dist/utils/frontmatter.js +1 -40
- package/dist/utils/fs.js +1 -37
- package/package.json +11 -2
- package/templates/{standards-backend → backend-standard}/SKILL.md +55 -55
- package/templates/backend-standard/agents/openai.yaml +4 -0
- package/templates/{standards-backend → backend-standard}/references/DDD/346/236/266/346/236/204/347/272/246/346/235/237.md +103 -103
- package/templates/{standards-backend → backend-standard}/references/JUC/345/271/266/345/217/221/350/247/204/350/214/203.md +232 -232
- package/templates/{standards-backend → backend-standard}/references//344/274/240/347/273/237/344/270/211/345/261/202/346/236/266/346/236/204/347/272/246/346/235/237.md +35 -35
- package/templates/{standards-backend → backend-standard}/references//345/220/216/347/253/257/345/274/200/345/217/221/350/247/204/350/214/203.md +49 -49
- package/templates/{standards-backend → backend-standard}/references//346/225/260/346/215/256/345/272/223/350/256/276/350/256/241/350/247/204/350/214/203.md +116 -116
- package/templates/{standards-backend → backend-standard}/references//350/256/276/350/256/241/346/250/241/345/274/217/350/220/275/345/234/260/346/211/213/345/206/214.md +395 -395
- package/templates/{frontend-develop-standard → frontend-standard}/SKILL.md +63 -63
- package/templates/frontend-standard/agents/openai.yaml +4 -0
- package/templates/{frontend-develop-standard/references/frontend_develop_standard.md → frontend-standard/references/frontend_standard.md} +28 -321
- package/dist/skill-core/agent-config.js +0 -40
- package/dist/task-core/parser.js +0 -70
- package/dist/task-core/service.js +0 -28
- package/dist/task-core/storage.js +0 -159
- package/dist/task-core/types.js +0 -1
- package/src/cli.ts +0 -44
- package/src/config/default.ts +0 -51
- package/src/config/loader.ts +0 -37
- package/src/config/path.ts +0 -13
- package/src/config/types.ts +0 -22
- package/src/skill-core/adapters/claudecode-adapter.ts +0 -45
- package/src/skill-core/adapters/codex-adapter.ts +0 -36
- package/src/skill-core/adapters/iflow-adapter.ts +0 -49
- package/src/skill-core/adapters/index.ts +0 -39
- package/src/skill-core/adapters/shared.ts +0 -45
- package/src/skill-core/manifest-loader.ts +0 -72
- package/src/skill-core/scaffold.ts +0 -192
- package/src/skill-core/service.ts +0 -137
- package/src/skill-core/tool-interactions.ts +0 -95
- package/src/skill-core/types.ts +0 -22
- package/src/skill-core/validator.ts +0 -28
- package/src/types/yaml.d.ts +0 -4
- package/src/utils/frontmatter.ts +0 -55
- package/src/utils/fs.ts +0 -41
- package/templates/frontend-develop-standard/agents/openai.yaml +0 -4
- package/templates/standards-backend/agents/openai.yaml +0 -4
- package/tests/init.test.ts +0 -63
- package/tsconfig.json +0 -16
- package/vitest.config.ts +0 -9
package/dist/task-core/parser.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
export function splitMarkdownToTasks(markdown, sourcePath, rootTitle) {
|
|
4
|
-
const normalized = markdown.replace(/\r\n/g, "\n");
|
|
5
|
-
const lines = normalized.split("\n");
|
|
6
|
-
const headingStack = [];
|
|
7
|
-
const extracted = [];
|
|
8
|
-
for (const line of lines) {
|
|
9
|
-
const headingMatch = line.match(/^(#{1,6})\s+(.+?)\s*$/);
|
|
10
|
-
if (headingMatch) {
|
|
11
|
-
const level = headingMatch[1].length;
|
|
12
|
-
const title = headingMatch[2].trim();
|
|
13
|
-
headingStack.splice(level - 1);
|
|
14
|
-
headingStack[level - 1] = title;
|
|
15
|
-
continue;
|
|
16
|
-
}
|
|
17
|
-
const listMatch = line.match(/^\s*(?:[-*+]|\d+\.)\s+(.+?)\s*$/);
|
|
18
|
-
if (!listMatch)
|
|
19
|
-
continue;
|
|
20
|
-
const item = listMatch[1].trim();
|
|
21
|
-
const prefix = headingStack.filter(Boolean).join(" / ");
|
|
22
|
-
extracted.push(prefix ? `${prefix} - ${item}` : item);
|
|
23
|
-
}
|
|
24
|
-
if (extracted.length === 0) {
|
|
25
|
-
for (const line of lines) {
|
|
26
|
-
const headingMatch = line.match(/^#{2,6}\s+(.+?)\s*$/);
|
|
27
|
-
if (headingMatch)
|
|
28
|
-
extracted.push(headingMatch[1].trim());
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
if (extracted.length === 0) {
|
|
32
|
-
extracted.push(rootTitle);
|
|
33
|
-
}
|
|
34
|
-
return dedupe(extracted).map((title, idx) => createTaskItem({
|
|
35
|
-
sourcePath,
|
|
36
|
-
title,
|
|
37
|
-
order: idx + 1
|
|
38
|
-
}));
|
|
39
|
-
}
|
|
40
|
-
function createTaskItem(input) {
|
|
41
|
-
const now = new Date().toISOString();
|
|
42
|
-
return {
|
|
43
|
-
id: buildStableId(input.sourcePath, input.title, input.order),
|
|
44
|
-
title: input.title,
|
|
45
|
-
status: "todo",
|
|
46
|
-
source: normalizePath(input.sourcePath),
|
|
47
|
-
createdAt: now,
|
|
48
|
-
updatedAt: now
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
function buildStableId(sourcePath, title, order) {
|
|
52
|
-
const digest = createHash("sha1")
|
|
53
|
-
.update(`${normalizePath(sourcePath)}::${title}::${order}`)
|
|
54
|
-
.digest("hex");
|
|
55
|
-
return digest.slice(0, 10);
|
|
56
|
-
}
|
|
57
|
-
function normalizePath(input) {
|
|
58
|
-
return input.replaceAll("\\", "/").replaceAll(path.sep, "/");
|
|
59
|
-
}
|
|
60
|
-
function dedupe(items) {
|
|
61
|
-
const seen = new Set();
|
|
62
|
-
const result = [];
|
|
63
|
-
for (const item of items) {
|
|
64
|
-
if (seen.has(item))
|
|
65
|
-
continue;
|
|
66
|
-
seen.add(item);
|
|
67
|
-
result.push(item);
|
|
68
|
-
}
|
|
69
|
-
return result;
|
|
70
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { pathExists } from "../utils/fs.js";
|
|
4
|
-
import { splitMarkdownToTasks } from "./parser.js";
|
|
5
|
-
import { archiveTask, initTaskWorkspace, refreshArchiveIndex, refreshTaskIndex, saveTask, updateTaskStatus } from "./storage.js";
|
|
6
|
-
export async function splitTasks(cwd, fromFile, title) {
|
|
7
|
-
const absoluteInput = path.resolve(cwd, fromFile);
|
|
8
|
-
if (!(await pathExists(absoluteInput))) {
|
|
9
|
-
throw new Error(`Input file not found: ${fromFile}`);
|
|
10
|
-
}
|
|
11
|
-
const markdown = await readFile(absoluteInput, "utf8");
|
|
12
|
-
const tasks = splitMarkdownToTasks(markdown, fromFile, title);
|
|
13
|
-
await initTaskWorkspace(cwd);
|
|
14
|
-
for (const task of tasks) {
|
|
15
|
-
await saveTask(cwd, task);
|
|
16
|
-
}
|
|
17
|
-
await refreshTaskIndex(cwd);
|
|
18
|
-
return tasks.length;
|
|
19
|
-
}
|
|
20
|
-
export async function archiveTaskById(cwd, id) {
|
|
21
|
-
await archiveTask(cwd, id);
|
|
22
|
-
await refreshTaskIndex(cwd);
|
|
23
|
-
await refreshArchiveIndex(cwd);
|
|
24
|
-
}
|
|
25
|
-
export async function updateTask(cwd, id, status) {
|
|
26
|
-
await updateTaskStatus(cwd, id, status);
|
|
27
|
-
await refreshTaskIndex(cwd);
|
|
28
|
-
}
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import { readFile, rm, writeFile, readdir } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { ensureDir, pathExists } from "../utils/fs.js";
|
|
4
|
-
import { parseFrontmatter, stringifyFrontmatter } from "../utils/frontmatter.js";
|
|
5
|
-
const ROOT_DIR = ".cdspec";
|
|
6
|
-
const TASKS_DIR = "tasks";
|
|
7
|
-
const ARCHIVE_DIR = "archive";
|
|
8
|
-
export function taskDir(cwd) {
|
|
9
|
-
return path.join(cwd, ROOT_DIR, TASKS_DIR);
|
|
10
|
-
}
|
|
11
|
-
export function archiveDir(cwd) {
|
|
12
|
-
return path.join(cwd, ROOT_DIR, ARCHIVE_DIR);
|
|
13
|
-
}
|
|
14
|
-
export async function initTaskWorkspace(cwd) {
|
|
15
|
-
await ensureDir(taskDir(cwd));
|
|
16
|
-
await ensureDir(archiveDir(cwd));
|
|
17
|
-
}
|
|
18
|
-
export async function saveTask(cwd, task) {
|
|
19
|
-
await initTaskWorkspace(cwd);
|
|
20
|
-
const targetFile = path.join(taskDir(cwd), `${task.id}.md`);
|
|
21
|
-
const body = `# ${task.title}\n\n- status: ${task.status}\n- source: ${task.source}\n`;
|
|
22
|
-
const content = stringifyFrontmatter({
|
|
23
|
-
id: task.id,
|
|
24
|
-
title: task.title,
|
|
25
|
-
status: task.status,
|
|
26
|
-
source: task.source,
|
|
27
|
-
created_at: task.createdAt,
|
|
28
|
-
updated_at: task.updatedAt
|
|
29
|
-
}, body);
|
|
30
|
-
await writeFile(targetFile, content, "utf8");
|
|
31
|
-
}
|
|
32
|
-
export async function loadTask(cwd, id) {
|
|
33
|
-
const file = path.join(taskDir(cwd), `${id}.md`);
|
|
34
|
-
if (!(await pathExists(file)))
|
|
35
|
-
return null;
|
|
36
|
-
const raw = await readFile(file, "utf8");
|
|
37
|
-
const parsed = parseFrontmatter(raw);
|
|
38
|
-
return {
|
|
39
|
-
id: parsed.attributes.id,
|
|
40
|
-
title: parsed.attributes.title,
|
|
41
|
-
status: parseStatus(parsed.attributes.status),
|
|
42
|
-
source: parsed.attributes.source,
|
|
43
|
-
createdAt: parsed.attributes.created_at,
|
|
44
|
-
updatedAt: parsed.attributes.updated_at
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
export async function updateTaskStatus(cwd, id, next) {
|
|
48
|
-
const current = await loadTask(cwd, id);
|
|
49
|
-
if (!current) {
|
|
50
|
-
throw new Error(`Task "${id}" not found.`);
|
|
51
|
-
}
|
|
52
|
-
if (!canTransition(current.status, next)) {
|
|
53
|
-
throw new Error(`Invalid status transition: ${current.status} -> ${next}.`);
|
|
54
|
-
}
|
|
55
|
-
const updated = {
|
|
56
|
-
...current,
|
|
57
|
-
status: next,
|
|
58
|
-
updatedAt: new Date().toISOString()
|
|
59
|
-
};
|
|
60
|
-
await saveTask(cwd, updated);
|
|
61
|
-
return updated;
|
|
62
|
-
}
|
|
63
|
-
export async function archiveTask(cwd, id) {
|
|
64
|
-
const current = await loadTask(cwd, id);
|
|
65
|
-
if (!current) {
|
|
66
|
-
throw new Error(`Task "${id}" not found.`);
|
|
67
|
-
}
|
|
68
|
-
if (current.status !== "done") {
|
|
69
|
-
throw new Error(`Task "${id}" must be done before archive.`);
|
|
70
|
-
}
|
|
71
|
-
const archived = {
|
|
72
|
-
...current,
|
|
73
|
-
archivedAt: new Date().toISOString()
|
|
74
|
-
};
|
|
75
|
-
const sourceFile = path.join(taskDir(cwd), `${id}.md`);
|
|
76
|
-
const archiveFile = path.join(archiveDir(cwd), `${id}.md`);
|
|
77
|
-
const body = `# ${archived.title}\n\nArchived from ${sourceFile.replaceAll("\\", "/")}.\n`;
|
|
78
|
-
const content = stringifyFrontmatter({
|
|
79
|
-
id: archived.id,
|
|
80
|
-
title: archived.title,
|
|
81
|
-
status: archived.status,
|
|
82
|
-
source: archived.source,
|
|
83
|
-
created_at: archived.createdAt,
|
|
84
|
-
updated_at: archived.updatedAt,
|
|
85
|
-
archived_at: archived.archivedAt
|
|
86
|
-
}, body);
|
|
87
|
-
await writeFile(archiveFile, content, "utf8");
|
|
88
|
-
await rm(sourceFile, { force: true });
|
|
89
|
-
return archived;
|
|
90
|
-
}
|
|
91
|
-
export async function refreshTaskIndex(cwd) {
|
|
92
|
-
await initTaskWorkspace(cwd);
|
|
93
|
-
const tasks = await loadByDir(taskDir(cwd));
|
|
94
|
-
const grouped = {
|
|
95
|
-
todo: tasks.filter((task) => task.status === "todo"),
|
|
96
|
-
in_progress: tasks.filter((task) => task.status === "in_progress"),
|
|
97
|
-
done: tasks.filter((task) => task.status === "done")
|
|
98
|
-
};
|
|
99
|
-
const lines = [
|
|
100
|
-
"# Task Index",
|
|
101
|
-
"",
|
|
102
|
-
"## todo",
|
|
103
|
-
...grouped.todo.map((task) => `- [${task.id}] ${task.title}`),
|
|
104
|
-
"",
|
|
105
|
-
"## in_progress",
|
|
106
|
-
...grouped.in_progress.map((task) => `- [${task.id}] ${task.title}`),
|
|
107
|
-
"",
|
|
108
|
-
"## done",
|
|
109
|
-
...grouped.done.map((task) => `- [${task.id}] ${task.title}`),
|
|
110
|
-
""
|
|
111
|
-
];
|
|
112
|
-
await writeFile(path.join(taskDir(cwd), "index.md"), lines.join("\n"), "utf8");
|
|
113
|
-
}
|
|
114
|
-
export async function refreshArchiveIndex(cwd) {
|
|
115
|
-
await initTaskWorkspace(cwd);
|
|
116
|
-
const records = await loadByDir(archiveDir(cwd));
|
|
117
|
-
const lines = [
|
|
118
|
-
"# Archive Index",
|
|
119
|
-
"",
|
|
120
|
-
...records.map((record) => `- [${record.id}] ${record.title}`),
|
|
121
|
-
""
|
|
122
|
-
];
|
|
123
|
-
await writeFile(path.join(archiveDir(cwd), "index.md"), lines.join("\n"), "utf8");
|
|
124
|
-
}
|
|
125
|
-
async function loadByDir(dirPath) {
|
|
126
|
-
if (!(await pathExists(dirPath)))
|
|
127
|
-
return [];
|
|
128
|
-
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
129
|
-
const result = [];
|
|
130
|
-
for (const entry of entries) {
|
|
131
|
-
if (!entry.isFile() || !entry.name.endsWith(".md") || entry.name === "index.md")
|
|
132
|
-
continue;
|
|
133
|
-
const raw = await readFile(path.join(dirPath, entry.name), "utf8");
|
|
134
|
-
const parsed = parseFrontmatter(raw);
|
|
135
|
-
result.push({
|
|
136
|
-
id: parsed.attributes.id,
|
|
137
|
-
title: parsed.attributes.title,
|
|
138
|
-
status: parseStatus(parsed.attributes.status),
|
|
139
|
-
source: parsed.attributes.source,
|
|
140
|
-
createdAt: parsed.attributes.created_at,
|
|
141
|
-
updatedAt: parsed.attributes.updated_at
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
return result;
|
|
145
|
-
}
|
|
146
|
-
function parseStatus(input) {
|
|
147
|
-
if (input === "todo" || input === "in_progress" || input === "done")
|
|
148
|
-
return input;
|
|
149
|
-
return "todo";
|
|
150
|
-
}
|
|
151
|
-
function canTransition(current, next) {
|
|
152
|
-
if (current === next)
|
|
153
|
-
return true;
|
|
154
|
-
if (current === "todo" && next === "in_progress")
|
|
155
|
-
return true;
|
|
156
|
-
if (current === "in_progress" && next === "done")
|
|
157
|
-
return true;
|
|
158
|
-
return false;
|
|
159
|
-
}
|
package/dist/task-core/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/src/cli.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import checkbox from '@inquirer/checkbox'
|
|
3
|
-
import { Command } from 'commander'
|
|
4
|
-
import path from 'node:path'
|
|
5
|
-
import process from 'node:process'
|
|
6
|
-
import { initSkills } from './skill-core/service.js'
|
|
7
|
-
|
|
8
|
-
const program = new Command()
|
|
9
|
-
|
|
10
|
-
program.name('cdspec').description('Skill init CLI').version('0.1.0')
|
|
11
|
-
|
|
12
|
-
program
|
|
13
|
-
.command('init')
|
|
14
|
-
.description('Initialize skills for selected coding agents')
|
|
15
|
-
.option('--agents <agents>', 'codex|claudecode|iflow|all or comma-separated')
|
|
16
|
-
.action(async (options: { agents?: string }) => {
|
|
17
|
-
const selected = options.agents ?? (await askAgentsSelection())
|
|
18
|
-
const files = await initSkills(process.cwd(), selected, true)
|
|
19
|
-
console.log(`Initialized setup for "${selected}".`)
|
|
20
|
-
console.log(`Generated files: ${files.map(x => path.relative(process.cwd(), x)).join(', ')}`)
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
program.parseAsync(process.argv).catch((error: unknown) => {
|
|
24
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
25
|
-
console.error(message)
|
|
26
|
-
process.exitCode = 1
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
async function askAgentsSelection(): Promise<string> {
|
|
30
|
-
if (!process.stdin.isTTY) return 'all'
|
|
31
|
-
const values = await checkbox({
|
|
32
|
-
message: 'Select coding agents',
|
|
33
|
-
choices: [
|
|
34
|
-
{ name: 'codex', value: 'codex', checked: true },
|
|
35
|
-
{ name: 'claudecode', value: 'claudecode' },
|
|
36
|
-
{ name: 'iflow', value: 'iflow' },
|
|
37
|
-
{ name: 'all', value: 'all' }
|
|
38
|
-
]
|
|
39
|
-
})
|
|
40
|
-
if (values.length === 0) return 'all'
|
|
41
|
-
if (values.includes('all')) return 'all'
|
|
42
|
-
return values.join(',')
|
|
43
|
-
}
|
|
44
|
-
|
package/src/config/default.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { CDSpecConfig } from "./types.js";
|
|
2
|
-
|
|
3
|
-
export const defaultConfig: CDSpecConfig = {
|
|
4
|
-
commandBindings: [
|
|
5
|
-
{
|
|
6
|
-
id: "propose",
|
|
7
|
-
skill: "openspec-core",
|
|
8
|
-
description: "Create a change proposal with scope and impacted specs."
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
id: "explore",
|
|
12
|
-
skill: "openspec-core",
|
|
13
|
-
description: "Analyze existing specs and produce planned deltas."
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
id: "apply",
|
|
17
|
-
skill: "openspec-core",
|
|
18
|
-
description: "Implement approved tasks and keep spec updates in sync."
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
id: "archive",
|
|
22
|
-
skill: "openspec-core",
|
|
23
|
-
description: "Archive completed changes and update baseline specs."
|
|
24
|
-
}
|
|
25
|
-
],
|
|
26
|
-
agents: {
|
|
27
|
-
codex: {
|
|
28
|
-
rootDir: ".codex",
|
|
29
|
-
commandsDir: "prompts",
|
|
30
|
-
commandFilePattern: "opsx-{id}.md",
|
|
31
|
-
slashPattern: "/opsx-{id}",
|
|
32
|
-
guideFile: "AGENTS.md"
|
|
33
|
-
},
|
|
34
|
-
claudecode: {
|
|
35
|
-
rootDir: ".claude",
|
|
36
|
-
commandsDir: "commands/opsx",
|
|
37
|
-
commandFilePattern: "{id}.md",
|
|
38
|
-
slashPattern: "/opsx:{id}",
|
|
39
|
-
guideFile: "CLAUDE.md",
|
|
40
|
-
guideAtProjectRoot: true
|
|
41
|
-
},
|
|
42
|
-
iflow: {
|
|
43
|
-
rootDir: ".iflow",
|
|
44
|
-
commandsDir: "commands",
|
|
45
|
-
commandFilePattern: "opsx-{id}.md",
|
|
46
|
-
slashPattern: "/opsx-{id}",
|
|
47
|
-
guideFile: "IFLOW.md"
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
|
package/src/config/loader.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
-
import { parse } from "yaml";
|
|
4
|
-
import { pathExists } from "../utils/fs.js";
|
|
5
|
-
import { defaultConfig } from "./default.js";
|
|
6
|
-
import { CDSpecConfig } from "./types.js";
|
|
7
|
-
|
|
8
|
-
const CONFIG_FILE = "cdspec.config.yaml";
|
|
9
|
-
|
|
10
|
-
export async function loadConfig(cwd: string): Promise<CDSpecConfig> {
|
|
11
|
-
const configPath = path.join(cwd, CONFIG_FILE);
|
|
12
|
-
if (!(await pathExists(configPath))) {
|
|
13
|
-
return defaultConfig;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const raw = await readFile(configPath, "utf8");
|
|
17
|
-
const parsed = parse(raw) as Partial<CDSpecConfig> | null;
|
|
18
|
-
if (!parsed) return defaultConfig;
|
|
19
|
-
|
|
20
|
-
const merged: CDSpecConfig = {
|
|
21
|
-
commandBindings:
|
|
22
|
-
parsed.commandBindings && parsed.commandBindings.length > 0
|
|
23
|
-
? parsed.commandBindings
|
|
24
|
-
: defaultConfig.commandBindings,
|
|
25
|
-
agents: {
|
|
26
|
-
codex: { ...defaultConfig.agents.codex, ...(parsed.agents?.codex || {}) },
|
|
27
|
-
claudecode: {
|
|
28
|
-
...defaultConfig.agents.claudecode,
|
|
29
|
-
...(parsed.agents?.claudecode || {})
|
|
30
|
-
},
|
|
31
|
-
iflow: { ...defaultConfig.agents.iflow, ...(parsed.agents?.iflow || {}) }
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
return merged;
|
|
36
|
-
}
|
|
37
|
-
|
package/src/config/path.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import os from "node:os";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
export function resolveAgentRoot(cwd: string, rootDir: string): string {
|
|
5
|
-
if (rootDir.startsWith("~/") || rootDir.startsWith("~\\")) {
|
|
6
|
-
return path.join(os.homedir(), rootDir.slice(2));
|
|
7
|
-
}
|
|
8
|
-
if (path.win32.isAbsolute(rootDir) || path.posix.isAbsolute(rootDir)) {
|
|
9
|
-
return path.normalize(rootDir);
|
|
10
|
-
}
|
|
11
|
-
return path.join(cwd, rootDir);
|
|
12
|
-
}
|
|
13
|
-
|
package/src/config/types.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { Target } from "../skill-core/types.js";
|
|
2
|
-
|
|
3
|
-
export interface CommandBinding {
|
|
4
|
-
id: string;
|
|
5
|
-
skill: string;
|
|
6
|
-
description: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface AgentConfig {
|
|
10
|
-
rootDir: string;
|
|
11
|
-
commandsDir: string;
|
|
12
|
-
commandFilePattern: string;
|
|
13
|
-
slashPattern: string;
|
|
14
|
-
guideFile: string;
|
|
15
|
-
guideAtProjectRoot?: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface CDSpecConfig {
|
|
19
|
-
commandBindings: CommandBinding[];
|
|
20
|
-
agents: Record<Target, AgentConfig>;
|
|
21
|
-
}
|
|
22
|
-
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { emptyDir, pathExists } from "../../utils/fs.js";
|
|
3
|
-
import { Diagnostic, SkillManifest, TargetAdapter } from "../types.js";
|
|
4
|
-
import { emitWithLayout } from "./shared.js";
|
|
5
|
-
|
|
6
|
-
export class ClaudeCodeAdapter implements TargetAdapter {
|
|
7
|
-
target = "claudecode" as const;
|
|
8
|
-
|
|
9
|
-
validate(manifest: SkillManifest): Diagnostic[] {
|
|
10
|
-
const diagnostics: Diagnostic[] = [];
|
|
11
|
-
if (!manifest.name) {
|
|
12
|
-
diagnostics.push({ level: "error", message: "[claudecode] name is required." });
|
|
13
|
-
}
|
|
14
|
-
return diagnostics;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async emit(manifest: SkillManifest, outDir: string, force: boolean): Promise<void> {
|
|
18
|
-
if ((await pathExists(outDir)) && !force) {
|
|
19
|
-
throw new Error(
|
|
20
|
-
`Target already exists for skill "${manifest.name}" at ${outDir}. Use --force to overwrite.`
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
if (force) {
|
|
24
|
-
await emptyDir(outDir);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const meta = {
|
|
28
|
-
name: manifest.name,
|
|
29
|
-
description: manifest.description,
|
|
30
|
-
entry: "SKILL.md"
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
await emitWithLayout(manifest, outDir, true, [
|
|
34
|
-
{
|
|
35
|
-
relativePath: "claudecode.skill.json",
|
|
36
|
-
content: `${JSON.stringify(meta, null, 2)}\n`
|
|
37
|
-
}
|
|
38
|
-
]);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function claudecodeSkillPath(baseOutDir: string, skillName: string): string {
|
|
43
|
-
return path.join(baseOutDir, "claudecode", skillName);
|
|
44
|
-
}
|
|
45
|
-
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { emptyDir, pathExists } from "../../utils/fs.js";
|
|
3
|
-
import { Diagnostic, SkillManifest, TargetAdapter } from "../types.js";
|
|
4
|
-
import { emitWithLayout } from "./shared.js";
|
|
5
|
-
|
|
6
|
-
export class CodexAdapter implements TargetAdapter {
|
|
7
|
-
target = "codex" as const;
|
|
8
|
-
|
|
9
|
-
validate(manifest: SkillManifest): Diagnostic[] {
|
|
10
|
-
const diagnostics: Diagnostic[] = [];
|
|
11
|
-
if (!manifest.description) {
|
|
12
|
-
diagnostics.push({
|
|
13
|
-
level: "error",
|
|
14
|
-
message: `[codex] ${manifest.name}: description is required.`
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
return diagnostics;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async emit(manifest: SkillManifest, outDir: string, force: boolean): Promise<void> {
|
|
21
|
-
if ((await pathExists(outDir)) && !force) {
|
|
22
|
-
throw new Error(
|
|
23
|
-
`Target already exists for skill "${manifest.name}" at ${outDir}. Use --force to overwrite.`
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
if (force) {
|
|
27
|
-
await emptyDir(outDir);
|
|
28
|
-
}
|
|
29
|
-
await emitWithLayout(manifest, outDir, true, []);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function codexSkillPath(baseOutDir: string, skillName: string): string {
|
|
34
|
-
return path.join(baseOutDir, "codex", skillName);
|
|
35
|
-
}
|
|
36
|
-
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { emptyDir, pathExists } from "../../utils/fs.js";
|
|
3
|
-
import { Diagnostic, SkillManifest, TargetAdapter } from "../types.js";
|
|
4
|
-
import { emitWithLayout } from "./shared.js";
|
|
5
|
-
|
|
6
|
-
export class IFlowAdapter implements TargetAdapter {
|
|
7
|
-
target = "iflow" as const;
|
|
8
|
-
|
|
9
|
-
validate(manifest: SkillManifest): Diagnostic[] {
|
|
10
|
-
const diagnostics: Diagnostic[] = [];
|
|
11
|
-
if (!manifest.description) {
|
|
12
|
-
diagnostics.push({
|
|
13
|
-
level: "warning",
|
|
14
|
-
message: `[iflow] ${manifest.name}: description is empty.`
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
return diagnostics;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async emit(manifest: SkillManifest, outDir: string, force: boolean): Promise<void> {
|
|
21
|
-
if ((await pathExists(outDir)) && !force) {
|
|
22
|
-
throw new Error(
|
|
23
|
-
`Target already exists for skill "${manifest.name}" at ${outDir}. Use --force to overwrite.`
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
if (force) {
|
|
27
|
-
await emptyDir(outDir);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const skillYaml = [
|
|
31
|
-
`name: "${manifest.name}"`,
|
|
32
|
-
`description: "${manifest.description.replace(/"/g, '\\"')}"`,
|
|
33
|
-
'entry: "prompt.md"'
|
|
34
|
-
].join("\n");
|
|
35
|
-
|
|
36
|
-
await emitWithLayout(manifest, outDir, false, [
|
|
37
|
-
{
|
|
38
|
-
relativePath: "prompt.md",
|
|
39
|
-
content: `# ${manifest.name}\n\n${manifest.body}\n`
|
|
40
|
-
},
|
|
41
|
-
{ relativePath: "iflow.skill.yaml", content: `${skillYaml}\n` }
|
|
42
|
-
]);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function iflowSkillPath(baseOutDir: string, skillName: string): string {
|
|
47
|
-
return path.join(baseOutDir, "iflow", skillName);
|
|
48
|
-
}
|
|
49
|
-
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { Target, TargetAdapter } from "../types.js";
|
|
2
|
-
import { ClaudeCodeAdapter } from "./claudecode-adapter.js";
|
|
3
|
-
import { CodexAdapter } from "./codex-adapter.js";
|
|
4
|
-
import { IFlowAdapter } from "./iflow-adapter.js";
|
|
5
|
-
|
|
6
|
-
const adapters: Record<Target, TargetAdapter> = {
|
|
7
|
-
codex: new CodexAdapter(),
|
|
8
|
-
claudecode: new ClaudeCodeAdapter(),
|
|
9
|
-
iflow: new IFlowAdapter()
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export function getAdapter(target: Target): TargetAdapter {
|
|
13
|
-
return adapters[target];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function expandTargets(raw: string): Target[] {
|
|
17
|
-
const value = raw.trim().toLowerCase();
|
|
18
|
-
if (value === "all") return ["codex", "claudecode", "iflow"];
|
|
19
|
-
const parts = value
|
|
20
|
-
.split(",")
|
|
21
|
-
.map((item) => item.trim())
|
|
22
|
-
.filter(Boolean)
|
|
23
|
-
.map(normalizeTargetAlias);
|
|
24
|
-
if (parts.length === 0) {
|
|
25
|
-
throw new Error(`Unknown target "${raw}". Use codex|claudecode|iflow|all.`);
|
|
26
|
-
}
|
|
27
|
-
const invalid = parts.filter(
|
|
28
|
-
(item) => item !== "codex" && item !== "claudecode" && item !== "iflow"
|
|
29
|
-
);
|
|
30
|
-
if (invalid.length > 0) {
|
|
31
|
-
throw new Error(`Unknown target "${invalid[0]}". Use codex|claudecode|iflow|all.`);
|
|
32
|
-
}
|
|
33
|
-
return Array.from(new Set(parts)) as Target[];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function normalizeTargetAlias(value: string): string {
|
|
37
|
-
if (value === "ifow") return "iflow";
|
|
38
|
-
return value;
|
|
39
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { copyFile, readFile, readdir, writeFile } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { ensureDir, pathExists } from "../../utils/fs.js";
|
|
4
|
-
import { SkillManifest } from "../types.js";
|
|
5
|
-
|
|
6
|
-
export async function emitWithLayout(
|
|
7
|
-
manifest: SkillManifest,
|
|
8
|
-
outDir: string,
|
|
9
|
-
includeOriginalSkillMd: boolean,
|
|
10
|
-
generatedFiles: Array<{ relativePath: string; content: string }>
|
|
11
|
-
): Promise<void> {
|
|
12
|
-
await ensureDir(outDir);
|
|
13
|
-
if (includeOriginalSkillMd) {
|
|
14
|
-
const srcSkillMd = path.join(manifest.sourcePath, "SKILL.md");
|
|
15
|
-
await copyFile(srcSkillMd, path.join(outDir, "SKILL.md"));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
for (const file of generatedFiles) {
|
|
19
|
-
const targetFile = path.join(outDir, file.relativePath);
|
|
20
|
-
await ensureDir(path.dirname(targetFile));
|
|
21
|
-
await writeFile(targetFile, file.content, "utf8");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const folders = ["agents", "references", "scripts", "assets"];
|
|
25
|
-
for (const folder of folders) {
|
|
26
|
-
const sourceFolder = path.join(manifest.sourcePath, folder);
|
|
27
|
-
if (!(await pathExists(sourceFolder))) continue;
|
|
28
|
-
await copyFolder(sourceFolder, path.join(outDir, folder));
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function copyFolder(srcDir: string, dstDir: string): Promise<void> {
|
|
33
|
-
await ensureDir(dstDir);
|
|
34
|
-
const entries = await readdir(srcDir, { withFileTypes: true });
|
|
35
|
-
for (const entry of entries) {
|
|
36
|
-
const src = path.join(srcDir, entry.name);
|
|
37
|
-
const dst = path.join(dstDir, entry.name);
|
|
38
|
-
if (entry.isDirectory()) {
|
|
39
|
-
await copyFolder(src, dst);
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
const data = await readFile(src);
|
|
43
|
-
await writeFile(dst, data);
|
|
44
|
-
}
|
|
45
|
-
}
|