hunter-harness 0.2.6 → 0.2.7

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/dist/bin.js +207 -76
  2. package/package.json +2 -2
package/dist/bin.js CHANGED
@@ -1670,6 +1670,9 @@ function affectedPaths(operation) {
1670
1670
  }
1671
1671
  async function writeJournal(transactionRoot, journal) {
1672
1672
  await atomicWriteJson(join3(transactionRoot, "journal.json"), journal);
1673
+ await writeStatus(transactionRoot, journal);
1674
+ }
1675
+ async function writeStatus(transactionRoot, journal) {
1673
1676
  await atomicWriteJson(join3(transactionRoot, "status.json"), {
1674
1677
  schema_version: 1,
1675
1678
  transaction_id: journal.transaction_id,
@@ -1679,6 +1682,16 @@ async function writeJournal(transactionRoot, journal) {
1679
1682
  updated_at: (/* @__PURE__ */ new Date()).toISOString()
1680
1683
  });
1681
1684
  }
1685
+ function journalOperation(operation) {
1686
+ if (operation.operation === "rename") {
1687
+ return {
1688
+ operation: "rename",
1689
+ from_path: operation.from_path,
1690
+ to_path: operation.to_path
1691
+ };
1692
+ }
1693
+ return { operation: operation.operation, path: operation.path };
1694
+ }
1682
1695
  async function pruneOlderSuccessful(layout, currentId, kind) {
1683
1696
  if (kind === void 0)
1684
1697
  return;
@@ -1799,12 +1812,12 @@ async function runTransaction(projectRoot, rawOperations, options = {}) {
1799
1812
  const snapshots = await snapshotPaths(projectRoot, transactionRoot, paths);
1800
1813
  await stageOperations(transactionRoot, operations);
1801
1814
  const journal = {
1802
- schema_version: 1,
1815
+ schema_version: 2,
1803
1816
  transaction_id: transactionId,
1804
1817
  ...options.kind === void 0 ? {} : { kind: options.kind },
1805
1818
  state: "prepared",
1806
1819
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
1807
- operations,
1820
+ operations: operations.map(journalOperation),
1808
1821
  snapshots,
1809
1822
  applied_count: 0,
1810
1823
  failure: null
@@ -1820,7 +1833,7 @@ async function runTransaction(projectRoot, rawOperations, options = {}) {
1820
1833
  }
1821
1834
  await applyOperation(projectRoot, transactionRoot, operation, index, transactionId);
1822
1835
  journal.applied_count = index + 1;
1823
- await writeJournal(transactionRoot, journal);
1836
+ await writeStatus(transactionRoot, journal);
1824
1837
  if (options.interruptAfterApply === journal.applied_count) {
1825
1838
  journal.state = "interrupted";
1826
1839
  journal.failure = "injected interruption";
@@ -2346,6 +2359,7 @@ async function initializeProject(options) {
2346
2359
  }
2347
2360
  manifests.push({
2348
2361
  adapter: agent,
2362
+ profile,
2349
2363
  bundle_version: bundle.manifest.bundle_version,
2350
2364
  bundle_manifest_hash: bundleHash
2351
2365
  });
@@ -2450,9 +2464,9 @@ async function initializeProject(options) {
2450
2464
  return byTarget !== 0 ? byTarget : left.block_id.localeCompare(right.block_id);
2451
2465
  });
2452
2466
  const installedState = {
2453
- schema_version: 3,
2454
- profile,
2467
+ schema_version: 4,
2455
2468
  adapters: enabledAgents,
2469
+ profiles: Object.fromEntries(enabledAgents.map((agent) => [agent, profile])),
2456
2470
  installed_at: (/* @__PURE__ */ new Date()).toISOString(),
2457
2471
  manifests,
2458
2472
  files: mergedTargets.map((target) => ({
@@ -2507,17 +2521,35 @@ async function readOptionalText(path) {
2507
2521
  async function readInstalledState(root) {
2508
2522
  const content = await readOptionalText(join6(root, INSTALLED_STATE_PATH));
2509
2523
  if (content === "") {
2510
- return { profile: null, schemaVersion: null, adapters: [], trusted: /* @__PURE__ */ new Map() };
2524
+ return {
2525
+ profile: null,
2526
+ schemaVersion: null,
2527
+ adapters: [],
2528
+ profiles: /* @__PURE__ */ new Map(),
2529
+ trusted: /* @__PURE__ */ new Map(),
2530
+ files: [],
2531
+ manifests: [],
2532
+ managedBlocks: []
2533
+ };
2511
2534
  }
2512
2535
  let parsed;
2513
2536
  try {
2514
2537
  parsed = JSON.parse(content);
2515
2538
  } catch {
2516
- return { profile: null, schemaVersion: null, adapters: [], trusted: /* @__PURE__ */ new Map() };
2539
+ return {
2540
+ profile: null,
2541
+ schemaVersion: null,
2542
+ adapters: [],
2543
+ profiles: /* @__PURE__ */ new Map(),
2544
+ trusted: /* @__PURE__ */ new Map(),
2545
+ files: [],
2546
+ manifests: [],
2547
+ managedBlocks: []
2548
+ };
2517
2549
  }
2518
2550
  const profile = parseHarnessProfile(parsed.profile);
2519
2551
  const trusted = /* @__PURE__ */ new Map();
2520
- if ((parsed.schema_version === 2 || parsed.schema_version === 3) && Array.isArray(parsed.files)) {
2552
+ if ((parsed.schema_version === 2 || parsed.schema_version === 3 || parsed.schema_version === 4) && Array.isArray(parsed.files)) {
2521
2553
  for (const entry of parsed.files) {
2522
2554
  if (entry !== null && typeof entry === "object" && "target_path" in entry && "sha256" in entry) {
2523
2555
  const target = entry.target_path;
@@ -2529,8 +2561,42 @@ async function readInstalledState(root) {
2529
2561
  }
2530
2562
  }
2531
2563
  const schemaVersion = typeof parsed.schema_version === "number" ? parsed.schema_version : null;
2532
- const adapters = schemaVersion === 3 && Array.isArray(parsed.adapters) ? sortHarnessAgents(parsed.adapters.filter((value) => value === "claude-code" || value === "codex" || value === "cursor" || value === "codebuddy")) : schemaVersion === 1 || schemaVersion === 2 ? ["claude-code"] : [];
2533
- return { profile, schemaVersion, adapters, trusted };
2564
+ const adapters = (schemaVersion === 3 || schemaVersion === 4) && Array.isArray(parsed.adapters) ? sortHarnessAgents(parsed.adapters.filter((value) => value === "claude-code" || value === "codex" || value === "cursor" || value === "codebuddy")) : schemaVersion === 1 || schemaVersion === 2 ? ["claude-code"] : [];
2565
+ const profiles = /* @__PURE__ */ new Map();
2566
+ if (schemaVersion === 4 && parsed.profiles !== null && typeof parsed.profiles === "object" && !Array.isArray(parsed.profiles)) {
2567
+ for (const agent of adapters) {
2568
+ const value = parsed.profiles[agent];
2569
+ const agentProfile = parseHarnessProfile(value);
2570
+ if (agentProfile !== null)
2571
+ profiles.set(agent, agentProfile);
2572
+ }
2573
+ } else if (profile !== null) {
2574
+ for (const agent of adapters)
2575
+ profiles.set(agent, profile);
2576
+ }
2577
+ const files = Array.isArray(parsed.files) ? parsed.files.filter((entry) => entry !== null && typeof entry === "object" && typeof entry.target_path === "string" && typeof entry.source_path === "string" && typeof entry.sha256 === "string" && "owner" in entry) : [];
2578
+ const manifests = schemaVersion === 4 && Array.isArray(parsed.manifests) ? parsed.manifests.filter((entry) => entry !== null && typeof entry === "object" && typeof entry.adapter === "string" && typeof entry.profile === "string") : [];
2579
+ const managedBlocks = Array.isArray(parsed.managed_blocks) ? parsed.managed_blocks.filter((entry) => entry !== null && typeof entry === "object" && typeof entry.target_path === "string" && typeof entry.block_id === "string") : [];
2580
+ return {
2581
+ profile,
2582
+ schemaVersion,
2583
+ adapters,
2584
+ profiles,
2585
+ trusted,
2586
+ files,
2587
+ manifests,
2588
+ managedBlocks
2589
+ };
2590
+ }
2591
+ async function readInstalledAgentConfiguration(projectRoot) {
2592
+ const installed = await readInstalledState(resolve4(projectRoot));
2593
+ return {
2594
+ agents: installed.adapters,
2595
+ profiles: Object.fromEntries(installed.adapters.map((agent) => [
2596
+ agent,
2597
+ installed.profiles.get(agent) ?? installed.profile ?? "general"
2598
+ ]))
2599
+ };
2534
2600
  }
2535
2601
  async function readContextIndexBundleHash(root) {
2536
2602
  const content = await readOptionalText(join6(root, CONTEXT_INDEX_PATH));
@@ -2588,16 +2654,18 @@ function conflict(target, reason, oldSha, incomingSha) {
2588
2654
  function sortByTarget(items) {
2589
2655
  return [...items].sort((left, right) => left.target_path.localeCompare(right.target_path));
2590
2656
  }
2591
- async function reconcileContextIndex(root, profile, agents, manifests, codebuddySurface2) {
2657
+ async function reconcileContextIndex(root, agents, profiles, manifests, codebuddySurface2) {
2592
2658
  const existing = await readOptionalText(join6(root, CONTEXT_INDEX_PATH));
2593
- const context = { profile, codebuddySurface: codebuddySurface2 };
2594
2659
  const record = {
2595
2660
  schema_version: 2,
2596
2661
  project: {
2597
2662
  shared_instructions: "AGENTS.md",
2598
2663
  adapters: Object.fromEntries(agents.map((agent) => [
2599
2664
  agent,
2600
- getAdapter(agent).contextIndex(context)
2665
+ getAdapter(agent).contextIndex({
2666
+ profile: profiles.get(agent) ?? "general",
2667
+ codebuddySurface: codebuddySurface2
2668
+ })
2601
2669
  ]))
2602
2670
  },
2603
2671
  knowledge: { index: ".harness/knowledge/index.json" },
@@ -2616,23 +2684,25 @@ async function reconcileContextIndex(root, profile, agents, manifests, codebuddy
2616
2684
  content: next
2617
2685
  };
2618
2686
  }
2619
- async function projectTransitionOperation(root, previousProfile, profile, oldAgents, agents, codebuddySurface2) {
2620
- if (previousProfile === profile && oldAgents.length === agents.length && oldAgents.every((agent, index) => agent === agents[index]))
2621
- return null;
2687
+ async function projectTransitionOperation(root, agents, profiles, codebuddySurface2) {
2622
2688
  const path = ".harness/project.yaml";
2623
2689
  const content = await readOptionalText(join6(root, path));
2624
2690
  if (content === "")
2625
2691
  return null;
2626
2692
  const project = parseYaml3(content);
2693
+ const activeProfiles = [...new Set(agents.map((agent) => profiles.get(agent) ?? "general"))].sort();
2694
+ const next = stringifyYaml2({
2695
+ ...project,
2696
+ project: { ...project.project, profiles: activeProfiles },
2697
+ adapters: { enabled: agents },
2698
+ ...agents.includes("codebuddy") ? { adapter_options: { codebuddy: { surface: codebuddySurface2 } } } : { adapter_options: void 0 }
2699
+ }, { sortMapEntries: true });
2700
+ if (next === content)
2701
+ return null;
2627
2702
  return {
2628
2703
  operation: "modify",
2629
2704
  path,
2630
- content: stringifyYaml2({
2631
- ...project,
2632
- project: { ...project.project, profiles: [profile] },
2633
- adapters: { enabled: agents },
2634
- ...agents.includes("codebuddy") ? { adapter_options: { codebuddy: { surface: codebuddySurface2 } } } : { adapter_options: void 0 }
2635
- }, { sortMapEntries: true })
2705
+ content: next
2636
2706
  };
2637
2707
  }
2638
2708
  function mergeTargets(targets) {
@@ -2691,30 +2761,42 @@ function stateWithoutInstalledAt(value) {
2691
2761
  }
2692
2762
  async function refreshProject(options) {
2693
2763
  const root = resolve4(options.projectRoot);
2694
- const profile = options.profile;
2695
2764
  const installed = await readInstalledState(root);
2696
- const previousProfile = installed.profile;
2697
- const agents = sortHarnessAgents(options.agents);
2698
2765
  const oldAgents = installed.adapters.length > 0 ? installed.adapters : ["claude-code"];
2766
+ const selectedAgents = sortHarnessAgents(options.agents);
2767
+ const selectedSet = new Set(selectedAgents);
2768
+ const agents = sortHarnessAgents([...oldAgents, ...selectedAgents]);
2769
+ const profiles = new Map(installed.profiles);
2770
+ if (options.profile !== void 0) {
2771
+ for (const agent of selectedAgents)
2772
+ profiles.set(agent, options.profile);
2773
+ }
2774
+ const profile = options.profile ?? selectedAgents.map((agent) => profiles.get(agent)).find((value) => value !== void 0) ?? installed.profile ?? "general";
2775
+ const previousProfile = selectedAgents.map((agent) => installed.profiles.get(agent)).find((value) => value !== void 0) ?? installed.profile;
2699
2776
  const codebuddySurface2 = options.codebuddySurface ?? "both";
2700
- const context = { profile, codebuddySurface: codebuddySurface2 };
2701
2777
  const owned = [];
2702
2778
  const manifests = [];
2703
2779
  for (const agent of agents) {
2704
- const bundle = await loadAgentBundle(options.resourcesRoot, profile, agent);
2780
+ const agentProfile = profiles.get(agent) ?? profile;
2781
+ profiles.set(agent, agentProfile);
2782
+ const bundle = await loadAgentBundle(options.resourcesRoot, agentProfile, agent);
2705
2783
  manifests.push({
2706
2784
  adapter: agent,
2785
+ profile: agentProfile,
2707
2786
  bundle_version: bundle.manifest.bundle_version,
2708
2787
  bundle_manifest_hash: sha256Bytes(canonicalJson(bundle.manifest.files))
2709
2788
  });
2710
- for (const target of managedTargetsFor(getAdapter(agent), bundle, context)) {
2711
- owned.push({ ...target, owner: agent });
2789
+ if (selectedSet.has(agent)) {
2790
+ const context = { profile: agentProfile, codebuddySurface: codebuddySurface2 };
2791
+ for (const target of managedTargetsFor(getAdapter(agent), bundle, context)) {
2792
+ owned.push({ ...target, owner: agent });
2793
+ }
2712
2794
  }
2713
2795
  }
2714
2796
  const newManaged = mergeTargets(owned);
2715
2797
  let trusted = installed.trusted;
2716
2798
  let migrationOldPaths = null;
2717
- if (installed.schemaVersion === 1) {
2799
+ if (installed.schemaVersion === 1 && selectedSet.has("claude-code")) {
2718
2800
  const contextHash = await readContextIndexBundleHash(root);
2719
2801
  if (contextHash !== null) {
2720
2802
  const migrations = await loadMigrationManifests(options.resourcesRoot);
@@ -2738,12 +2820,18 @@ async function refreshProject(options) {
2738
2820
  });
2739
2821
  }
2740
2822
  }
2741
- } else if (previousProfile !== null) {
2742
- const oldContext = { profile: previousProfile, codebuddySurface: codebuddySurface2 };
2823
+ } else {
2743
2824
  const oldTargets = [];
2744
- for (const agent of oldAgents) {
2745
- const bundle = await loadAgentBundle(options.resourcesRoot, previousProfile, agent);
2746
- oldTargets.push(...managedTargetsFor(getAdapter(agent), bundle, oldContext));
2825
+ for (const agent of selectedAgents) {
2826
+ const oldProfile = installed.profiles.get(agent) ?? installed.profile;
2827
+ if (!oldAgents.includes(agent) || oldProfile === null || oldProfile === void 0) {
2828
+ continue;
2829
+ }
2830
+ const bundle = await loadAgentBundle(options.resourcesRoot, oldProfile, agent);
2831
+ oldTargets.push(...managedTargetsFor(getAdapter(agent), bundle, {
2832
+ profile: oldProfile,
2833
+ codebuddySurface: codebuddySurface2
2834
+ }));
2747
2835
  }
2748
2836
  oldOnly = oldTargets.filter((target) => !newTargetSet.has(target.target_path));
2749
2837
  }
@@ -2753,7 +2841,7 @@ async function refreshProject(options) {
2753
2841
  const unchanged = [];
2754
2842
  const conflicts = [];
2755
2843
  const ops = [];
2756
- const newStateFiles = [];
2844
+ const newStateFiles = installed.files.filter((entry) => entry.owner === "shared" || !selectedSet.has(entry.owner));
2757
2845
  for (const target of newManaged) {
2758
2846
  const incoming = target.sha256;
2759
2847
  const current = await fileHex(join6(root, target.target_path));
@@ -2801,41 +2889,50 @@ async function refreshProject(options) {
2801
2889
  }
2802
2890
  }
2803
2891
  await reconcileMarkdownBlock(root, "AGENTS.md", AGENTS_CORE_BLOCK_ID, AGENTS_MANAGED_BLOCK_CONTENT, false, ops, conflicts, preserved);
2804
- await reconcileMarkdownBlock(root, "CLAUDE.md", CLAUDE_BLOCK_ID, CLAUDE_MANAGED_BLOCK_CONTENT, !agents.includes("claude-code"), ops, conflicts, preserved);
2805
- await reconcileMarkdownBlock(root, "CODEBUDDY.md", CODEBUDDY_BLOCK_ID, CODEBUDDY_MANAGED_BLOCK_CONTENT, !agents.includes("codebuddy"), ops, conflicts, preserved);
2806
- const projectOperation = await projectTransitionOperation(root, previousProfile, profile, oldAgents, agents, codebuddySurface2);
2892
+ if (selectedSet.has("claude-code")) {
2893
+ await reconcileMarkdownBlock(root, "CLAUDE.md", CLAUDE_BLOCK_ID, CLAUDE_MANAGED_BLOCK_CONTENT, false, ops, conflicts, preserved);
2894
+ }
2895
+ if (selectedSet.has("codebuddy")) {
2896
+ await reconcileMarkdownBlock(root, "CODEBUDDY.md", CODEBUDDY_BLOCK_ID, CODEBUDDY_MANAGED_BLOCK_CONTENT, false, ops, conflicts, preserved);
2897
+ }
2898
+ const projectOperation = await projectTransitionOperation(root, agents, profiles, codebuddySurface2);
2807
2899
  if (projectOperation !== null)
2808
2900
  ops.push(projectOperation);
2809
- const contextOperation = await reconcileContextIndex(root, profile, agents, manifests, codebuddySurface2);
2901
+ const contextOperation = await reconcileContextIndex(root, agents, profiles, manifests, codebuddySurface2);
2810
2902
  if (contextOperation !== null)
2811
2903
  ops.push(contextOperation);
2812
2904
  const managedBlocks = [
2905
+ ...installed.managedBlocks.filter((entry) => entry.owner !== "shared" && !selectedSet.has(entry.owner)),
2813
2906
  {
2814
2907
  owner: "shared",
2815
2908
  target_path: "AGENTS.md",
2816
2909
  block_id: AGENTS_CORE_BLOCK_ID,
2817
2910
  content_sha256: createHash5("sha256").update(AGENTS_MANAGED_BLOCK_CONTENT).digest("hex")
2818
2911
  },
2819
- ...agents.includes("claude-code") ? [{
2912
+ ...selectedSet.has("claude-code") ? [{
2820
2913
  owner: "claude-code",
2821
2914
  target_path: "CLAUDE.md",
2822
2915
  block_id: CLAUDE_BLOCK_ID,
2823
2916
  content_sha256: createHash5("sha256").update(CLAUDE_MANAGED_BLOCK_CONTENT).digest("hex")
2824
2917
  }] : [],
2825
- ...agents.includes("codebuddy") ? [{
2918
+ ...selectedSet.has("codebuddy") ? [{
2826
2919
  owner: "codebuddy",
2827
2920
  target_path: "CODEBUDDY.md",
2828
2921
  block_id: CODEBUDDY_BLOCK_ID,
2829
2922
  content_sha256: createHash5("sha256").update(CODEBUDDY_MANAGED_BLOCK_CONTENT).digest("hex")
2830
2923
  }] : []
2831
2924
  ].sort((left, right) => left.target_path.localeCompare(right.target_path) || left.block_id.localeCompare(right.block_id));
2925
+ const filesByTarget = new Map(newStateFiles.map((entry) => [entry.target_path, entry]));
2832
2926
  const installedState = {
2833
- schema_version: 3,
2834
- profile,
2927
+ schema_version: 4,
2835
2928
  adapters: agents,
2929
+ profiles: Object.fromEntries(agents.map((agent) => [
2930
+ agent,
2931
+ profiles.get(agent) ?? "general"
2932
+ ])),
2836
2933
  installed_at: (/* @__PURE__ */ new Date()).toISOString(),
2837
- manifests,
2838
- files: newStateFiles.sort((left, right) => left.target_path.localeCompare(right.target_path) || left.source_path.localeCompare(right.source_path)),
2934
+ manifests: manifests.sort((left, right) => left.adapter.localeCompare(right.adapter)),
2935
+ files: [...filesByTarget.values()].sort((left, right) => left.target_path.localeCompare(right.target_path) || left.source_path.localeCompare(right.source_path)),
2839
2936
  managed_blocks: managedBlocks
2840
2937
  };
2841
2938
  const existingState = await readOptionalText(join6(root, INSTALLED_STATE_PATH));
@@ -2853,7 +2950,10 @@ async function refreshProject(options) {
2853
2950
  }
2854
2951
  if (!options.dryRun) {
2855
2952
  await runTransaction(root, ops, { kind: "refresh" });
2856
- await pruneEmptyParentDirs(root, removed.map((item2) => item2.target_path), getAdapters([.../* @__PURE__ */ new Set([...agents, ...oldAgents])]).flatMap((adapter) => adapter.pruneBoundaries(context)));
2953
+ await pruneEmptyParentDirs(root, removed.map((item2) => item2.target_path), getAdapters(selectedAgents).flatMap((adapter) => adapter.pruneBoundaries({
2954
+ profile: profiles.get(adapter.name) ?? profile,
2955
+ codebuddySurface: codebuddySurface2
2956
+ })));
2857
2957
  }
2858
2958
  return {
2859
2959
  profile,
@@ -4568,9 +4668,9 @@ async function detectProject(root) {
4568
4668
  }
4569
4669
  return { status: "valid", config: parsed.data };
4570
4670
  }
4571
- function parseProfile(value, current) {
4671
+ function parseProfile(value) {
4572
4672
  if (value === void 0 || value === "") {
4573
- return current;
4673
+ return void 0;
4574
4674
  }
4575
4675
  if (value === "1" || value === "general") return "general";
4576
4676
  if (value === "2" || value === "java") return "java";
@@ -4655,7 +4755,7 @@ async function runRefresh(options, dependencies) {
4655
4755
  let targetProfile;
4656
4756
  let targetAgents;
4657
4757
  try {
4658
- targetProfile = parseProfile(options.profile, currentProfile);
4758
+ targetProfile = parseProfile(options.profile);
4659
4759
  targetAgents = options.agents === void 0 ? refreshAgents(detection.config) : parseAgentsInput(options.agents);
4660
4760
  } catch (error) {
4661
4761
  const message = error instanceof Error ? error.message : String(error);
@@ -4664,12 +4764,12 @@ async function runRefresh(options, dependencies) {
4664
4764
  return error instanceof InitConfigurationError ? error.exitCode : 3;
4665
4765
  }
4666
4766
  const dryRun = options.dryRun === true;
4667
- if ((targetProfile !== currentProfile || targetAgents.some((agent, index) => agent !== refreshAgents(detection.config)[index]) || targetAgents.length !== refreshAgents(detection.config).length) && !dryRun) {
4767
+ if ((targetProfile !== void 0 && targetProfile !== currentProfile || targetAgents.some((agent, index) => agent !== refreshAgents(detection.config)[index]) || targetAgents.length !== refreshAgents(detection.config).length) && !dryRun) {
4668
4768
  try {
4669
4769
  const preview = await refreshProject({
4670
4770
  projectRoot: dependencies.cwd,
4671
4771
  resourcesRoot: dependencies.resourcesRoot,
4672
- profile: targetProfile,
4772
+ ...targetProfile === void 0 ? {} : { profile: targetProfile },
4673
4773
  agents: targetAgents,
4674
4774
  codebuddySurface: codebuddySurface(detection.config),
4675
4775
  dryRun: true,
@@ -4694,7 +4794,7 @@ async function runRefresh(options, dependencies) {
4694
4794
  return 2;
4695
4795
  }
4696
4796
  } else if (!options.yes && !dryRun) {
4697
- const label = targetProfile === currentProfile ? `\u5237\u65B0\u5F53\u524D\u914D\u7F6E\uFF08${currentProfile}\uFF09` : `\u5207\u6362\u914D\u7F6E\uFF1A${currentProfile} \u2192 ${targetProfile}`;
4797
+ const label = targetProfile === currentProfile ? `\u5237\u65B0\u5F53\u524D\u914D\u7F6E\uFF08${currentProfile}\uFF09` : targetProfile === void 0 ? "\u5237\u65B0\u6240\u9009\u5DE5\u5177\u7684\u5F53\u524D\u914D\u7F6E" : `\u66F4\u65B0\u6240\u9009\u5DE5\u5177\u914D\u7F6E\uFF1A${currentProfile} \u2192 ${targetProfile}`;
4698
4798
  const answer = await dependencies.prompt(`${label}\uFF1F[y/N]\uFF1A`);
4699
4799
  if (!/^(?:y|yes)$/i.test(answer.trim())) {
4700
4800
  return 2;
@@ -4705,7 +4805,7 @@ async function runRefresh(options, dependencies) {
4705
4805
  const result = await refreshProject({
4706
4806
  projectRoot: dependencies.cwd,
4707
4807
  resourcesRoot: dependencies.resourcesRoot,
4708
- profile: targetProfile,
4808
+ ...targetProfile === void 0 ? {} : { profile: targetProfile },
4709
4809
  agents: targetAgents,
4710
4810
  codebuddySurface: codebuddySurface(detection.config),
4711
4811
  dryRun,
@@ -4720,7 +4820,7 @@ async function runRefresh(options, dependencies) {
4720
4820
  if (result.removed.length > 0) parts.push(`\u5DF2\u5220\u9664 ${result.removed.length} \u4E2A`);
4721
4821
  if (result.preserved.length > 0) parts.push(`\u5DF2\u4FDD\u7559 ${result.preserved.length} \u4E2A`);
4722
4822
  if (result.unchanged.length > 0) parts.push(`\u65E0\u9700\u53D8\u66F4 ${result.unchanged.length} \u4E2A`);
4723
- dependencies.stdout(`Harness \u5237\u65B0\uFF08${targetProfile}\uFF09\uFF1A${parts.join("\uFF0C") || "\u6CA1\u6709\u53D8\u66F4"}\u3002
4823
+ dependencies.stdout(`Harness \u5237\u65B0\uFF08${result.profile}\uFF09\uFF1A${parts.join("\uFF0C") || "\u6CA1\u6709\u53D8\u66F4"}\u3002
4724
4824
  `);
4725
4825
  }
4726
4826
  return output.exit_code;
@@ -4750,9 +4850,12 @@ async function runRefresh(options, dependencies) {
4750
4850
  }
4751
4851
 
4752
4852
  // src/commands/configure.ts
4753
- function otherProfile(current) {
4754
- return current === "general" ? "java" : "general";
4755
- }
4853
+ var AGENT_LABELS = {
4854
+ "claude-code": "Claude Code",
4855
+ codex: "Codex",
4856
+ cursor: "Cursor",
4857
+ codebuddy: "CodeBuddy"
4858
+ };
4756
4859
  async function runFirstInstall(options, dependencies) {
4757
4860
  const requestId = uuidV7();
4758
4861
  try {
@@ -4834,21 +4937,40 @@ async function runExistingProject(options, dependencies, currentProfile) {
4834
4937
  if (options.nonInteractive === true) {
4835
4938
  return runRefresh(refreshOptions, dependencies);
4836
4939
  }
4837
- const menu = await dependencies.prompt(
4838
- `Hunter Harness \u5DF2\u521D\u59CB\u5316\uFF08profile: ${currentProfile}\uFF09\u3002
4839
- 1. \u5237\u65B0\u5F53\u524D\u914D\u7F6E\uFF08\u9ED8\u8BA4\u4E14\u63A8\u8350\uFF09
4840
- 2. \u5207\u6362\u5230\u53E6\u4E00\u79CD\u914D\u7F6E
4841
- 3. \u53D6\u6D88
4842
- \u8BF7\u9009\u62E9 [1]: `
4843
- );
4844
- const choice = menu.trim();
4845
- if (choice === "3" || /^c/i.test(choice)) {
4846
- return 2;
4847
- }
4848
- if (choice === "2" || /^s/i.test(choice)) {
4849
- refreshOptions.profile = otherProfile(currentProfile);
4850
- } else {
4851
- refreshOptions.profile = currentProfile;
4940
+ const installed = await readInstalledAgentConfiguration(dependencies.cwd);
4941
+ const currentAgents = installed.agents.length > 0 ? installed.agents : ["claude-code"];
4942
+ const currentLines = currentAgents.map(
4943
+ (agent) => `- ${AGENT_LABELS[agent]}\uFF1A${installed.profiles[agent] ?? currentProfile}`
4944
+ ).join("\n");
4945
+ if (refreshOptions.agents === void 0) {
4946
+ const defaultSelection = currentAgents.map((agent) => String(HARNESS_AGENT_ORDER.indexOf(agent) + 1)).join(",");
4947
+ const answer = await dependencies.prompt(
4948
+ `Hunter Harness \u5F53\u524D\u914D\u7F6E\uFF1A
4949
+ ${currentLines}
4950
+ \u8BF7\u9009\u62E9\u672C\u6B21\u8981\u65B0\u589E\u6216\u5237\u65B0\u7684\u5DE5\u5177\uFF08\u53EF\u591A\u9009\uFF0C\u9017\u53F7\u5206\u9694\uFF1B\u672A\u9009\u62E9\u7684\u5DE5\u5177\u4FDD\u6301\u4E0D\u53D8\uFF09\uFF1A
4951
+ 1. Claude Code
4952
+ 2. Codex
4953
+ 3. Cursor
4954
+ 4. CodeBuddy
4955
+ \u8BF7\u8F93\u5165\u7F16\u53F7 [${defaultSelection}]\uFF0C\u6216\u8F93\u5165 0 \u53D6\u6D88\uFF1A`
4956
+ );
4957
+ if (answer.trim() === "0" || /^c/i.test(answer.trim())) return 2;
4958
+ refreshOptions.agents = answer.trim() === "" ? currentAgents.join(",") : answer.trim();
4959
+ }
4960
+ if (refreshOptions.profile === void 0) {
4961
+ const selected = parseAgentsInput(refreshOptions.agents);
4962
+ const selectedProfiles = new Set(selected.flatMap((agent) => {
4963
+ const profile = installed.profiles[agent];
4964
+ return profile === void 0 ? [] : [profile];
4965
+ }));
4966
+ const defaultProfile = selectedProfiles.size === 1 ? [...selectedProfiles][0] ?? currentProfile : currentProfile;
4967
+ const answer = await dependencies.prompt(
4968
+ `\u8BF7\u9009\u62E9\u6240\u9009\u5DE5\u5177\u4F7F\u7528\u7684 Harness \u914D\u7F6E\uFF1A
4969
+ 1. \u901A\u7528
4970
+ 2. Java
4971
+ \u8BF7\u8F93\u5165\u7F16\u53F7 [${defaultProfile === "java" ? "2" : "1"}]\uFF1A`
4972
+ );
4973
+ refreshOptions.profile = answer.trim() === "" ? defaultProfile : answer.trim();
4852
4974
  }
4853
4975
  refreshOptions.confirmed = true;
4854
4976
  return runRefresh(refreshOptions, dependencies);
@@ -5405,8 +5527,12 @@ async function verifyWorkflowPackageIntegrity(resourcesRoot) {
5405
5527
  async function monorepoResourcesRoot() {
5406
5528
  const here = dirname4(fileURLToPath(import.meta.url));
5407
5529
  const candidates = [
5408
- join17(here, "../../../../resources"),
5409
- join17(here, "../../../../../resources")
5530
+ // TypeScript source: packages/cli/src/workflow-data -> packages/workflow-data-harness.
5531
+ join17(here, "../../../workflow-data-harness"),
5532
+ // Bundled CLI: packages/cli/dist -> packages/workflow-data-harness.
5533
+ join17(here, "../../workflow-data-harness"),
5534
+ // Test/build layouts that preserve additional source directory levels.
5535
+ join17(here, "../../../../packages/workflow-data-harness")
5410
5536
  ];
5411
5537
  for (const candidate of candidates) {
5412
5538
  if (await pathExists3(join17(candidate, "harness", "manifests"))) return candidate;
@@ -5414,9 +5540,14 @@ async function monorepoResourcesRoot() {
5414
5540
  return null;
5415
5541
  }
5416
5542
  async function siblingWorkflowPackage(cwd) {
5543
+ const here = dirname4(fileURLToPath(import.meta.url));
5417
5544
  const candidates = [
5418
5545
  join17(cwd, "node_modules", "@hunter-harness", "workflow-harness"),
5419
- join17(dirname4(fileURLToPath(import.meta.url)), "..", "..", "workflow-data-harness")
5546
+ // Published layout: node_modules/hunter-harness/dist/bin.js alongside the
5547
+ // scoped workflow package in node_modules/@hunter-harness/workflow-harness.
5548
+ join17(here, "..", "..", "@hunter-harness", "workflow-harness"),
5549
+ // Monorepo bundled layout: packages/cli/dist/bin.js.
5550
+ join17(here, "..", "..", "workflow-data-harness")
5420
5551
  ];
5421
5552
  for (const candidate of candidates) {
5422
5553
  if (await pathExists3(join17(candidate, "harness", "manifests"))) return candidate;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hunter-harness",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Local-first, server-governed agent harness",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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 ../../node_modules/esbuild/bin/esbuild src/bin.ts --bundle --platform=node --format=esm --target=node24 --external:commander --external:yaml --external:zod --external:pacote --outfile=dist/bin.js",
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
  },