speccrew 0.1.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/.speccrew/agents/speccrew-feature-designer.md +142 -0
- package/.speccrew/agents/speccrew-product-manager.md +61 -0
- package/.speccrew/agents/speccrew-system-designer.md +200 -0
- package/.speccrew/agents/speccrew-system-developer.md +238 -0
- package/.speccrew/agents/speccrew-task-worker.md +80 -0
- package/.speccrew/agents/speccrew-team-leader.md +92 -0
- package/.speccrew/agents/speccrew-test-manager.md +313 -0
- package/.speccrew/skills/speccrew-create-agents/SKILL.md +98 -0
- package/.speccrew/skills/speccrew-create-agents/templates/agents/designer-agent.md +54 -0
- package/.speccrew/skills/speccrew-create-agents/templates/agents/dev-agent.md +79 -0
- package/.speccrew/skills/speccrew-create-agents/templates/agents/test-agent.md +80 -0
- package/.speccrew/skills/speccrew-dev-backend/SKILL.md +205 -0
- package/.speccrew/skills/speccrew-dev-backend/templates/TASK-RECORD-TEMPLATE.md +118 -0
- package/.speccrew/skills/speccrew-dev-desktop/SKILL.md +258 -0
- package/.speccrew/skills/speccrew-dev-desktop/templates/TASK-RECORD-TEMPLATE.md +161 -0
- package/.speccrew/skills/speccrew-dev-frontend/SKILL.md +202 -0
- package/.speccrew/skills/speccrew-dev-frontend/templates/TASK-RECORD-TEMPLATE.md +115 -0
- package/.speccrew/skills/speccrew-dev-mobile/SKILL.md +200 -0
- package/.speccrew/skills/speccrew-dev-mobile/templates/TASK-RECORD-TEMPLATE.md +125 -0
- package/.speccrew/skills/speccrew-fd-api-contract/SKILL.md +73 -0
- package/.speccrew/skills/speccrew-fd-api-contract/templates/API-CONTRACT-TEMPLATE.md +96 -0
- package/.speccrew/skills/speccrew-fd-feature-design/SKILL.md +395 -0
- package/.speccrew/skills/speccrew-fd-feature-design/templates/FEATURE-SPEC-TEMPLATE.md +387 -0
- package/.speccrew/skills/speccrew-get-timestamp/SKILL.md +80 -0
- package/.speccrew/skills/speccrew-get-timestamp/scripts/get-timestamp.js +35 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-api-analyze/SKILL.md +1116 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-api-analyze/templates/FEATURE-DETAIL-TEMPLATE-FASTAPI.md +462 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-api-analyze/templates/FEATURE-DETAIL-TEMPLATE-JAVA.md +480 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-api-analyze/templates/FEATURE-DETAIL-TEMPLATE-NET.md +464 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-api-analyze/templates/FEATURE-DETAIL-TEMPLATE.md +480 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-api-analyze/templates/MODULE-OVERVIEW-TEMPLATE.md +367 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/SKILL.md +667 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/STATUS-FORMATS.md +74 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/batch-orchestrator.js +176 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/get-next-batch.js +150 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/get-pending-features.js +106 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/mark-stale.js +249 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/process-batch-results.js +848 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-dispatch/scripts/update-feature-status.js +226 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-init-features/SKILL.md +264 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-init-features/examples/features.json +34 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-init-features/scripts/generate-inventory.js +867 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-init-features/scripts/test-inventory.js +26 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-module-classify/SKILL.md +165 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-module-classify/scripts/apply-module-mapping.js +208 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-module-classify/scripts/extract-module-summary.js +180 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-module-classify/scripts/reindex-modules.js +358 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-ui-analyze/SKILL.md +1055 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-ui-analyze/templates/FEATURE-DETAIL-TEMPLATE-UI-DESKTOP.md +303 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-ui-analyze/templates/FEATURE-DETAIL-TEMPLATE-UI-ELECTRON.md +327 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-ui-analyze/templates/FEATURE-DETAIL-TEMPLATE-UI-MINIAPP.md +292 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-ui-analyze/templates/FEATURE-DETAIL-TEMPLATE-UI-MOBILE.md +281 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-ui-analyze/templates/FEATURE-DETAIL-TEMPLATE-UI.md +324 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-ui-style-extract/SKILL.md +270 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-ui-style-extract/templates/COMPONENT-PATTERN-TEMPLATE.md +33 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-ui-style-extract/templates/LAYOUT-PATTERN-TEMPLATE.md +33 -0
- package/.speccrew/skills/speccrew-knowledge-bizs-ui-style-extract/templates/PAGE-TYPE-TEMPLATE.md +33 -0
- package/.speccrew/skills/speccrew-knowledge-graph-query/SKILL.md +229 -0
- package/.speccrew/skills/speccrew-knowledge-graph-query/scripts/graph-query.js +549 -0
- package/.speccrew/skills/speccrew-knowledge-graph-write/SKILL.md +181 -0
- package/.speccrew/skills/speccrew-knowledge-graph-write/scripts/graph-write.js +651 -0
- package/.speccrew/skills/speccrew-knowledge-module-summarize/SKILL.md +305 -0
- package/.speccrew/skills/speccrew-knowledge-module-summarize/templates/MODULE-OVERVIEW-TEMPLATE.md +400 -0
- package/.speccrew/skills/speccrew-knowledge-system-summarize/SKILL.md +351 -0
- package/.speccrew/skills/speccrew-knowledge-system-summarize/templates/SYSTEM-OVERVIEW-TEMPLATE.md +294 -0
- package/.speccrew/skills/speccrew-knowledge-techs-dispatch/SKILL.md +683 -0
- package/.speccrew/skills/speccrew-knowledge-techs-dispatch/STATUS-FORMATS.md +550 -0
- package/.speccrew/skills/speccrew-knowledge-techs-dispatch/templates/techs-manifest-EXAMPLE.json +35 -0
- package/.speccrew/skills/speccrew-knowledge-techs-generate/SKILL.md +1087 -0
- package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/ARCHITECTURE-TEMPLATE.md +240 -0
- package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/COLOR-SYSTEM-TEMPLATE.md +68 -0
- package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/COMPONENT-LIBRARY-TEMPLATE.md +86 -0
- package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/CONVENTIONS-BUILD-TEMPLATE.md +466 -0
- package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/CONVENTIONS-DATA-TEMPLATE.md +432 -0
- package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/CONVENTIONS-DESIGN-TEMPLATE.md +1209 -0
- package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/CONVENTIONS-DEV-TEMPLATE.md +1433 -0
- package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/CONVENTIONS-SYSTEM-TEST-TEMPLATE.md +1052 -0
- package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/CONVENTIONS-UNIT-TEST-TEMPLATE.md +946 -0
- package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/INDEX-TEMPLATE.md +29 -0
- package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/PAGE-LAYOUTS-TEMPLATE.md +69 -0
- package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/PAGE-TYPE-SUMMARY-TEMPLATE.md +74 -0
- package/.speccrew/skills/speccrew-knowledge-techs-generate/templates/TECH-STACK-TEMPLATE.md +232 -0
- package/.speccrew/skills/speccrew-knowledge-techs-generate-conventions/SKILL.md +628 -0
- package/.speccrew/skills/speccrew-knowledge-techs-generate-ui-style/SKILL.md +392 -0
- package/.speccrew/skills/speccrew-knowledge-techs-index/SKILL.md +489 -0
- package/.speccrew/skills/speccrew-knowledge-techs-index/templates/INDEX-TEMPLATE.md +243 -0
- package/.speccrew/skills/speccrew-knowledge-techs-init/SKILL.md +269 -0
- package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/SKILL.md +562 -0
- package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/BUSINESS-COMPONENTS-TEMPLATE.md +171 -0
- package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/COMMON-COMPONENTS-TEMPLATE.md +177 -0
- package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/COMPONENT-INDIVIDUAL-TEMPLATE.md +80 -0
- package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/COMPONENT-LIBRARY-TEMPLATE.md +118 -0
- package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/LAYOUT-INDIVIDUAL-TEMPLATE.md +97 -0
- package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/LAYOUT-PATTERNS-TEMPLATE.md +208 -0
- package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/NAVIGATION-PATTERNS-TEMPLATE.md +157 -0
- package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/PAGE-TYPE-INDIVIDUAL-TEMPLATE.md +123 -0
- package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/PAGE-TYPE-SUMMARY-TEMPLATE.md +58 -0
- package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/SPACING-TEMPLATE.md +119 -0
- package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/STYLE-SYSTEM-TEMPLATE.md +117 -0
- package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/TYPOGRAPHY-TEMPLATE.md +107 -0
- package/.speccrew/skills/speccrew-knowledge-techs-ui-analyze/templates/UI-STYLE-GUIDE-TEMPLATE.md +171 -0
- package/.speccrew/skills/speccrew-pm-requirement-analysis/SKILL.md +434 -0
- package/.speccrew/skills/speccrew-pm-requirement-analysis/templates/BIZS-MODELING-TEMPLATE.md +332 -0
- package/.speccrew/skills/speccrew-pm-requirement-analysis/templates/PRD-TEMPLATE.md +200 -0
- package/.speccrew/skills/speccrew-pm-requirement-assess/SKILL.md +195 -0
- package/.speccrew/skills/speccrew-project-diagnosis/SKILL.md +208 -0
- package/.speccrew/skills/speccrew-project-diagnosis/templates/DIAGNOSIS-REPORT-TEMPLATE.md +202 -0
- package/.speccrew/skills/speccrew-sd-backend/SKILL.md +188 -0
- package/.speccrew/skills/speccrew-sd-backend/templates/INDEX-TEMPLATE.md +85 -0
- package/.speccrew/skills/speccrew-sd-backend/templates/SD-BACKEND-TEMPLATE.md +269 -0
- package/.speccrew/skills/speccrew-sd-desktop/SKILL.md +192 -0
- package/.speccrew/skills/speccrew-sd-desktop/templates/INDEX-TEMPLATE.md +271 -0
- package/.speccrew/skills/speccrew-sd-desktop/templates/SD-DESKTOP-TEMPLATE.md +673 -0
- package/.speccrew/skills/speccrew-sd-frontend/SKILL.md +176 -0
- package/.speccrew/skills/speccrew-sd-frontend/templates/INDEX-TEMPLATE.md +184 -0
- package/.speccrew/skills/speccrew-sd-frontend/templates/SD-FRONTEND-TEMPLATE.md +382 -0
- package/.speccrew/skills/speccrew-sd-mobile/SKILL.md +189 -0
- package/.speccrew/skills/speccrew-sd-mobile/templates/INDEX-TEMPLATE.md +219 -0
- package/.speccrew/skills/speccrew-sd-mobile/templates/SD-MOBILE-TEMPLATE.md +534 -0
- package/.speccrew/skills/speccrew-test-case-design/SKILL.md +284 -0
- package/.speccrew/skills/speccrew-test-case-design/templates/TEST-CASE-DESIGN-TEMPLATE.md +263 -0
- package/.speccrew/skills/speccrew-test-code-gen/SKILL.md +313 -0
- package/.speccrew/skills/speccrew-test-code-gen/templates/TEST-CODE-PLAN-TEMPLATE.md +180 -0
- package/.speccrew/skills/speccrew-test-execute/SKILL.md +283 -0
- package/.speccrew/skills/speccrew-test-execute/templates/BUG-REPORT-TEMPLATE.md +50 -0
- package/.speccrew/skills/speccrew-test-execute/templates/TEST-REPORT-TEMPLATE.md +57 -0
- package/.speccrew/skills/speccrew-workflow-diagnose/SKILL.md +155 -0
- package/LICENSE +21 -0
- package/README.ar.md +318 -0
- package/README.en.md +318 -0
- package/README.es.md +318 -0
- package/README.md +340 -0
- package/bin/cli.js +62 -0
- package/lib/commands/doctor.js +138 -0
- package/lib/commands/init.js +231 -0
- package/lib/commands/list.js +114 -0
- package/lib/commands/uninstall.js +117 -0
- package/lib/commands/update.js +351 -0
- package/lib/ide-adapters.js +73 -0
- package/lib/utils.js +104 -0
- package/package.json +28 -0
- package/workspace-template/docs/configs/document-templates.json +667 -0
- package/workspace-template/docs/configs/platform-mapping.json +194 -0
- package/workspace-template/docs/configs/tech-stack-mappings.json +313 -0
- package/workspace-template/docs/configs/validation-rules.json +87 -0
- package/workspace-template/docs/rules/mermaid-rule.md +114 -0
- package/workspace-template/docs/solutions/Agent/346/212/200/350/203/275/345/256/232/344/271/211+/351/234/200/346/261/202/346/226/207/346/241/243+UML/344/275/277/347/224/250/346/250/241/346/235/277/357/274/210ISA-95/345/205/255/346/256/265/345/274/217/350/236/215/345/220/210/347/211/210/357/274/211.md +586 -0
- package/workspace-template/docs/solutions/agent-knowledge-map.md +238 -0
- package/workspace-template/docs/solutions/bizs-knowledge-pipeline.md +678 -0
- package/workspace-template/docs/solutions/harness.md +410 -0
- package/workspace-template/docs/solutions/knowledge-incremental-sync-spec.md +943 -0
- package/workspace-template/docs/solutions/techs-knowledge-pipeline.md +803 -0
- package/workspace-template/docs/solutions/workspace-structure.md +318 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const {
|
|
4
|
+
isSpeccrewFile,
|
|
5
|
+
readSpeccrewRC,
|
|
6
|
+
writeSpeccrewRC,
|
|
7
|
+
getPackageVersion,
|
|
8
|
+
getSourceRoot,
|
|
9
|
+
getWorkspaceTemplatePath,
|
|
10
|
+
copyDirRecursive,
|
|
11
|
+
} = require('../utils');
|
|
12
|
+
const { resolveIDE, getIDEConfig } = require('../ide-adapters');
|
|
13
|
+
|
|
14
|
+
// 解析命令行参数
|
|
15
|
+
function parseArgs() {
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
let ide = null;
|
|
18
|
+
|
|
19
|
+
for (let i = 0; i < args.length; i++) {
|
|
20
|
+
if (args[i] === '--ide' && i + 1 < args.length) {
|
|
21
|
+
ide = args[i + 1];
|
|
22
|
+
i++;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return { ide };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 递归获取目录下所有文件(相对路径)
|
|
30
|
+
function getAllFiles(dir, baseDir = dir, result = []) {
|
|
31
|
+
if (!fs.existsSync(dir)) return result;
|
|
32
|
+
|
|
33
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
34
|
+
for (const entry of entries) {
|
|
35
|
+
const fullPath = path.join(dir, entry.name);
|
|
36
|
+
const relPath = path.relative(baseDir, fullPath);
|
|
37
|
+
if (entry.isDirectory()) {
|
|
38
|
+
getAllFiles(fullPath, baseDir, result);
|
|
39
|
+
} else {
|
|
40
|
+
result.push(relPath);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 递归获取目录下所有子目录(相对路径)
|
|
47
|
+
function getAllDirs(dir, baseDir = dir, result = []) {
|
|
48
|
+
if (!fs.existsSync(dir)) return result;
|
|
49
|
+
|
|
50
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
const fullPath = path.join(dir, entry.name);
|
|
53
|
+
const relPath = path.relative(baseDir, fullPath);
|
|
54
|
+
if (entry.isDirectory()) {
|
|
55
|
+
result.push(relPath);
|
|
56
|
+
getAllDirs(fullPath, baseDir, result);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 复制文件并返回是否实际复制(目标不存在或内容不同)
|
|
63
|
+
function copyFileIfChanged(src, dest) {
|
|
64
|
+
if (!fs.existsSync(src)) return { copied: false, isNew: false };
|
|
65
|
+
|
|
66
|
+
const destExists = fs.existsSync(dest);
|
|
67
|
+
if (!destExists) {
|
|
68
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
69
|
+
fs.copyFileSync(src, dest);
|
|
70
|
+
return { copied: true, isNew: true };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 比较文件内容
|
|
74
|
+
const srcContent = fs.readFileSync(src);
|
|
75
|
+
const destContent = fs.readFileSync(dest);
|
|
76
|
+
if (srcContent.equals(destContent)) {
|
|
77
|
+
return { copied: false, isNew: false };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fs.copyFileSync(src, dest);
|
|
81
|
+
return { copied: true, isNew: false };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 更新 agents 目录
|
|
85
|
+
function updateAgents(srcDir, destDir, stats) {
|
|
86
|
+
if (!fs.existsSync(srcDir)) return;
|
|
87
|
+
|
|
88
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
89
|
+
for (const entry of entries) {
|
|
90
|
+
if (!entry.isFile()) continue;
|
|
91
|
+
if (!isSpeccrewFile(entry.name)) continue;
|
|
92
|
+
|
|
93
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
94
|
+
const destPath = path.join(destDir, entry.name);
|
|
95
|
+
|
|
96
|
+
const result = copyFileIfChanged(srcPath, destPath);
|
|
97
|
+
if (result.isNew) {
|
|
98
|
+
stats.added++;
|
|
99
|
+
} else if (result.copied) {
|
|
100
|
+
stats.updated++;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 检测目标目录中多余的 speccrew-* 文件
|
|
105
|
+
if (fs.existsSync(destDir)) {
|
|
106
|
+
const destEntries = fs.readdirSync(destDir, { withFileTypes: true });
|
|
107
|
+
for (const entry of destEntries) {
|
|
108
|
+
if (!entry.isFile()) continue;
|
|
109
|
+
if (!isSpeccrewFile(entry.name)) continue;
|
|
110
|
+
|
|
111
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
112
|
+
if (!fs.existsSync(srcPath)) {
|
|
113
|
+
stats.extra.push(entry.name);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 递归更新 skills 目录
|
|
120
|
+
function updateSkillsRecursive(srcDir, destDir, stats, currentRelPath = '') {
|
|
121
|
+
if (!fs.existsSync(srcDir)) return;
|
|
122
|
+
|
|
123
|
+
// 确保目标目录存在
|
|
124
|
+
if (!fs.existsSync(destDir)) {
|
|
125
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
129
|
+
for (const entry of entries) {
|
|
130
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
131
|
+
const destPath = path.join(destDir, entry.name);
|
|
132
|
+
const relPath = currentRelPath ? path.join(currentRelPath, entry.name) : entry.name;
|
|
133
|
+
|
|
134
|
+
if (entry.isDirectory()) {
|
|
135
|
+
// 只处理 speccrew-* 前缀的目录
|
|
136
|
+
if (!isSpeccrewFile(entry.name)) continue;
|
|
137
|
+
|
|
138
|
+
const dirStats = { added: 0, updated: 0 };
|
|
139
|
+
updateSkillsRecursive(srcPath, destPath, stats, relPath);
|
|
140
|
+
} else {
|
|
141
|
+
// 在 speccrew-* 目录下的文件
|
|
142
|
+
// 检查是否在 speccrew-* 父目录下
|
|
143
|
+
const parentDir = path.basename(srcDir);
|
|
144
|
+
if (!isSpeccrewFile(parentDir)) continue;
|
|
145
|
+
|
|
146
|
+
const result = copyFileIfChanged(srcPath, destPath);
|
|
147
|
+
if (result.isNew) {
|
|
148
|
+
stats.added++;
|
|
149
|
+
} else if (result.copied) {
|
|
150
|
+
stats.updated++;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 检测目标目录中多余的 speccrew-* 目录或文件
|
|
156
|
+
if (fs.existsSync(destDir)) {
|
|
157
|
+
const parentDir = path.basename(srcDir);
|
|
158
|
+
const inSpeccrewDir = isSpeccrewFile(parentDir);
|
|
159
|
+
|
|
160
|
+
const destEntries = fs.readdirSync(destDir, { withFileTypes: true });
|
|
161
|
+
for (const entry of destEntries) {
|
|
162
|
+
if (!isSpeccrewFile(entry.name)) continue;
|
|
163
|
+
|
|
164
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
165
|
+
if (!fs.existsSync(srcPath)) {
|
|
166
|
+
const extraPath = currentRelPath ? path.join(currentRelPath, entry.name) : entry.name;
|
|
167
|
+
if (entry.isDirectory()) {
|
|
168
|
+
stats.extraDirs.push(extraPath);
|
|
169
|
+
} else if (inSpeccrewDir) {
|
|
170
|
+
stats.extra.push(extraPath);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 更新 skills 目录(入口)
|
|
178
|
+
function updateSkills(srcDir, destDir, stats) {
|
|
179
|
+
if (!fs.existsSync(srcDir)) return;
|
|
180
|
+
|
|
181
|
+
// 确保目标目录存在
|
|
182
|
+
if (!fs.existsSync(destDir)) {
|
|
183
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 遍历源目录中的 speccrew-* 技能目录
|
|
187
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
188
|
+
for (const entry of entries) {
|
|
189
|
+
if (!entry.isDirectory()) continue;
|
|
190
|
+
if (!isSpeccrewFile(entry.name)) continue;
|
|
191
|
+
|
|
192
|
+
const srcSkillDir = path.join(srcDir, entry.name);
|
|
193
|
+
const destSkillDir = path.join(destDir, entry.name);
|
|
194
|
+
|
|
195
|
+
// 检查是否是新增的技能
|
|
196
|
+
const isNewSkill = !fs.existsSync(destSkillDir);
|
|
197
|
+
|
|
198
|
+
// 递归复制技能目录
|
|
199
|
+
const skillStats = { added: 0, updated: 0, extra: [], extraDirs: [] };
|
|
200
|
+
updateSkillsRecursive(srcSkillDir, destSkillDir, skillStats, entry.name);
|
|
201
|
+
|
|
202
|
+
if (isNewSkill) {
|
|
203
|
+
// 如果是全新技能,计算文件数量作为 added
|
|
204
|
+
const files = getAllFiles(destSkillDir);
|
|
205
|
+
stats.added += files.length;
|
|
206
|
+
} else {
|
|
207
|
+
stats.added += skillStats.added;
|
|
208
|
+
stats.updated += skillStats.updated;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
stats.extra.push(...skillStats.extra);
|
|
212
|
+
stats.extraDirs.push(...skillStats.extraDirs);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 检测目标目录中多余的 speccrew-* 技能目录
|
|
216
|
+
const destEntries = fs.readdirSync(destDir, { withFileTypes: true });
|
|
217
|
+
for (const entry of destEntries) {
|
|
218
|
+
if (!entry.isDirectory()) continue;
|
|
219
|
+
if (!isSpeccrewFile(entry.name)) continue;
|
|
220
|
+
|
|
221
|
+
const srcSkillDir = path.join(srcDir, entry.name);
|
|
222
|
+
if (!fs.existsSync(srcSkillDir)) {
|
|
223
|
+
stats.extraDirs.push(entry.name);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 更新 workspace docs
|
|
229
|
+
function updateWorkspaceDocs(srcDir, destDir, stats) {
|
|
230
|
+
if (!fs.existsSync(srcDir)) return;
|
|
231
|
+
|
|
232
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
233
|
+
for (const entry of entries) {
|
|
234
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
235
|
+
const destPath = path.join(destDir, entry.name);
|
|
236
|
+
|
|
237
|
+
if (entry.isDirectory()) {
|
|
238
|
+
const subStats = { updated: 0 };
|
|
239
|
+
updateWorkspaceDocs(srcPath, destPath, subStats);
|
|
240
|
+
stats.updated += subStats.updated;
|
|
241
|
+
} else {
|
|
242
|
+
const result = copyFileIfChanged(srcPath, destPath);
|
|
243
|
+
if (result.copied || result.isNew) {
|
|
244
|
+
stats.updated++;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// 主函数
|
|
251
|
+
function run() {
|
|
252
|
+
try {
|
|
253
|
+
const args = parseArgs();
|
|
254
|
+
const projectRoot = process.cwd();
|
|
255
|
+
|
|
256
|
+
// 读取 .speccrewrc
|
|
257
|
+
const rc = readSpeccrewRC(projectRoot);
|
|
258
|
+
if (!rc) {
|
|
259
|
+
console.error('Error: .speccrewrc not found. Please run "speccrew init" first.');
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// 获取版本信息
|
|
264
|
+
const currentVersion = getPackageVersion();
|
|
265
|
+
const installedVersion = rc.version || 'unknown';
|
|
266
|
+
|
|
267
|
+
// 解析 IDE 列表
|
|
268
|
+
let ides;
|
|
269
|
+
if (args.ide) {
|
|
270
|
+
ides = [getIDEConfig(args.ide)];
|
|
271
|
+
} else if (rc.ide) {
|
|
272
|
+
const ideIds = Array.isArray(rc.ide) ? rc.ide : [rc.ide];
|
|
273
|
+
ides = ideIds.map(id => getIDEConfig(id));
|
|
274
|
+
} else {
|
|
275
|
+
console.error('Error: No IDE specified. Use --ide <name> or set in .speccrewrc');
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const sourceRoot = getSourceRoot();
|
|
280
|
+
const workspaceTemplatePath = getWorkspaceTemplatePath();
|
|
281
|
+
|
|
282
|
+
// 统计信息
|
|
283
|
+
const totalStats = {
|
|
284
|
+
agents: { updated: 0, added: 0 },
|
|
285
|
+
skills: { updated: 0, added: 0 },
|
|
286
|
+
workspaceDocs: { updated: 0 },
|
|
287
|
+
extra: [],
|
|
288
|
+
extraDirs: [],
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// 对每个 IDE 执行更新
|
|
292
|
+
for (const ide of ides) {
|
|
293
|
+
// 更新 agents
|
|
294
|
+
const srcAgentsDir = path.join(sourceRoot, 'agents');
|
|
295
|
+
const destAgentsDir = path.join(projectRoot, ide.agentsDir);
|
|
296
|
+
const agentStats = { updated: 0, added: 0, extra: [] };
|
|
297
|
+
updateAgents(srcAgentsDir, destAgentsDir, agentStats);
|
|
298
|
+
totalStats.agents.updated += agentStats.updated;
|
|
299
|
+
totalStats.agents.added += agentStats.added;
|
|
300
|
+
totalStats.extra.push(...agentStats.extra);
|
|
301
|
+
|
|
302
|
+
// 更新 skills
|
|
303
|
+
const srcSkillsDir = path.join(sourceRoot, 'skills');
|
|
304
|
+
const destSkillsDir = path.join(projectRoot, ide.skillsDir);
|
|
305
|
+
const skillStats = { updated: 0, added: 0, extra: [], extraDirs: [] };
|
|
306
|
+
updateSkills(srcSkillsDir, destSkillsDir, skillStats);
|
|
307
|
+
totalStats.skills.updated += skillStats.updated;
|
|
308
|
+
totalStats.skills.added += skillStats.added;
|
|
309
|
+
totalStats.extra.push(...skillStats.extra);
|
|
310
|
+
totalStats.extraDirs.push(...skillStats.extraDirs);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// 更新 workspace docs
|
|
314
|
+
const srcDocsDir = path.join(workspaceTemplatePath, 'docs');
|
|
315
|
+
const destDocsDir = path.join(projectRoot, 'speccrew-workspace', 'docs');
|
|
316
|
+
const docsStats = { updated: 0 };
|
|
317
|
+
updateWorkspaceDocs(srcDocsDir, destDocsDir, docsStats);
|
|
318
|
+
totalStats.workspaceDocs.updated = docsStats.updated;
|
|
319
|
+
|
|
320
|
+
// 更新 .speccrewrc
|
|
321
|
+
rc.version = currentVersion;
|
|
322
|
+
rc.updatedAt = new Date().toISOString();
|
|
323
|
+
writeSpeccrewRC(projectRoot, rc);
|
|
324
|
+
|
|
325
|
+
// 输出结果
|
|
326
|
+
if (installedVersion === currentVersion) {
|
|
327
|
+
console.log(`Already up to date: v${currentVersion}\n`);
|
|
328
|
+
} else {
|
|
329
|
+
console.log(`SpecCrew updated: v${installedVersion} → v${currentVersion}\n`);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
console.log(`Agents: ${totalStats.agents.updated} updated, ${totalStats.agents.added} added`);
|
|
333
|
+
console.log(`Skills: ${totalStats.skills.updated} updated, ${totalStats.skills.added} added`);
|
|
334
|
+
console.log(`Workspace docs: ${totalStats.workspaceDocs.updated} updated`);
|
|
335
|
+
|
|
336
|
+
// 输出警告
|
|
337
|
+
const allExtras = [...new Set([...totalStats.extra, ...totalStats.extraDirs])];
|
|
338
|
+
if (allExtras.length > 0) {
|
|
339
|
+
console.log('\nWarning: The following installed items are not in the current version:');
|
|
340
|
+
for (const item of allExtras) {
|
|
341
|
+
console.log(` - ${item} (user-created, skipped)`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
} catch (error) {
|
|
346
|
+
console.error(`Error: ${error.message}`);
|
|
347
|
+
process.exit(1);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
module.exports = { run };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const IDE_CONFIGS = {
|
|
5
|
+
qoder: {
|
|
6
|
+
name: 'Qoder',
|
|
7
|
+
baseDir: '.qoder',
|
|
8
|
+
skillsDir: '.qoder/skills',
|
|
9
|
+
agentsDir: '.qoder/agents',
|
|
10
|
+
},
|
|
11
|
+
cursor: {
|
|
12
|
+
name: 'Cursor',
|
|
13
|
+
baseDir: '.cursor',
|
|
14
|
+
skillsDir: '.cursor/skills',
|
|
15
|
+
agentsDir: '.cursor/agents',
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// 自动检测项目根目录下存在的 IDE 目录
|
|
20
|
+
function detectIDE(projectRoot) {
|
|
21
|
+
const detected = [];
|
|
22
|
+
for (const [key, config] of Object.entries(IDE_CONFIGS)) {
|
|
23
|
+
const basePath = path.join(projectRoot, config.baseDir);
|
|
24
|
+
if (fs.existsSync(basePath)) {
|
|
25
|
+
detected.push({ id: key, ...config });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return detected;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 获取指定 IDE 的配置
|
|
32
|
+
function getIDEConfig(ideId) {
|
|
33
|
+
const config = IDE_CONFIGS[ideId];
|
|
34
|
+
if (!config) {
|
|
35
|
+
const validIds = Object.keys(IDE_CONFIGS).join(', ');
|
|
36
|
+
throw new Error(`Unknown IDE: ${ideId}. Valid options: ${validIds}`);
|
|
37
|
+
}
|
|
38
|
+
return { id: ideId, ...config };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 解析 IDE 参数:优先用 --ide 参数,其次自动检测,最后从 .speccrewrc 读取
|
|
42
|
+
function resolveIDE(projectRoot, cliIdeArg) {
|
|
43
|
+
// 1. CLI 参数优先
|
|
44
|
+
if (cliIdeArg) {
|
|
45
|
+
return [getIDEConfig(cliIdeArg)];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 2. 从 .speccrewrc 读取
|
|
49
|
+
const rcPath = path.join(projectRoot, '.speccrewrc');
|
|
50
|
+
if (fs.existsSync(rcPath)) {
|
|
51
|
+
try {
|
|
52
|
+
const rc = JSON.parse(fs.readFileSync(rcPath, 'utf8'));
|
|
53
|
+
if (rc.ide) {
|
|
54
|
+
const ides = Array.isArray(rc.ide) ? rc.ide : [rc.ide];
|
|
55
|
+
return ides.map(id => getIDEConfig(id));
|
|
56
|
+
}
|
|
57
|
+
} catch (e) {
|
|
58
|
+
// ignore malformed .speccrewrc
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 3. 自动检测
|
|
63
|
+
const detected = detectIDE(projectRoot);
|
|
64
|
+
if (detected.length === 0) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
'No supported IDE detected. Please specify with --ide <name>.\n' +
|
|
67
|
+
`Supported IDEs: ${Object.keys(IDE_CONFIGS).join(', ')}`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return detected;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = { IDE_CONFIGS, detectIDE, getIDEConfig, resolveIDE };
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
// 递归复制目录,支持过滤函数
|
|
5
|
+
function copyDirRecursive(src, dest, filter) {
|
|
6
|
+
if (!fs.existsSync(src)) return { copied: 0, skipped: 0 };
|
|
7
|
+
|
|
8
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
9
|
+
let copied = 0, skipped = 0;
|
|
10
|
+
|
|
11
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
12
|
+
for (const entry of entries) {
|
|
13
|
+
const srcPath = path.join(src, entry.name);
|
|
14
|
+
const destPath = path.join(dest, entry.name);
|
|
15
|
+
|
|
16
|
+
if (filter && !filter(entry.name, srcPath, entry.isDirectory())) {
|
|
17
|
+
skipped++;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (entry.isDirectory()) {
|
|
22
|
+
const sub = copyDirRecursive(srcPath, destPath, filter);
|
|
23
|
+
copied += sub.copied;
|
|
24
|
+
skipped += sub.skipped;
|
|
25
|
+
} else {
|
|
26
|
+
fs.copyFileSync(srcPath, destPath);
|
|
27
|
+
copied++;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return { copied, skipped };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 判断是否 speccrew-* 前缀的文件/目录
|
|
34
|
+
function isSpeccrewFile(name) {
|
|
35
|
+
return name.startsWith('speccrew-');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 读取 .speccrewrc 配置
|
|
39
|
+
function readSpeccrewRC(projectRoot) {
|
|
40
|
+
const rcPath = path.join(projectRoot, '.speccrewrc');
|
|
41
|
+
if (!fs.existsSync(rcPath)) return null;
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(fs.readFileSync(rcPath, 'utf8'));
|
|
44
|
+
} catch (e) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 写入 .speccrewrc 配置
|
|
50
|
+
function writeSpeccrewRC(projectRoot, config) {
|
|
51
|
+
const rcPath = path.join(projectRoot, '.speccrewrc');
|
|
52
|
+
fs.writeFileSync(rcPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 获取包版本
|
|
56
|
+
function getPackageVersion() {
|
|
57
|
+
const pkgPath = path.join(__dirname, '..', 'package.json');
|
|
58
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
59
|
+
return pkg.version;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 获取包内 .speccrew 源文件目录
|
|
63
|
+
function getSourceRoot() {
|
|
64
|
+
return path.join(__dirname, '..', '.speccrew');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 获取包内 workspace-template 目录
|
|
68
|
+
function getWorkspaceTemplatePath() {
|
|
69
|
+
return path.join(__dirname, '..', 'workspace-template');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 递归创建目录结构(数组形式)
|
|
73
|
+
function ensureDirectories(baseDir, dirs) {
|
|
74
|
+
for (const dir of dirs) {
|
|
75
|
+
fs.mkdirSync(path.join(baseDir, dir), { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 递归删除目录
|
|
80
|
+
function removeDirRecursive(dirPath) {
|
|
81
|
+
if (!fs.existsSync(dirPath)) return;
|
|
82
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
83
|
+
for (const entry of entries) {
|
|
84
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
85
|
+
if (entry.isDirectory()) {
|
|
86
|
+
removeDirRecursive(fullPath);
|
|
87
|
+
} else {
|
|
88
|
+
fs.unlinkSync(fullPath);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
fs.rmdirSync(dirPath);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = {
|
|
95
|
+
copyDirRecursive,
|
|
96
|
+
isSpeccrewFile,
|
|
97
|
+
readSpeccrewRC,
|
|
98
|
+
writeSpeccrewRC,
|
|
99
|
+
getPackageVersion,
|
|
100
|
+
getSourceRoot,
|
|
101
|
+
getWorkspaceTemplatePath,
|
|
102
|
+
ensureDirectories,
|
|
103
|
+
removeDirRecursive,
|
|
104
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "speccrew",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Spec-Driven Development toolkit for AI-powered IDEs",
|
|
5
|
+
"author": "charlesmu99",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/charlesmu99/speccrew.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/charlesmu99/speccrew",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/charlesmu99/speccrew/issues"
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"speccrew": "./bin/cli.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"bin/",
|
|
19
|
+
"lib/",
|
|
20
|
+
".speccrew/",
|
|
21
|
+
"workspace-template/"
|
|
22
|
+
],
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=16.0.0"
|
|
25
|
+
},
|
|
26
|
+
"keywords": ["spec-driven", "ai-ide", "development-toolkit", "agents", "skills"],
|
|
27
|
+
"license": "MIT"
|
|
28
|
+
}
|