jsharness 1.4.0 → 1.5.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/bin/jsharness.js +4 -2
- package/lib/index.mjs +122 -4
- package/package.json +1 -1
package/bin/jsharness.js
CHANGED
|
@@ -13,15 +13,17 @@
|
|
|
13
13
|
* npx jsharness openspec list # 列出 OpenSpec 变更
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { fileURLToPath } from 'url';
|
|
16
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
17
17
|
import path from 'path';
|
|
18
18
|
|
|
19
19
|
// 关键:用相对路径引用主库,不用 import('jsharness') 包名
|
|
20
20
|
// 这样在 npx 临时安装环境中也能可靠定位模块
|
|
21
|
+
// Windows 上必须用 file:// URL 格式,不能用 C:\path 格式
|
|
21
22
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
22
23
|
const libPath = path.join(__dirname, '..', 'lib', 'index.mjs');
|
|
24
|
+
const libURL = pathToFileURL(libPath).href;
|
|
23
25
|
|
|
24
|
-
const { runInit, listTools, showStatus, listOpenSpecChanges, archiveOpenSpecChange } = await import(
|
|
26
|
+
const { runInit, listTools, showStatus, listOpenSpecChanges, archiveOpenSpecChange } = await import(libURL);
|
|
25
27
|
|
|
26
28
|
// 读取版本号
|
|
27
29
|
import { createRequire } from 'module';
|
package/lib/index.mjs
CHANGED
|
@@ -584,6 +584,100 @@ function downloadFile(url, destPath) {
|
|
|
584
584
|
});
|
|
585
585
|
}
|
|
586
586
|
|
|
587
|
+
// ============================================================
|
|
588
|
+
// 复制 .harness/ 完整目录到目标项目
|
|
589
|
+
// ============================================================
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* 将源 .harness/ 目录递归复制到目标项目的 .harness/ 目录
|
|
593
|
+
*
|
|
594
|
+
* 包含所有子目录:rules/, skills/, agents/, gate/, workflow/, mcp/,
|
|
595
|
+
* config/, dev-map/, docs/, specs/ 以及根级文件 README.md 等
|
|
596
|
+
*
|
|
597
|
+
* @param {string} sourceHarnessDir - 源 .harness/ 目录
|
|
598
|
+
* @param {string} projectDir - 目标项目根目录
|
|
599
|
+
* @param {object} options
|
|
600
|
+
* @param {boolean} [options.force] - 是否覆盖已有文件
|
|
601
|
+
* @param {boolean} [options.verbose]
|
|
602
|
+
* @param {string} [options.stack] - 技术栈过滤(影响 rules/project/ 下的文件)
|
|
603
|
+
* @returns {{ copied: string[], skipped: string[] }}
|
|
604
|
+
*/
|
|
605
|
+
function copyHarnessToProject(sourceHarnessDir, projectDir, options = {}) {
|
|
606
|
+
const { force = false, verbose = false, stack = 'all' } = options;
|
|
607
|
+
const targetHarnessDir = path.join(projectDir, '.harness');
|
|
608
|
+
const copied = [];
|
|
609
|
+
const skipped = [];
|
|
610
|
+
|
|
611
|
+
// 需要跳过的文件/目录(二进制、临时文件等)
|
|
612
|
+
const SKIP_ENTRIES = new Set(['.git', 'node_modules', '.DS_Store', 'Thumbs.db']);
|
|
613
|
+
|
|
614
|
+
// 技术栈过滤:rules/project/ 下只复制匹配的文件
|
|
615
|
+
function shouldCopyFile(relPath) {
|
|
616
|
+
if (stack === 'all') return true;
|
|
617
|
+
|
|
618
|
+
const lowerRel = relPath.toLowerCase().replace(/\\/g, '/');
|
|
619
|
+
|
|
620
|
+
// rules/project/ 下的文件需要按技术栈过滤
|
|
621
|
+
if (lowerRel.includes('rules/project/')) {
|
|
622
|
+
if (stack === 'vue3') {
|
|
623
|
+
return lowerRel.includes('vue') || lowerRel.includes('frontend') || lowerRel.includes('web');
|
|
624
|
+
}
|
|
625
|
+
if (stack === 'java') {
|
|
626
|
+
return lowerRel.includes('java') || lowerRel.includes('backend');
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
return true;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function copyRecursive(srcDir, destDir, relBase) {
|
|
634
|
+
if (!fs.existsSync(srcDir)) return;
|
|
635
|
+
|
|
636
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
637
|
+
for (const entry of entries) {
|
|
638
|
+
if (SKIP_ENTRIES.has(entry.name)) continue;
|
|
639
|
+
|
|
640
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
641
|
+
const relPath = relBase ? `${relBase}/${entry.name}` : entry.name;
|
|
642
|
+
const destPath = path.join(destDir, entry.name);
|
|
643
|
+
|
|
644
|
+
if (entry.isDirectory()) {
|
|
645
|
+
// 递归复制子目录
|
|
646
|
+
if (!fs.existsSync(destPath)) {
|
|
647
|
+
fs.mkdirSync(destPath, { recursive: true });
|
|
648
|
+
}
|
|
649
|
+
copyRecursive(srcPath, destPath, relPath);
|
|
650
|
+
} else {
|
|
651
|
+
// 文件:检查是否需要复制
|
|
652
|
+
if (!shouldCopyFile(relPath)) {
|
|
653
|
+
if (verbose) console.log(` ⏭ 技术栈过滤跳过: ${relPath}`);
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (fs.existsSync(destPath) && !force) {
|
|
658
|
+
skipped.push(`.harness/${relPath}`);
|
|
659
|
+
if (verbose) console.log(` ⏭ 跳过 (已存在): .harness/${relPath}`);
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// 确保目标目录存在
|
|
664
|
+
const destFileDir = path.dirname(destPath);
|
|
665
|
+
if (!fs.existsSync(destFileDir)) {
|
|
666
|
+
fs.mkdirSync(destFileDir, { recursive: true });
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
fs.copyFileSync(srcPath, destPath);
|
|
670
|
+
copied.push(`.harness/${relPath}`);
|
|
671
|
+
if (verbose) console.log(` ✅ 复制: .harness/${relPath}`);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
copyRecursive(sourceHarnessDir, targetHarnessDir, '');
|
|
677
|
+
|
|
678
|
+
return { copied, skipped };
|
|
679
|
+
}
|
|
680
|
+
|
|
587
681
|
// ============================================================
|
|
588
682
|
// 扫描 Harness 源文件
|
|
589
683
|
// ============================================================
|
|
@@ -782,7 +876,17 @@ export async function runInit(projectDir, options = {}) {
|
|
|
782
876
|
if (!stack) stack = await promptSelectStack();
|
|
783
877
|
console.log(`\n🏗️ 技术栈: ${stackLabel(stack)}\n`);
|
|
784
878
|
|
|
785
|
-
// 4.
|
|
879
|
+
// 4. 复制 .harness/ 完整目录到目标项目(rules/skills/agents/gate/workflow/... 全部写入)
|
|
880
|
+
console.log(`📂 复制 .harness/ 完整目录到项目...`);
|
|
881
|
+
const harnessCopyResult = copyHarnessToProject(harnessDir, projectDir, { force, verbose, stack });
|
|
882
|
+
if (harnessCopyResult.copied.length > 0) {
|
|
883
|
+
console.log(` ✅ 复制 ${harnessCopyResult.copied.length} 个文件/目录到 .harness/`);
|
|
884
|
+
}
|
|
885
|
+
if (harnessCopyResult.skipped.length > 0) {
|
|
886
|
+
console.log(` ⏭ 跳过 ${harnessCopyResult.skipped.length} 个 (已存在)`);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// 5. 扫描源文件(用于注入到 AI 工具特有目录)
|
|
786
890
|
const allRuleFiles = scanHarnessRules(harnessDir, stack);
|
|
787
891
|
const allSkillFiles = scanHarnessSkills(harnessDir, stack);
|
|
788
892
|
|
|
@@ -790,7 +894,7 @@ export async function runInit(projectDir, options = {}) {
|
|
|
790
894
|
console.log(`📋 扫描结果: ${allRuleFiles.length} 个规则, ${allSkillFiles.length} 个技能\n`);
|
|
791
895
|
}
|
|
792
896
|
|
|
793
|
-
//
|
|
897
|
+
// 6. 对每个目标工具执行注入(写入 AI 工具特有目录如 .codebuddy/ .claude/ 等)
|
|
794
898
|
const summary = [];
|
|
795
899
|
|
|
796
900
|
for (const tool of targetTools) {
|
|
@@ -812,7 +916,7 @@ export async function runInit(projectDir, options = {}) {
|
|
|
812
916
|
summary.push({ tool: tool.name, written, skipped });
|
|
813
917
|
}
|
|
814
918
|
|
|
815
|
-
//
|
|
919
|
+
// 7. 初始化 OpenSpec
|
|
816
920
|
console.log('\n━━━ 初始化 OpenSpec ━━━');
|
|
817
921
|
const openspecResult = initOpenSpec(projectDir, { force, verbose });
|
|
818
922
|
if (openspecResult.created.length > 0) {
|
|
@@ -823,10 +927,24 @@ export async function runInit(projectDir, options = {}) {
|
|
|
823
927
|
console.log(` ⏭ 跳过 ${openspecResult.skipped.length} 项 (已存在)`);
|
|
824
928
|
}
|
|
825
929
|
|
|
826
|
-
//
|
|
930
|
+
// 8. 输出总结
|
|
827
931
|
console.log('\n═════════════════════════════');
|
|
828
932
|
console.log('✅ 初始化完成!');
|
|
829
933
|
|
|
934
|
+
// .harness/ 完整目录
|
|
935
|
+
if (harnessCopyResult.copied.length > 0) {
|
|
936
|
+
console.log(`\n [.harness/ 完整目录]`);
|
|
937
|
+
console.log(` ✅ 复制 ${harnessCopyResult.copied.length} 个文件到 .harness/`);
|
|
938
|
+
if (verbose) {
|
|
939
|
+
harnessCopyResult.copied.forEach(f => console.log(` - ${f}`));
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
if (harnessCopyResult.skipped.length > 0 && verbose) {
|
|
943
|
+
console.log(` ⏭ 跳过 ${harnessCopyResult.skipped.length} 个 (.harness/ 中已存在):`);
|
|
944
|
+
harnessCopyResult.skipped.forEach(f => console.log(` - ${f}`));
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// AI 工具特有目录注入
|
|
830
948
|
for (const s of summary) {
|
|
831
949
|
if (s.written.length > 0) {
|
|
832
950
|
console.log(`\n [${s.tool}]`);
|