jsharness 1.0.2 → 1.1.1

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 CHANGED
@@ -1,14 +1,16 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  * jsharness CLI
5
5
  *
6
6
  * Usage:
7
- * npx jsharness init # 初始化(自动检测 AI 工具)
8
- * npx jsharness init --tool codebuddy # 指定工具
9
- * npx jsharness init --stack vue3 # 指定技术栈
10
- * npx jsharness list-tools # 列出支持的工具
11
- * npx jsharness status # 查看当前状态
7
+ * npx jsharness init # 交互式初始化(选择工具+技术栈)
8
+ * npx jsharness init --tool codebuddy # 指定工具(跳过工具选择)
9
+ * npx jsharness init --stack vue3 # 指定技术栈(跳过技术栈选择)
10
+ * npx jsharness init --tool cursor --stack all # 全部指定,无交互
11
+ * npx jsharness list-tools # 列出支持的工具
12
+ * npx jsharness status # 查看当前状态
13
+ * npx jsharness openspec list # 列出 OpenSpec 变更
12
14
  */
13
15
 
14
16
  import { createRequire } from 'module';
@@ -17,7 +19,7 @@ import { program } from 'commander';
17
19
  const require = createRequire(import.meta.url);
18
20
 
19
21
  // 通过包名引用主库(npm 安装后兼容)
20
- const { runInit, listTools, showStatus } = await import('jsharness');
22
+ const { runInit, listTools, showStatus, listOpenSpecChanges, archiveOpenSpecChange } = await import('jsharness');
21
23
 
22
24
  program
23
25
  .name('jsharness')
@@ -27,8 +29,8 @@ program
27
29
  program
28
30
  .command('init')
29
31
  .description('初始化 Harness 到当前项目的 AI 工具中')
30
- .option('-t, --tool <name>', '指定目标 AI 工具')
31
- .option('-s, --stack <name>', '指定技术栈 (vue3/java/all)', 'all')
32
+ .option('-t, --tool <name>', '指定目标 AI 工具(跳过交互选择)')
33
+ .option('-s, --stack <name>', '指定技术栈 vue3/java/all(跳过交互选择)')
32
34
  .option('--rules-only', '只注入规则,不注入技能')
33
35
  .option('--skills-only', '只注入技能,不注入规则')
34
36
  .option('--force', '覆盖已有配置')
@@ -45,6 +47,22 @@ program
45
47
  .description('查看当前项目 Harness 初始化状态')
46
48
  .action(() => showStatus(process.cwd()));
47
49
 
50
+ // OpenSpec 子命令
51
+ const openspecCmd = program
52
+ .command('openspec')
53
+ .description('OpenSpec 变更管理');
54
+
55
+ openspecCmd
56
+ .command('list')
57
+ .description('列出所有 OpenSpec 变更')
58
+ .action(() => listOpenSpecChanges(process.cwd()));
59
+
60
+ openspecCmd
61
+ .command('archive')
62
+ .description('归档一个已完成的 OpenSpec 变更')
63
+ .requiredOption('-c, --change <name>', '变更名称')
64
+ .action((opts) => archiveOpenSpecChange(process.cwd(), opts.change));
65
+
48
66
  program.parse(process.argv);
49
67
 
50
68
  // 无参数时默认执行 init
package/lib/index.mjs CHANGED
@@ -7,6 +7,7 @@
7
7
  import fs from 'fs';
8
8
  import path from 'path';
9
9
  import { fileURLToPath } from 'url';
10
+ import readline from 'readline';
10
11
 
11
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
13
 
@@ -595,10 +596,110 @@ export function injectOutputs(projectDir, outputs, options = {}) {
595
596
  // CLI 命令实现
596
597
  // ============================================================
597
598
 
599
+ // ============================================================
600
+ // 交互式问答辅助函数
601
+ // ============================================================
602
+
603
+ /**
604
+ * 通用 readline 提问
605
+ */
606
+ function askQuestion(query) {
607
+ const rl = readline.createInterface({
608
+ input: process.stdin,
609
+ output: process.stdout,
610
+ });
611
+ return new Promise((resolve) => {
612
+ rl.question(query, (answer) => {
613
+ rl.close();
614
+ resolve(answer.trim());
615
+ });
616
+ });
617
+ }
618
+
619
+ /**
620
+ * 交互式选择 AI 编程工具
621
+ *
622
+ * 先自动检测,检测结果让用户确认或修改;
623
+ * 检测不到则展示列表让用户选择(支持多选,逗号分隔)。
624
+ *
625
+ * @param {string} projectDir
626
+ * @returns {Promise<string[]>} 选中的工具 ID 列表
627
+ */
628
+ async function promptSelectTools(projectDir) {
629
+ const detected = detectTool(projectDir);
630
+
631
+ console.log('━━━ 选择 AI 编程工具 ━━━\n');
632
+
633
+ // 展示所有支持的工具
634
+ SUPPORTED_TOOLS.forEach((t, i) => {
635
+ const isDetected = detected.some(d => d.id === t.id);
636
+ console.log(` ${String(i + 1).padStart(2)}. ${isDetected ? '✅' : '○ '} ${t.name}`);
637
+ console.log(` ${t.description}`);
638
+ });
639
+
640
+ if (detected.length > 0) {
641
+ console.log(`\n 🔍 自动检测到: ${detected.map(t => t.name).join(', ')}`);
642
+ console.log(` 按回车使用检测结果,或输入编号选择其他工具\n`);
643
+ } else {
644
+ console.log('\n 未自动检测到 AI 工具,请手动选择\n');
645
+ }
646
+
647
+ const answer = await askQuestion(' 请选择 (多选用逗号分隔,如 1,3): ');
648
+
649
+ // 回车 = 使用检测结果
650
+ if (!answer && detected.length > 0) {
651
+ return detected.map(t => t.id);
652
+ }
653
+
654
+ // 解析用户输入的编号
655
+ const indices = answer.split(/[,,\s]+/).map(s => parseInt(s, 10)).filter(n => !isNaN(n));
656
+ const selected = [];
657
+ for (const idx of indices) {
658
+ if (idx >= 1 && idx <= SUPPORTED_TOOLS.length) {
659
+ selected.push(SUPPORTED_TOOLS[idx - 1].id);
660
+ }
661
+ }
662
+
663
+ if (selected.length === 0) {
664
+ console.log(' ⚠️ 无有效选择,默认使用 CodeBuddy 格式\n');
665
+ return [SUPPORTED_TOOLS[0].id];
666
+ }
667
+
668
+ return selected;
669
+ }
670
+
671
+ /**
672
+ * 交互式选择技术栈
673
+ *
674
+ * @returns {Promise<string>} 'vue3' | 'java' | 'all'
675
+ */
676
+ async function promptSelectStack() {
677
+ console.log('━━━ 选择项目技术栈 ━━━\n');
678
+ console.log(' 1. 🖥️ 前端 (Vue3 + TypeScript + Element Plus)');
679
+ console.log(' 2. ☕ 后端 (Spring Boot + JDK21 + MyBatis-Plus)');
680
+ console.log(' 3. 🔗 前后端一起\n');
681
+
682
+ const answer = await askQuestion(' 请选择 [1/2/3]: ');
683
+
684
+ const map = { '1': 'vue3', '2': 'java', '3': 'all' };
685
+ return map[answer] || 'all';
686
+ }
687
+
688
+ function stackLabel(stack) {
689
+ const labels = {
690
+ vue3: '前端 (Vue3 + TypeScript + Element Plus)',
691
+ java: '后端 (Spring Boot + JDK21 + MyBatis-Plus)',
692
+ all: '前后端一起',
693
+ };
694
+ return labels[stack] || stack;
695
+ }
696
+
697
+
698
+
598
699
  export async function runInit(projectDir, options = {}) {
599
700
  const {
600
701
  tool: requestedTool,
601
- stack = 'all',
702
+ stack: requestedStack,
602
703
  rulesOnly = false,
603
704
  skillsOnly = false,
604
705
  force = false,
@@ -619,7 +720,6 @@ export async function runInit(projectDir, options = {}) {
619
720
  console.error(' 然后重新运行:');
620
721
  console.error(' npx jsharness init --verbose');
621
722
  console.error('');
622
- // 输出调试信息
623
723
  const nmPath = path.join(projectDir, 'node_modules', 'jsharness', '.harness');
624
724
  console.error(` [DEBUG] 项目目录: ${projectDir}`);
625
725
  console.error(` [DEBUG] node_modules/jsharness/.harness 存在: ${fs.existsSync(nmPath) ? '是' : '否'}`);
@@ -627,33 +727,39 @@ export async function runInit(projectDir, options = {}) {
627
727
  }
628
728
  if (verbose) console.log(` 📂 Harness 源: ${harnessDir}`);
629
729
 
630
- // 2. 检测或指定 AI 工具
730
+ // 2. 交互式选择 AI 工具
631
731
  let targetTools = [];
632
- if (requestedTool) {
633
- const found = SUPPORTED_TOOLS.find(t => t.id === requestedTool || t.name.includes(requestedTool));
732
+ const selectedToolIds = requestedTool
733
+ ? [requestedTool]
734
+ : await promptSelectTools(projectDir);
735
+
736
+ for (const tid of selectedToolIds) {
737
+ const found = SUPPORTED_TOOLS.find(t => t.id === tid || t.name.includes(tid));
634
738
  if (found) {
635
- targetTools = [{ ...found, detected: true }];
636
- } else {
637
- console.error(`❌ 不支持的工具: ${requestedTool}`);
638
- console.log(` 可用工具: ${SUPPORTED_TOOLS.map(t => t.id).join(', ')}`);
639
- process.exit(1);
640
- }
641
- } else {
642
- targetTools = detectTool(projectDir);
643
- if (targetTools.length === 0) {
644
- console.log('⚠️ 未自动检测到 AI 工具。将使用默认模式(CodeBuddy 格式)。\n');
645
- console.log(' 提示: 使用 --tool <name> 指定工具,或运行 npx hariness list-tools 查看');
646
- targetTools = [{ ...SUPPORTED_TOOLS[0], detected: true }]; // 默认 CodeBuddy
739
+ targetTools.push({ ...found, detected: true });
647
740
  } else {
648
- console.log(`🔍 检测到的 AI 工具:`);
649
- for (const t of targetTools) {
650
- console.log(` • ${t.name} (${t.description})`);
651
- }
652
- console.log('');
741
+ console.warn(`⚠️ 不支持的工具: ${tid},已跳过`);
653
742
  }
654
743
  }
655
744
 
656
- // 3. 扫描源文件
745
+ if (targetTools.length === 0) {
746
+ console.error('❌ 未选择任何 AI 工具,初始化终止。');
747
+ process.exit(1);
748
+ }
749
+
750
+ console.log(`\n📋 选中的 AI 工具:`);
751
+ for (const t of targetTools) {
752
+ console.log(` • ${t.name}`);
753
+ }
754
+
755
+ // 3. 交互式选择技术栈
756
+ let stack = requestedStack;
757
+ if (!stack) {
758
+ stack = await promptSelectStack();
759
+ }
760
+ console.log(`\n🏗️ 技术栈: ${stackLabel(stack)}\n`);
761
+
762
+ // 4. 扫描源文件
657
763
  const allRuleFiles = scanHarnessRules(harnessDir, stack);
658
764
  const allSkillFiles = scanHarnessSkills(harnessDir, stack);
659
765
 
@@ -661,7 +767,7 @@ export async function runInit(projectDir, options = {}) {
661
767
  console.log(`📋 扫描结果: ${allRuleFiles.length} 个规则, ${allSkillFiles.length} 个技能\n`);
662
768
  }
663
769
 
664
- // 4. 对每个目标工具执行注入
770
+ // 5. 对每个目标工具执行注入
665
771
  const summary = [];
666
772
 
667
773
  for (const tool of targetTools) {
@@ -671,7 +777,7 @@ export async function runInit(projectDir, options = {}) {
671
777
 
672
778
  // 注入 Rules
673
779
  if (!skillsOnly && allRuleFiles.length > 0) {
674
- console.log(`\n📜 注入规则 (${allRuleFiles.length} 个)...`);
780
+ console.log(`📜 注入规则 (${allRuleFiles.length} 个)...`);
675
781
  const result = transformRules(allRuleFiles, tool.id, { stack });
676
782
  outputs.push(...result.files);
677
783
  }
@@ -689,7 +795,18 @@ export async function runInit(projectDir, options = {}) {
689
795
  summary.push({ tool: tool.name, written, skipped });
690
796
  }
691
797
 
692
- // 5. 输出总结
798
+ // 6. 初始化 OpenSpec 变更管理目录(内聚在 jsharness 中,无需额外依赖)
799
+ console.log('\n━━━ 初始化 OpenSpec ━━━');
800
+ const openspecResult = initOpenSpec(projectDir, { force, verbose });
801
+ if (openspecResult.created.length > 0) {
802
+ console.log(` ✅ 创建 ${openspecResult.created.length} 项:`);
803
+ openspecResult.created.forEach(f => console.log(` - ${f}`));
804
+ }
805
+ if (openspecResult.skipped.length > 0) {
806
+ console.log(` ⏭ 跳过 ${openspecResult.skipped.length} 项 (已存在)`);
807
+ }
808
+
809
+ // 7. 输出总结
693
810
  console.log('\n═════════════════════════════');
694
811
  console.log('✅ 初始化完成!');
695
812
 
@@ -705,11 +822,17 @@ export async function runInit(projectDir, options = {}) {
705
822
  }
706
823
  }
707
824
 
825
+ if (openspecResult.created.length > 0) {
826
+ console.log(`\n [OpenSpec]`);
827
+ console.log(` ✅ 创建 ${openspecResult.created.length} 项`);
828
+ }
829
+
708
830
  if (!force && summary.some(s => s.skipped.length > 0)) {
709
831
  console.log('\n💡 提示: 使用 --force 可覆盖已有配置');
710
832
  }
711
833
 
712
- console.log('\n🎉 Harness 规则已注入到你的 AI 工具中!开始对话即可生效。\n');
834
+ console.log('\n🎉 Harness 规则已注入到你的 AI 工具中!开始对话即可生效。');
835
+ console.log('📋 OpenSpec 变更管理已就绪,可使用 AI 工具的 openspec skill 创建变更。\n');
713
836
  }
714
837
 
715
838
  export function listTools() {
@@ -768,9 +891,201 @@ export function showStatus(projectDir) {
768
891
  console.log(` ${exists ? '✅' : '○ '} ${t.name.padEnd(12)} ${t.path}`);
769
892
  }
770
893
 
894
+ // OpenSpec 状态
895
+ const openspecDir = path.join(projectDir, 'openspec');
896
+ const hasOpenSpec = fs.existsSync(openspecDir);
897
+ console.log(`\n OpenSpec: ${hasOpenSpec ? '✅ 已初始化' : '○ 未初始化'}`);
898
+ if (hasOpenSpec) {
899
+ const changesDir = path.join(openspecDir, 'changes');
900
+ if (fs.existsSync(changesDir)) {
901
+ const active = fs.readdirSync(changesDir, { withFileTypes: true })
902
+ .filter(d => d.isDirectory() && d.name !== 'archive');
903
+ const archiveDir = path.join(changesDir, 'archive');
904
+ const archived = fs.existsSync(archiveDir)
905
+ ? fs.readdirSync(archiveDir, { withFileTypes: true }).filter(d => d.isDirectory()).length
906
+ : 0;
907
+ console.log(` 活跃变更: ${active.length} 归档: ${archived}`);
908
+ }
909
+ }
910
+
771
911
  console.log('');
772
912
  }
773
913
 
914
+ // ============================================================
915
+ // OpenSpec 内聚初始化(无需额外 npm 依赖,直接创建目录结构)
916
+ // ============================================================
917
+
918
+ const OPENSPEC_DIRS = [
919
+ 'openspec',
920
+ 'openspec/changes',
921
+ 'openspec/changes/archive',
922
+ 'openspec/specs',
923
+ ];
924
+
925
+ const OPENSPEC_GITIGNORE = `# OpenSpec generated files
926
+ changes/*/dist/
927
+ *.log
928
+ `;
929
+
930
+ /**
931
+ * 初始化 OpenSpec 目录结构到目标项目
932
+ *
933
+ * @param {string} projectDir - 目标项目根目录
934
+ * @param {object} options - 选项 { force, verbose }
935
+ * @returns {{ created: string[], skipped: string[] }}
936
+ */
937
+ export function initOpenSpec(projectDir, options = {}) {
938
+ const { force = false, verbose = false } = options;
939
+ const created = [];
940
+ const skipped = [];
941
+
942
+ for (const dir of OPENSPEC_DIRS) {
943
+ const fullPath = path.join(projectDir, dir);
944
+ if (fs.existsSync(fullPath)) {
945
+ if (verbose) console.log(` ⏭ 目录已存在: ${dir}/`);
946
+ skipped.push(dir);
947
+ continue;
948
+ }
949
+ fs.mkdirSync(fullPath, { recursive: true });
950
+ created.push(dir);
951
+ if (verbose) console.log(` ✅ 创建目录: ${dir}/`);
952
+ }
953
+
954
+ // .gitkeep 确保空目录可被 git 追踪
955
+ for (const dir of ['openspec/changes/archive', 'openspec/specs']) {
956
+ const gitkeepPath = path.join(projectDir, dir, '.gitkeep');
957
+ if (!fs.existsSync(gitkeepPath)) {
958
+ fs.writeFileSync(gitkeepPath, '', 'utf-8');
959
+ }
960
+ }
961
+
962
+ // openspec/.gitignore
963
+ const gitignorePath = path.join(projectDir, 'openspec', '.gitignore');
964
+ if (!fs.existsSync(gitignorePath) || force) {
965
+ fs.writeFileSync(gitignorePath, OPENSPEC_GITIGNORE, 'utf-8');
966
+ if (!created.includes('openspec')) created.push('openspec/.gitignore');
967
+ if (verbose) console.log(` ✅ 创建文件: openspec/.gitignore`);
968
+ } else {
969
+ skipped.push('openspec/.gitignore');
970
+ }
971
+
972
+ // openspec/README.md
973
+ const readmePath = path.join(projectDir, 'openspec', 'README.md');
974
+ if (!fs.existsSync(readmePath) || force) {
975
+ fs.writeFileSync(readmePath, generateOpenSpecReadme(), 'utf-8');
976
+ created.push('openspec/README.md');
977
+ if (verbose) console.log(` ✅ 创建文件: openspec/README.md`);
978
+ } else {
979
+ skipped.push('openspec/README.md');
980
+ }
981
+
982
+ return { created, skipped };
983
+ }
984
+
985
+ function generateOpenSpecReadme() {
986
+ return `# OpenSpec 变更管理
987
+
988
+ 本目录由 Harness Engineering 系统自动创建,用于管理结构化变更。
989
+
990
+ ## 目录结构
991
+
992
+ \`\`\`
993
+ openspec/
994
+ ├── changes/ # 活跃的变更(每个 change 一个子目录)
995
+ │ └── archive/ # 已归档的变更
996
+ └── specs/ # 功能规格定义
997
+ \`\`\`
998
+
999
+ ## 使用方式
1000
+
1001
+ 在 AI 对话中使用 OpenSpec skills:
1002
+
1003
+ - **openspec-propose** — 创建新变更提案
1004
+ - **openspec-explore** — 探索和澄清需求
1005
+ - **openspec-apply-change** — 实施变更任务
1006
+ - **openspec-archive-change** — 归档已完成变更
1007
+
1008
+ ## 命令行操作
1009
+
1010
+ \`\`\`bash
1011
+ # 查看状态
1012
+ npx jsharness status
1013
+
1014
+ # 列出活跃变更
1015
+ npx jsharness openspec list
1016
+
1017
+ # 归档变更
1018
+ npx jsharness openspec archive --change "<name>"
1019
+ \`\`\`
1020
+
1021
+ > 本目录由 Harness Engineering 系统自动管理。
1022
+ `;
1023
+ }
1024
+
1025
+ /**
1026
+ * 列出当前项目的 OpenSpec changes
1027
+ */
1028
+ export function listOpenSpecChanges(projectDir) {
1029
+ const changesDir = path.join(projectDir, 'openspec', 'changes');
1030
+ if (!fs.existsSync(changesDir)) {
1031
+ console.log('\n⚠️ OpenSpec 尚未初始化。请先运行: npx jsharness init\n');
1032
+ return;
1033
+ }
1034
+
1035
+ const entries = fs.readdirSync(changesDir, { withFileTypes: true })
1036
+ .filter(d => d.isDirectory() && d.name !== 'archive');
1037
+
1038
+ if (entries.length === 0) {
1039
+ console.log('\n📋 没有活跃的 OpenSpec 变更\n');
1040
+ return;
1041
+ }
1042
+
1043
+ console.log('\n📋 OpenSpec 活跃变更:\n');
1044
+ for (const entry of entries) {
1045
+ const yamlPath = path.join(changesDir, entry.name, '.openspec.yaml');
1046
+ let status = '(未知)';
1047
+ if (fs.existsSync(yamlPath)) {
1048
+ try {
1049
+ const yaml = fs.readFileSync(yamlPath, 'utf-8');
1050
+ const m = yaml.match(/status:\s*(.+)/);
1051
+ if (m) status = m[1].trim();
1052
+ } catch { /* ignore */ }
1053
+ }
1054
+ console.log(` • ${entry.name} ${status}`);
1055
+ }
1056
+
1057
+ const archiveDir = path.join(changesDir, 'archive');
1058
+ if (fs.existsSync(archiveDir)) {
1059
+ const archived = fs.readdirSync(archiveDir, { withFileTypes: true })
1060
+ .filter(d => d.isDirectory());
1061
+ if (archived.length > 0) {
1062
+ console.log('\n📦 已归档:');
1063
+ for (const entry of archived) {
1064
+ console.log(` • ${entry.name}`);
1065
+ }
1066
+ }
1067
+ }
1068
+ console.log('');
1069
+ }
1070
+
1071
+ /**
1072
+ * 归档一个 OpenSpec change
1073
+ */
1074
+ export function archiveOpenSpecChange(projectDir, changeName) {
1075
+ const changesDir = path.join(projectDir, 'openspec', 'changes');
1076
+ const sourceDir = path.join(changesDir, changeName);
1077
+ const archiveDir = path.join(changesDir, 'archive', `${new Date().toISOString().split('T')[0]}-${changeName}`);
1078
+
1079
+ if (!fs.existsSync(sourceDir)) {
1080
+ console.error(`\n❌ 变更 "${changeName}" 不存在\n`);
1081
+ process.exit(1);
1082
+ }
1083
+
1084
+ fs.mkdirSync(path.join(changesDir, 'archive'), { recursive: true });
1085
+ fs.renameSync(sourceDir, archiveDir);
1086
+ console.log(`\n✅ 变更 "${changeName}" 已归档到 openspec/changes/archive/${path.basename(archiveDir)}/\n`);
1087
+ }
1088
+
774
1089
  // ============================================================
775
1090
  // 内部:查找 Harness 源
776
1091
  // ============================================================
@@ -780,7 +1095,13 @@ function findHarnessSource(projectDir) {
780
1095
  const localHarness = path.join(projectDir, '.harness');
781
1096
  if (fs.existsSync(localHarness)) return localHarness;
782
1097
 
783
- // 2. 通过 node_modules 查找(npm i -D jsharness 后)
1098
+ // 2. 本包自身的 .harness/(最可靠:基于 lib/index.mjs 的位置定位)
1099
+ // lib/index.mjs 所在目录 = <pkg_root>/lib/
1100
+ // 所以 .harness/ 在 <pkg_root>/.harness/ 即 __dirname/../.harness
1101
+ const selfHarness = path.join(__dirname, '..', '.harness');
1102
+ if (fs.existsSync(selfHarness)) return selfHarness;
1103
+
1104
+ // 3. 通过 node_modules 查找(npm i -D jsharness 后)
784
1105
  const nmCandidates = [
785
1106
  path.join(projectDir, 'node_modules', 'jsharness', '.harness'),
786
1107
  path.join(projectDir, 'node_modules', '.pnpm', 'jsharness*', 'node_modules', 'jsharness', '.harness'),
@@ -796,7 +1117,7 @@ function findHarnessSource(projectDir) {
796
1117
  } catch { /* skip */ }
797
1118
  }
798
1119
 
799
- // 3. 通过 require.resolve 定位 npm 包根
1120
+ // 4. 通过 require.resolve 定位 npm 包根
800
1121
  try {
801
1122
  const _require = createRequire(import.meta.url);
802
1123
  const pkgPath = _require.resolve('jsharness/package.json');
@@ -806,10 +1127,6 @@ function findHarnessSource(projectDir) {
806
1127
  // require 解析失败,继续尝试其他方式
807
1128
  }
808
1129
 
809
- // 4. __dirname 相对路径(本地开发模式)
810
- const devHarness = path.join(__dirname, '..', '..', '.harness');
811
- if (fs.existsSync(devHarness)) return devHarness;
812
-
813
1130
  // 5. 最后尝试:沿 __dirname 向上搜索 .harness
814
1131
  let current = __dirname;
815
1132
  for (let i = 0; i < 5; i++) {
package/package.json CHANGED
@@ -1 +1,53 @@
1
- {"name":"jsharness","version":"1.0.2","description":"Harness Engineering - AI 编程行为工程化管控系统。将 rules/skills/gate/agents 一键注入到 CodeBuddy、Cursor、Copilot 等 AI 工具中。","main":"lib/index.mjs","bin":{"jsharness":"./bin/jsharness.js"},"type":"module","scripts":{"jsharness":"node bin/jsharness.js","init":"node bin/jsharness.js init","list-tools":"node bin/jsharness.js list-tools","status":"node bin/jsharness.js status","test":"node bin/jsharness.js status"},"keywords":["harness","ai-coding","codebuddy","cursor","copilot","rules-engineering","ai-assistant","vue3","spring-boot"],"author":"","license":"ISC","engines":{"node":">=18.0.0"},"files":["bin/","lib/",".harness/","README.md"],"dependencies":{"commander":"^12.1.0"},"devDependencies":{"openspec":"^0.0.0","webpack":"^5.107.0","webpack-cli":"^7.0.2"},"repository":{"type":"git","url":""},"homepage":""}
1
+ {
2
+ "name": "jsharness",
3
+ "version": "1.1.1",
4
+ "description": "Harness Engineering - AI 编程行为工程化管控系统。将 rules/skills/gate/agents 一键注入到 CodeBuddy、Cursor、Copilot 等 AI 工具中。",
5
+ "main": "lib/index.mjs",
6
+ "bin": {
7
+ "jsharness": "./bin/jsharness.js"
8
+ },
9
+ "type": "module",
10
+ "scripts": {
11
+ "jsharness": "node bin/jsharness.js",
12
+ "init": "node bin/jsharness.js init",
13
+ "list-tools": "node bin/jsharness.js list-tools",
14
+ "status": "node bin/jsharness.js status",
15
+ "prepublishOnly": "echo 'Publishing jsharness to npm...'"
16
+ },
17
+ "keywords": [
18
+ "harness",
19
+ "ai-coding",
20
+ "codebuddy",
21
+ "cursor",
22
+ "copilot",
23
+ "claude",
24
+ "rules-engineering",
25
+ "ai-assistant",
26
+ "vue3",
27
+ "spring-boot",
28
+ "openspec"
29
+ ],
30
+ "author": "",
31
+ "license": "ISC",
32
+ "engines": {
33
+ "node": ">=18.0.0"
34
+ },
35
+ "files": [
36
+ "bin/",
37
+ "lib/",
38
+ ".harness/",
39
+ "README.md"
40
+ ],
41
+ "dependencies": {
42
+ "commander": "^12.1.0"
43
+ },
44
+ "devDependencies": {
45
+ "webpack": "^5.107.0",
46
+ "webpack-cli": "^7.0.2"
47
+ },
48
+ "repository": {
49
+ "type": "git",
50
+ "url": ""
51
+ },
52
+ "homepage": ""
53
+ }