hunter-harness 0.2.10 → 0.2.11

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 (3) hide show
  1. package/README.md +3 -3
  2. package/dist/bin.js +253 -53
  3. package/package.json +5 -5
package/README.md CHANGED
@@ -21,8 +21,8 @@ npx hunter-harness update
21
21
  | Claude Code | `.claude/skills/` | `.claude/rules/*.md` | `.claude/agents/` |
22
22
  | Codex | `.agents/skills/` | `AGENTS.md` | 不生成 |
23
23
  | Cursor | `.cursor/skills/` | `.cursor/rules/*.mdc` | 不生成 |
24
- | CodeBuddy `both` | `.codebuddy/skills/` | `CODEBUDDY.md` | `.codebuddy/agents/` |
24
+ | CodeBuddy `both` | `.codebuddy/skills/` | `CODEBUDDY.md` + `.codebuddy/.rules/*.mdc` + `.codebuddy/rules/*.md` | `.codebuddy/agents/` |
25
25
 
26
- 需要 Node.js 24 或更高版本。token 只通过 `--token-env` 指定的环境变量读取,不要写入项目文件或命令参数。
26
+ 需要 Node.js 22.12 或更高版本。选择 CodeBuddy 时,可将已有 `.claude/rules` 非破坏性同步到 CodeBuddy,并在检测到 `.codegraph/` 时合并项目级 `.mcp.json`;疑似凭据内容会跳过。token 只通过 `--token-env` 指定的环境变量读取,不要写入项目文件或命令参数。
27
27
 
28
- 本包当前为 `UNLICENSED`。参考迁移资产的上游 license、commit/tag 和允许再分发范围确认前,不得对外发布。
28
+ 本包使用 MIT License。
package/dist/bin.js CHANGED
@@ -1098,8 +1098,8 @@ async function assertNoSymlinks(root, relativePath) {
1098
1098
  for (const segment of normalized.split("/")) {
1099
1099
  current = join(current, segment);
1100
1100
  try {
1101
- const stat6 = await lstat(current);
1102
- if (stat6.isSymbolicLink()) {
1101
+ const stat7 = await lstat(current);
1102
+ if (stat7.isSymbolicLink()) {
1103
1103
  throw new UnsafePathError("symbolic links are not managed");
1104
1104
  }
1105
1105
  } catch (error) {
@@ -1982,6 +1982,13 @@ function makeAdapter(spec) {
1982
1982
  return projectBundleFiles(bundle, spec.skillsRoot, spec.agentsRoot);
1983
1983
  },
1984
1984
  contextIndex(context) {
1985
+ if (spec.name === "codebuddy") {
1986
+ return {
1987
+ instructions: spec.instructions,
1988
+ skills_root: spec.skillsRoot,
1989
+ rules: codebuddyRulePaths(context)
1990
+ };
1991
+ }
1985
1992
  const rules = [];
1986
1993
  if (spec.rulesRoot !== null && spec.ruleExt !== null) {
1987
1994
  rules.push(`${spec.rulesRoot}/harness-general${spec.ruleExt}`);
@@ -2001,6 +2008,9 @@ function makeAdapter(spec) {
2001
2008
  boundaries.push(spec.agentsRoot);
2002
2009
  if (spec.rulesRoot !== null)
2003
2010
  boundaries.push(spec.rulesRoot);
2011
+ if (spec.name === "codebuddy") {
2012
+ boundaries.push(".codebuddy/.rules", ".codebuddy/rules");
2013
+ }
2004
2014
  const top = spec.skillsRoot.split("/")[0];
2005
2015
  if (top !== void 0)
2006
2016
  boundaries.push(top);
@@ -2053,6 +2063,17 @@ var ADAPTERS = {
2053
2063
  function getAdapter(name) {
2054
2064
  return ADAPTERS[name];
2055
2065
  }
2066
+ function codebuddyRulePaths(context) {
2067
+ const names = context.profile === "java" ? ["harness-general", "harness-profile-java"] : ["harness-general"];
2068
+ const paths = [];
2069
+ if (context.codebuddySurface !== "cli") {
2070
+ paths.push(...names.map((name) => `.codebuddy/.rules/${name}.mdc`));
2071
+ }
2072
+ if (context.codebuddySurface !== "ide") {
2073
+ paths.push(...names.map((name) => `.codebuddy/rules/${name}.md`));
2074
+ }
2075
+ return paths.sort((left, right) => left.localeCompare(right));
2076
+ }
2056
2077
  function getAdapters(names) {
2057
2078
  return HARNESS_AGENT_ORDER.filter((n) => names.includes(n)).map(getAdapter);
2058
2079
  }
@@ -2068,6 +2089,13 @@ function managedTargetsFor(adapter, bundle, context) {
2068
2089
  if (context.profile === "java") {
2069
2090
  records.push(ruleTarget("rules/harness-profile-java.mdc", ".cursor/rules/harness-profile-java.mdc", CURSOR_JAVA_RULES_CONTENT));
2070
2091
  }
2092
+ } else if (adapter.name === "codebuddy") {
2093
+ for (const targetPath of codebuddyRulePaths(context)) {
2094
+ const isMdc = targetPath.endsWith(".mdc");
2095
+ const isJava = targetPath.includes("harness-profile-java");
2096
+ const content = isJava ? isMdc ? CURSOR_JAVA_RULES_CONTENT : HARNESS_JAVA_RULES_CONTENT : isMdc ? CURSOR_GENERAL_RULES_CONTENT : HARNESS_GENERAL_RULES_CONTENT;
2097
+ records.push(ruleTarget(`rules/${isJava ? "harness-profile-java" : "harness-general"}${isMdc ? ".mdc" : ".md"}`, targetPath, content));
2098
+ }
2071
2099
  }
2072
2100
  assertNoCaseCollisions(records.map((record) => record.target_path));
2073
2101
  return records.sort((left, right) => left.target_path.localeCompare(right.target_path));
@@ -4107,8 +4135,8 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
4107
4135
  const after = JSON.parse(await readFile11(join12(transactionRoot, "after", "manifest.json"), "utf8"));
4108
4136
  for (const entry of after) {
4109
4137
  const target = join12(projectRoot, entry.path);
4110
- const exists5 = await pathExists(target);
4111
- if (exists5 !== entry.exists || exists5 && await sha256File(target) !== entry.hash) {
4138
+ const exists6 = await pathExists(target);
4139
+ if (exists6 !== entry.exists || exists6 && await sha256File(target) !== entry.hash) {
4112
4140
  throw new Error("cannot rollback dirty path: " + entry.path);
4113
4141
  }
4114
4142
  }
@@ -4120,15 +4148,15 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
4120
4148
  }
4121
4149
  seen.add(snapshot.path);
4122
4150
  const target = join12(projectRoot, snapshot.path);
4123
- const exists5 = await pathExists(target);
4151
+ const exists6 = await pathExists(target);
4124
4152
  if (snapshot.existed && snapshot.snapshot_name !== null) {
4125
4153
  const content = await readFile11(join12(transactionRoot, "before", snapshot.snapshot_name));
4126
4154
  operations.push({
4127
- operation: exists5 ? "modify" : "add",
4155
+ operation: exists6 ? "modify" : "add",
4128
4156
  path: snapshot.path,
4129
4157
  content
4130
4158
  });
4131
- } else if (exists5) {
4159
+ } else if (exists6) {
4132
4160
  operations.push({ operation: "delete", path: snapshot.path });
4133
4161
  }
4134
4162
  }
@@ -4672,14 +4700,118 @@ function serializeCliResult(result) {
4672
4700
  return JSON.stringify(result) + "\n";
4673
4701
  }
4674
4702
 
4703
+ // src/config/codebuddy-setup.ts
4704
+ import { mkdir as mkdir4, readFile as readFile14, readdir as readdir7, stat as stat4, writeFile as writeFile5 } from "node:fs/promises";
4705
+ import { basename as basename2, dirname as dirname4, extname, join as join15 } from "node:path";
4706
+ var MANAGED_RULE_NAMES = /* @__PURE__ */ new Set([
4707
+ "harness-general.md",
4708
+ "harness-general.mdc",
4709
+ "harness-profile-java.md",
4710
+ "harness-profile-java.mdc"
4711
+ ]);
4712
+ var SENSITIVE_ASSIGNMENT = /(?:password|passwd|token|secret|access[_-]?key|private[_-]?key)\s*[:=]\s*[^\s#]+/i;
4713
+ var PRIVATE_KEY_BLOCK = /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/i;
4714
+ async function exists4(path) {
4715
+ try {
4716
+ await stat4(path);
4717
+ return true;
4718
+ } catch {
4719
+ return false;
4720
+ }
4721
+ }
4722
+ async function readJsonObject(path) {
4723
+ try {
4724
+ const parsed = JSON.parse(await readFile14(path, "utf8"));
4725
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
4726
+ } catch (error) {
4727
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return {};
4728
+ return null;
4729
+ }
4730
+ }
4731
+ async function inspectCodeBuddySetup(projectRoot) {
4732
+ const rulesRoot = join15(projectRoot, ".claude", "rules");
4733
+ let claudeRules = [];
4734
+ try {
4735
+ claudeRules = (await readdir7(rulesRoot, { withFileTypes: true })).filter((entry) => entry.isFile() && [".md", ".mdc"].includes(extname(entry.name).toLowerCase())).map((entry) => entry.name).filter((name) => !MANAGED_RULE_NAMES.has(name)).sort();
4736
+ } catch (error) {
4737
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
4738
+ }
4739
+ const mcp = await readJsonObject(join15(projectRoot, ".mcp.json"));
4740
+ const servers = mcp?.mcpServers;
4741
+ const configured = servers !== null && typeof servers === "object" && !Array.isArray(servers) && Object.prototype.hasOwnProperty.call(servers, "codegraph");
4742
+ return {
4743
+ claudeRules,
4744
+ hasCodeGraphIndex: await exists4(join15(projectRoot, ".codegraph")),
4745
+ codeGraphConfigured: configured
4746
+ };
4747
+ }
4748
+ function destinationTargets(root, surface, name) {
4749
+ const stem = basename2(name, extname(name));
4750
+ const targets = [];
4751
+ if (surface !== "cli") targets.push(join15(root, ".codebuddy", ".rules", `${stem}.mdc`));
4752
+ if (surface !== "ide") targets.push(join15(root, ".codebuddy", "rules", `${stem}.md`));
4753
+ return targets;
4754
+ }
4755
+ async function applyCodeBuddySetup(options) {
4756
+ const result = {
4757
+ copied: [],
4758
+ preserved: [],
4759
+ skippedSensitive: [],
4760
+ mcpUpdated: false,
4761
+ warnings: []
4762
+ };
4763
+ if (options.syncClaudeRules) {
4764
+ const plan = await inspectCodeBuddySetup(options.projectRoot);
4765
+ for (const name of plan.claudeRules) {
4766
+ const source = join15(options.projectRoot, ".claude", "rules", name);
4767
+ const content = await readFile14(source, "utf8");
4768
+ if (SENSITIVE_ASSIGNMENT.test(content) || PRIVATE_KEY_BLOCK.test(content)) {
4769
+ result.skippedSensitive.push(name);
4770
+ continue;
4771
+ }
4772
+ for (const target of destinationTargets(options.projectRoot, options.surface, name)) {
4773
+ if (await exists4(target)) {
4774
+ result.preserved.push(target);
4775
+ continue;
4776
+ }
4777
+ await mkdir4(dirname4(target), { recursive: true });
4778
+ await writeFile5(target, content, { encoding: "utf8", flag: "wx" });
4779
+ result.copied.push(target);
4780
+ }
4781
+ }
4782
+ }
4783
+ if (options.configureCodeGraph) {
4784
+ const path = join15(options.projectRoot, ".mcp.json");
4785
+ const current = await readJsonObject(path);
4786
+ if (current === null) {
4787
+ result.warnings.push(".mcp.json \u4E0D\u662F\u6709\u6548 JSON\uFF0C\u5DF2\u4FDD\u7559\u539F\u6587\u4EF6\u5E76\u8DF3\u8FC7 CodeGraph MCP \u914D\u7F6E");
4788
+ } else {
4789
+ const currentServers = current.mcpServers;
4790
+ const servers = currentServers !== null && typeof currentServers === "object" && !Array.isArray(currentServers) ? currentServers : {};
4791
+ if (!Object.prototype.hasOwnProperty.call(servers, "codegraph")) {
4792
+ const next = {
4793
+ ...current,
4794
+ mcpServers: {
4795
+ ...servers,
4796
+ codegraph: { command: "codegraph", args: ["serve", "--mcp"] }
4797
+ }
4798
+ };
4799
+ await writeFile5(path, JSON.stringify(next, null, 2) + "\n", "utf8");
4800
+ result.mcpUpdated = true;
4801
+ }
4802
+ }
4803
+ }
4804
+ return result;
4805
+ }
4806
+
4675
4807
  // src/commands/refresh.ts
4676
- import { readFile as readFile14 } from "node:fs/promises";
4677
- import { join as join15 } from "node:path";
4808
+ import { readFile as readFile15 } from "node:fs/promises";
4809
+ import { join as join16 } from "node:path";
4678
4810
  import { parse as parseYaml9 } from "yaml";
4679
4811
  async function detectProject(root) {
4680
4812
  let content;
4681
4813
  try {
4682
- content = await readFile14(join15(root, ".harness", "project.yaml"), "utf8");
4814
+ content = await readFile15(join16(root, ".harness", "project.yaml"), "utf8");
4683
4815
  } catch (error) {
4684
4816
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
4685
4817
  return { status: "absent" };
@@ -4707,8 +4839,14 @@ function refreshAgents(config) {
4707
4839
  }));
4708
4840
  return agents.length > 0 ? agents : ["claude-code"];
4709
4841
  }
4710
- function codebuddySurface(config) {
4711
- return config.adapter_options?.codebuddy?.surface ?? "both";
4842
+ function codebuddySurface(config, override) {
4843
+ const value = override ?? config.adapter_options?.codebuddy?.surface ?? "both";
4844
+ if (value === "both" || value === "ide" || value === "cli") return value;
4845
+ throw new InitConfigurationError(
4846
+ "codebuddy surface \u5FC5\u987B\u4E3A both\u3001ide \u6216 cli",
4847
+ 3,
4848
+ "CODEBUDDY_SURFACE_INVALID"
4849
+ );
4712
4850
  }
4713
4851
  function summarize(result) {
4714
4852
  const items = [
@@ -4795,7 +4933,7 @@ async function runRefresh(options, dependencies) {
4795
4933
  resourcesRoot: dependencies.resourcesRoot,
4796
4934
  ...targetProfile === void 0 ? {} : { profile: targetProfile },
4797
4935
  agents: targetAgents,
4798
- codebuddySurface: codebuddySurface(detection.config),
4936
+ codebuddySurface: codebuddySurface(detection.config, options.codebuddySurface),
4799
4937
  dryRun: true,
4800
4938
  forceManaged: options.forceManaged === true
4801
4939
  });
@@ -4831,7 +4969,7 @@ async function runRefresh(options, dependencies) {
4831
4969
  resourcesRoot: dependencies.resourcesRoot,
4832
4970
  ...targetProfile === void 0 ? {} : { profile: targetProfile },
4833
4971
  agents: targetAgents,
4834
- codebuddySurface: codebuddySurface(detection.config),
4972
+ codebuddySurface: codebuddySurface(detection.config, options.codebuddySurface),
4835
4973
  dryRun,
4836
4974
  forceManaged: options.forceManaged === true
4837
4975
  });
@@ -4880,6 +5018,46 @@ var AGENT_LABELS = {
4880
5018
  cursor: "Cursor",
4881
5019
  codebuddy: "CodeBuddy"
4882
5020
  };
5021
+ async function configureCodeBuddyExtras(agents, surface, options, dependencies) {
5022
+ if (!agents.includes("codebuddy")) return;
5023
+ const plan = await inspectCodeBuddySetup(dependencies.cwd);
5024
+ let syncClaudeRules = false;
5025
+ let configureCodeGraph = false;
5026
+ if (plan.claudeRules.length > 0) {
5027
+ syncClaudeRules = options.nonInteractive === true ? options.yes === true : /^(?:|y|yes)$/i.test((await dependencies.prompt(
5028
+ `\u53D1\u73B0 ${plan.claudeRules.length} \u4E2A Claude \u81EA\u5B9A\u4E49\u89C4\u5219\uFF0C\u662F\u5426\u590D\u5236\u5230 CodeBuddy\uFF08\u4FDD\u7559\u6E90\u6587\u4EF6\u4E14\u4E0D\u8986\u76D6\u76EE\u6807\uFF09\uFF1F[Y/n]\uFF1A`
5029
+ )).trim());
5030
+ }
5031
+ if (plan.hasCodeGraphIndex && !plan.codeGraphConfigured) {
5032
+ configureCodeGraph = options.nonInteractive === true ? options.yes === true : /^(?:|y|yes)$/i.test((await dependencies.prompt(
5033
+ "\u68C0\u6D4B\u5230 .codegraph \u7D22\u5F15\uFF0C\u662F\u5426\u5408\u5E76 CodeGraph MCP \u5230\u9879\u76EE .mcp.json\uFF1F[Y/n]\uFF1A"
5034
+ )).trim());
5035
+ }
5036
+ if (options.dryRun === true) {
5037
+ if (syncClaudeRules || configureCodeGraph) {
5038
+ dependencies.stdout("CodeBuddy \u9644\u52A0\u914D\u7F6E\u5904\u4E8E dry-run\uFF0C\u672A\u5199\u5165\u89C4\u5219\u6216 .mcp.json\u3002\n");
5039
+ }
5040
+ return;
5041
+ }
5042
+ const result = await applyCodeBuddySetup({
5043
+ projectRoot: dependencies.cwd,
5044
+ surface,
5045
+ syncClaudeRules,
5046
+ configureCodeGraph
5047
+ });
5048
+ if (result.copied.length > 0) {
5049
+ dependencies.stdout(`\u5DF2\u5B89\u5168\u590D\u5236 ${result.copied.length} \u4E2A CodeBuddy \u89C4\u5219\u6587\u4EF6\u3002
5050
+ `);
5051
+ }
5052
+ if (result.skippedSensitive.length > 0) {
5053
+ dependencies.stderr(
5054
+ `\u68C0\u6D4B\u5230 ${result.skippedSensitive.length} \u4E2A\u7591\u4F3C\u542B\u51ED\u636E\u7684 Claude \u89C4\u5219\uFF0C\u5DF2\u4FDD\u7559\u539F\u5904\u4E14\u672A\u590D\u5236\u3002
5055
+ `
5056
+ );
5057
+ }
5058
+ if (result.mcpUpdated) dependencies.stdout("\u5DF2\u5408\u5E76 CodeGraph MCP \u5230 .mcp.json\u3002\n");
5059
+ for (const warning of result.warnings) dependencies.stderr(warning + "\n");
5060
+ }
4883
5061
  async function runFirstInstall(options, dependencies) {
4884
5062
  const requestId = uuidV7();
4885
5063
  try {
@@ -4910,6 +5088,12 @@ async function runFirstInstall(options, dependencies) {
4910
5088
  config,
4911
5089
  dryRun: options.dryRun === true
4912
5090
  });
5091
+ await configureCodeBuddyExtras(
5092
+ config.agents,
5093
+ config.codebuddy_surface,
5094
+ options,
5095
+ dependencies
5096
+ );
4913
5097
  const output = {
4914
5098
  schema_version: 1,
4915
5099
  command: "configure",
@@ -4949,20 +5133,29 @@ async function runFirstInstall(options, dependencies) {
4949
5133
  return exitCode;
4950
5134
  }
4951
5135
  }
4952
- async function runExistingProject(options, dependencies, currentProfile) {
5136
+ async function runExistingProject(options, dependencies, currentProfile, currentSurface) {
4953
5137
  const refreshOptions = {};
4954
5138
  if (options.agents !== void 0) refreshOptions.agents = options.agents;
5139
+ if (options.codebuddySurface !== void 0) {
5140
+ refreshOptions.codebuddySurface = options.codebuddySurface;
5141
+ }
4955
5142
  if (options.profile !== void 0) refreshOptions.profile = options.profile;
4956
5143
  if (options.nonInteractive !== void 0) refreshOptions.nonInteractive = options.nonInteractive;
4957
5144
  if (options.yes !== void 0) refreshOptions.yes = options.yes;
4958
5145
  if (options.dryRun !== void 0) refreshOptions.dryRun = options.dryRun;
4959
5146
  if (options.json !== void 0) refreshOptions.json = options.json;
4960
5147
  if (options.forceManaged !== void 0) refreshOptions.forceManaged = options.forceManaged;
4961
- if (options.nonInteractive === true) {
4962
- return runRefresh(refreshOptions, dependencies);
4963
- }
4964
5148
  const installed = await readInstalledAgentConfiguration(dependencies.cwd);
4965
5149
  const currentAgents = installed.agents.length > 0 ? installed.agents : ["claude-code"];
5150
+ if (options.nonInteractive === true) {
5151
+ const selectedAgents2 = options.agents === void 0 ? currentAgents : parseAgentsInput(options.agents);
5152
+ const code2 = await runRefresh(refreshOptions, dependencies);
5153
+ if (code2 === 0) {
5154
+ const surface = options.codebuddySurface === "ide" || options.codebuddySurface === "cli" || options.codebuddySurface === "both" ? options.codebuddySurface : currentSurface;
5155
+ await configureCodeBuddyExtras(selectedAgents2, surface, options, dependencies);
5156
+ }
5157
+ return code2;
5158
+ }
4966
5159
  const currentLines = currentAgents.map(
4967
5160
  (agent) => `- ${AGENT_LABELS[agent]}\uFF1A${installed.profiles[agent] ?? currentProfile}`
4968
5161
  ).join("\n");
@@ -4997,7 +5190,13 @@ ${currentLines}
4997
5190
  refreshOptions.profile = answer.trim() === "" ? defaultProfile : answer.trim();
4998
5191
  }
4999
5192
  refreshOptions.confirmed = true;
5000
- return runRefresh(refreshOptions, dependencies);
5193
+ const selectedAgents = parseAgentsInput(refreshOptions.agents);
5194
+ const code = await runRefresh(refreshOptions, dependencies);
5195
+ if (code === 0) {
5196
+ const surface = options.codebuddySurface === "ide" || options.codebuddySurface === "cli" || options.codebuddySurface === "both" ? options.codebuddySurface : currentSurface;
5197
+ await configureCodeBuddyExtras(selectedAgents, surface, options, dependencies);
5198
+ }
5199
+ return code;
5001
5200
  }
5002
5201
  async function runConfigure(options, dependencies) {
5003
5202
  const detection = await detectProject(dependencies.cwd);
@@ -5022,7 +5221,8 @@ async function runConfigure(options, dependencies) {
5022
5221
  }
5023
5222
  if (detection.status === "valid") {
5024
5223
  const currentProfile = detection.config.project.profiles[0] ?? "general";
5025
- return runExistingProject(options, dependencies, currentProfile);
5224
+ const currentSurface = detection.config.adapter_options?.codebuddy?.surface ?? "both";
5225
+ return runExistingProject(options, dependencies, currentProfile, currentSurface);
5026
5226
  }
5027
5227
  return runFirstInstall(options, dependencies);
5028
5228
  }
@@ -5371,11 +5571,11 @@ async function runUpdate(options, dependencies) {
5371
5571
  }
5372
5572
 
5373
5573
  // src/commands/recovery.ts
5374
- import { stat as stat4 } from "node:fs/promises";
5375
- import { join as join16 } from "node:path";
5376
- async function exists4(path) {
5574
+ import { stat as stat5 } from "node:fs/promises";
5575
+ import { join as join17 } from "node:path";
5576
+ async function exists5(path) {
5377
5577
  try {
5378
- await stat4(path);
5578
+ await stat5(path);
5379
5579
  return true;
5380
5580
  } catch {
5381
5581
  return false;
@@ -5432,7 +5632,7 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
5432
5632
  return 5;
5433
5633
  }
5434
5634
  }
5435
- const initialized = await exists4(join16(dependencies.cwd, ".harness", "project.yaml"));
5635
+ const initialized = await exists5(join17(dependencies.cwd, ".harness", "project.yaml"));
5436
5636
  if (!initialized || options.nonInteractive === true || explicitConfigure(options)) {
5437
5637
  return null;
5438
5638
  }
@@ -5475,8 +5675,8 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
5475
5675
  }
5476
5676
 
5477
5677
  // src/workflow-data/resolve.ts
5478
- import { cp, mkdir as mkdir4, readdir as readdir7, readFile as readFile15, rm as rm8, stat as stat5 } from "node:fs/promises";
5479
- import { dirname as dirname4, join as join17, relative as relative2 } from "node:path";
5678
+ import { cp, mkdir as mkdir5, readdir as readdir8, readFile as readFile16, rm as rm8, stat as stat6 } from "node:fs/promises";
5679
+ import { dirname as dirname5, join as join18, relative as relative2 } from "node:path";
5480
5680
  import { fileURLToPath } from "node:url";
5481
5681
  var WorkflowDataResolutionError = class extends Error {
5482
5682
  code;
@@ -5490,7 +5690,7 @@ var WorkflowDataResolutionError = class extends Error {
5490
5690
  };
5491
5691
  async function pathExists3(path) {
5492
5692
  try {
5493
- await stat5(path);
5693
+ await stat6(path);
5494
5694
  return true;
5495
5695
  } catch {
5496
5696
  return false;
@@ -5510,17 +5710,17 @@ function workflowPackageName(family, env) {
5510
5710
  return `${scope}/workflow-${family}`;
5511
5711
  }
5512
5712
  async function listFilesRecursive(root, base = root) {
5513
- const entries = await readdir7(root, { withFileTypes: true });
5713
+ const entries = await readdir8(root, { withFileTypes: true });
5514
5714
  const files = [];
5515
5715
  for (const entry of entries) {
5516
- const absolute = join17(root, entry.name);
5716
+ const absolute = join18(root, entry.name);
5517
5717
  if (entry.isDirectory()) {
5518
5718
  files.push(...await listFilesRecursive(absolute, base));
5519
5719
  continue;
5520
5720
  }
5521
5721
  if (!entry.isFile()) continue;
5522
5722
  const rel = relative2(base, absolute).replaceAll("\\", "/");
5523
- files.push({ path: rel, content: await readFile15(absolute, "utf8") });
5723
+ files.push({ path: rel, content: await readFile16(absolute, "utf8") });
5524
5724
  }
5525
5725
  return files;
5526
5726
  }
@@ -5533,7 +5733,7 @@ async function verifyWorkflowPackageIntegrity(resourcesRoot) {
5533
5733
  const manifest = await readWorkflowFamilyManifest(resourcesRoot);
5534
5734
  const expected = manifest.content_sha256;
5535
5735
  if (typeof expected !== "string" || expected.length === 0) return;
5536
- const harnessRoot = join17(resourcesRoot, "harness");
5736
+ const harnessRoot = join18(resourcesRoot, "harness");
5537
5737
  if (!await pathExists3(harnessRoot)) {
5538
5738
  throw new WorkflowDataResolutionError(
5539
5739
  "\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u7F3A\u5C11 harness/\uFF0C\u65E0\u6CD5\u6821\u9A8C SHA-256",
@@ -5552,40 +5752,40 @@ async function verifyWorkflowPackageIntegrity(resourcesRoot) {
5552
5752
  }
5553
5753
  }
5554
5754
  async function monorepoResourcesRoot() {
5555
- const here = dirname4(fileURLToPath(import.meta.url));
5755
+ const here = dirname5(fileURLToPath(import.meta.url));
5556
5756
  const candidates = [
5557
5757
  // TypeScript source: packages/cli/src/workflow-data -> packages/workflow-data-harness.
5558
- join17(here, "../../../workflow-data-harness"),
5758
+ join18(here, "../../../workflow-data-harness"),
5559
5759
  // Bundled CLI: packages/cli/dist -> packages/workflow-data-harness.
5560
- join17(here, "../../workflow-data-harness"),
5760
+ join18(here, "../../workflow-data-harness"),
5561
5761
  // Test/build layouts that preserve additional source directory levels.
5562
- join17(here, "../../../../packages/workflow-data-harness")
5762
+ join18(here, "../../../../packages/workflow-data-harness")
5563
5763
  ];
5564
5764
  for (const candidate of candidates) {
5565
- if (await pathExists3(join17(candidate, "harness", "manifests"))) return candidate;
5765
+ if (await pathExists3(join18(candidate, "harness", "manifests"))) return candidate;
5566
5766
  }
5567
5767
  return null;
5568
5768
  }
5569
5769
  async function siblingWorkflowPackage(cwd) {
5570
- const here = dirname4(fileURLToPath(import.meta.url));
5770
+ const here = dirname5(fileURLToPath(import.meta.url));
5571
5771
  const candidates = [
5572
- join17(cwd, "node_modules", "@hunter-harness", "workflow-harness"),
5772
+ join18(cwd, "node_modules", "@hunter-harness", "workflow-harness"),
5573
5773
  // Published layout: node_modules/hunter-harness/dist/bin.js alongside the
5574
5774
  // scoped workflow package in node_modules/@hunter-harness/workflow-harness.
5575
- join17(here, "..", "..", "@hunter-harness", "workflow-harness"),
5775
+ join18(here, "..", "..", "@hunter-harness", "workflow-harness"),
5576
5776
  // Monorepo bundled layout: packages/cli/dist/bin.js.
5577
- join17(here, "..", "..", "workflow-data-harness")
5777
+ join18(here, "..", "..", "workflow-data-harness")
5578
5778
  ];
5579
5779
  for (const candidate of candidates) {
5580
- if (await pathExists3(join17(candidate, "harness", "manifests"))) return candidate;
5780
+ if (await pathExists3(join18(candidate, "harness", "manifests"))) return candidate;
5581
5781
  }
5582
5782
  return null;
5583
5783
  }
5584
5784
  async function readCachedNpmPackageVersion(cacheRoot) {
5585
- const manifestPath = join17(cacheRoot, "package.json");
5785
+ const manifestPath = join18(cacheRoot, "package.json");
5586
5786
  if (!await pathExists3(manifestPath)) return null;
5587
5787
  try {
5588
- const pkg = JSON.parse(await readFile15(manifestPath, "utf8"));
5788
+ const pkg = JSON.parse(await readFile16(manifestPath, "utf8"));
5589
5789
  return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : null;
5590
5790
  } catch {
5591
5791
  return null;
@@ -5607,13 +5807,13 @@ async function latestWorkflowCacheIsStale(cacheRoot, packageName, resolveManifes
5607
5807
  }
5608
5808
  }
5609
5809
  async function materializeWorkflowPackageCache(cacheRoot, packageSpec, extract) {
5610
- await mkdir4(cacheRoot, { recursive: true });
5611
- const staging = join17(cacheRoot, ".extract");
5810
+ await mkdir5(cacheRoot, { recursive: true });
5811
+ const staging = join18(cacheRoot, ".extract");
5612
5812
  await rm8(staging, { recursive: true, force: true });
5613
- await mkdir4(staging, { recursive: true });
5813
+ await mkdir5(staging, { recursive: true });
5614
5814
  await extract(packageSpec, staging);
5615
- const packageDir = await pathExists3(join17(staging, "harness", "manifests")) ? staging : join17(staging, "package");
5616
- if (!await pathExists3(join17(packageDir, "harness", "manifests"))) {
5815
+ const packageDir = await pathExists3(join18(staging, "harness", "manifests")) ? staging : join18(staging, "package");
5816
+ if (!await pathExists3(join18(packageDir, "harness", "manifests"))) {
5617
5817
  throw new WorkflowDataResolutionError(
5618
5818
  "\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u5185\u5BB9\u65E0\u6548\uFF1A\u7F3A\u5C11 harness/manifests",
5619
5819
  "WORKFLOW_DATA_INVALID",
@@ -5642,8 +5842,8 @@ async function resolveWorkflowResourcesRoot(options, argv = []) {
5642
5842
  const packageName = workflowPackageName(family, options.env);
5643
5843
  const packageSpec = version === "latest" ? packageName : `${packageName}@${version}`;
5644
5844
  const cacheKey = packageSpec.replace("/", "+");
5645
- const cacheRoot = join17(options.cwd, ".harness", "cache", "workflow-packages", cacheKey);
5646
- if (await pathExists3(join17(cacheRoot, "harness", "manifests"))) {
5845
+ const cacheRoot = join18(options.cwd, ".harness", "cache", "workflow-packages", cacheKey);
5846
+ if (await pathExists3(join18(cacheRoot, "harness", "manifests"))) {
5647
5847
  if (version === "latest") {
5648
5848
  const stale = await latestWorkflowCacheIsStale(cacheRoot, packageName, options.pacoteManifest);
5649
5849
  if (stale) {
@@ -5689,9 +5889,9 @@ function describeWorkflowDataFetchFailure(error, packageSpec) {
5689
5889
  return `\u65E0\u6CD5\u83B7\u53D6\u5DE5\u4F5C\u6D41\u6570\u636E\u5305 ${packageSpec}\uFF1A\u672C\u5730\u7F13\u5B58\u4E0D\u5B58\u5728\u4E14\u83B7\u53D6\u5931\u8D25\u3002\u53EF${hintRoot}\u3002\u539F\u56E0\uFF1A${code !== "" ? code + " " : ""}${detail}`;
5690
5890
  }
5691
5891
  async function readWorkflowFamilyManifest(resourcesRoot) {
5692
- const manifestPath = join17(resourcesRoot, "hunter-workflow-family.json");
5892
+ const manifestPath = join18(resourcesRoot, "hunter-workflow-family.json");
5693
5893
  if (!await pathExists3(manifestPath)) return {};
5694
- return JSON.parse(await readFile15(manifestPath, "utf8"));
5894
+ return JSON.parse(await readFile16(manifestPath, "utf8"));
5695
5895
  }
5696
5896
 
5697
5897
  // src/bin.ts
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
- {
1
+ {
2
2
  "name": "hunter-harness",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "description": "Local-first, server-governed agent harness",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "engines": {
8
- "node": ">=24.0.0"
8
+ "node": ">=22.12.0"
9
9
  },
10
10
  "bin": {
11
11
  "hunter-harness": "dist/bin.js"
@@ -15,7 +15,7 @@
15
15
  ],
16
16
  "scripts": {
17
17
  "build": "node ../../node_modules/typescript/bin/tsc -p tsconfig.json && npm run bundle",
18
- "bundle": "node ../../scripts/bundle-cli.mjs",
18
+ "bundle": "node ../../scripts/bundle-cli.mjs",
19
19
  "typecheck": "node ../../node_modules/typescript/bin/tsc -p tsconfig.json --noEmit",
20
20
  "prepack": "npm run build"
21
21
  },
@@ -25,7 +25,7 @@
25
25
  "zod": "4.4.3"
26
26
  },
27
27
  "optionalDependencies": {
28
- "pacote": "^21.0.0"
28
+ "pacote": "21.4.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@hunter-harness/contracts": "*",