jsharness 1.4.1 → 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.
Files changed (2) hide show
  1. package/lib/index.mjs +122 -4
  2. package/package.json +1 -1
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
- // 5. 对每个目标工具执行注入
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
- // 6. 初始化 OpenSpec
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
- // 7. 输出总结
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}]`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsharness",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "description": "Harness Engineering - AI 编程行为工程化管控系统。将 rules/skills/gate/agents 一键注入到 CodeBuddy、Cursor、Copilot 等 AI 工具中。",
5
5
  "main": "lib/index.mjs",
6
6
  "bin": {