hunter-harness 0.2.5 → 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 +690 -240
  2. package/package.json +4 -4
package/dist/bin.js CHANGED
@@ -140,8 +140,18 @@ var baselineManifestSchema = z2.object({
140
140
  var finalizeProposalSchema = z2.object({
141
141
  schema_version: z2.literal(1),
142
142
  manifest_sha256: sha256Schema,
143
- base_artifact_id: z2.string().regex(/^art_/).nullable()
144
- }).strict();
143
+ base_artifact_id: z2.string().regex(/^art_/).nullable(),
144
+ sensitive_scan_skip: z2.literal(true).optional(),
145
+ sensitive_scan_skip_reason: z2.string().max(500).optional()
146
+ }).strict().superRefine((value, ctx) => {
147
+ if (value.sensitive_scan_skip_reason !== void 0 && value.sensitive_scan_skip !== true) {
148
+ ctx.addIssue({
149
+ code: "custom",
150
+ message: "sensitive_scan_skip_reason requires sensitive_scan_skip",
151
+ path: ["sensitive_scan_skip_reason"]
152
+ });
153
+ }
154
+ });
145
155
  var requestMetadataSchema = z2.object({
146
156
  request_id: z2.uuid(),
147
157
  idempotency_key: z2.uuid(),
@@ -1660,6 +1670,9 @@ function affectedPaths(operation) {
1660
1670
  }
1661
1671
  async function writeJournal(transactionRoot, journal) {
1662
1672
  await atomicWriteJson(join3(transactionRoot, "journal.json"), journal);
1673
+ await writeStatus(transactionRoot, journal);
1674
+ }
1675
+ async function writeStatus(transactionRoot, journal) {
1663
1676
  await atomicWriteJson(join3(transactionRoot, "status.json"), {
1664
1677
  schema_version: 1,
1665
1678
  transaction_id: journal.transaction_id,
@@ -1669,6 +1682,16 @@ async function writeJournal(transactionRoot, journal) {
1669
1682
  updated_at: (/* @__PURE__ */ new Date()).toISOString()
1670
1683
  });
1671
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
+ }
1672
1695
  async function pruneOlderSuccessful(layout, currentId, kind) {
1673
1696
  if (kind === void 0)
1674
1697
  return;
@@ -1789,12 +1812,12 @@ async function runTransaction(projectRoot, rawOperations, options = {}) {
1789
1812
  const snapshots = await snapshotPaths(projectRoot, transactionRoot, paths);
1790
1813
  await stageOperations(transactionRoot, operations);
1791
1814
  const journal = {
1792
- schema_version: 1,
1815
+ schema_version: 2,
1793
1816
  transaction_id: transactionId,
1794
1817
  ...options.kind === void 0 ? {} : { kind: options.kind },
1795
1818
  state: "prepared",
1796
1819
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
1797
- operations,
1820
+ operations: operations.map(journalOperation),
1798
1821
  snapshots,
1799
1822
  applied_count: 0,
1800
1823
  failure: null
@@ -1810,7 +1833,7 @@ async function runTransaction(projectRoot, rawOperations, options = {}) {
1810
1833
  }
1811
1834
  await applyOperation(projectRoot, transactionRoot, operation, index, transactionId);
1812
1835
  journal.applied_count = index + 1;
1813
- await writeJournal(transactionRoot, journal);
1836
+ await writeStatus(transactionRoot, journal);
1814
1837
  if (options.interruptAfterApply === journal.applied_count) {
1815
1838
  journal.state = "interrupted";
1816
1839
  journal.failure = "injected interruption";
@@ -2336,6 +2359,7 @@ async function initializeProject(options) {
2336
2359
  }
2337
2360
  manifests.push({
2338
2361
  adapter: agent,
2362
+ profile,
2339
2363
  bundle_version: bundle.manifest.bundle_version,
2340
2364
  bundle_manifest_hash: bundleHash
2341
2365
  });
@@ -2440,9 +2464,9 @@ async function initializeProject(options) {
2440
2464
  return byTarget !== 0 ? byTarget : left.block_id.localeCompare(right.block_id);
2441
2465
  });
2442
2466
  const installedState = {
2443
- schema_version: 3,
2444
- profile,
2467
+ schema_version: 4,
2445
2468
  adapters: enabledAgents,
2469
+ profiles: Object.fromEntries(enabledAgents.map((agent) => [agent, profile])),
2446
2470
  installed_at: (/* @__PURE__ */ new Date()).toISOString(),
2447
2471
  manifests,
2448
2472
  files: mergedTargets.map((target) => ({
@@ -2497,17 +2521,35 @@ async function readOptionalText(path) {
2497
2521
  async function readInstalledState(root) {
2498
2522
  const content = await readOptionalText(join6(root, INSTALLED_STATE_PATH));
2499
2523
  if (content === "") {
2500
- 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
+ };
2501
2534
  }
2502
2535
  let parsed;
2503
2536
  try {
2504
2537
  parsed = JSON.parse(content);
2505
2538
  } catch {
2506
- 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
+ };
2507
2549
  }
2508
2550
  const profile = parseHarnessProfile(parsed.profile);
2509
2551
  const trusted = /* @__PURE__ */ new Map();
2510
- 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)) {
2511
2553
  for (const entry of parsed.files) {
2512
2554
  if (entry !== null && typeof entry === "object" && "target_path" in entry && "sha256" in entry) {
2513
2555
  const target = entry.target_path;
@@ -2519,8 +2561,42 @@ async function readInstalledState(root) {
2519
2561
  }
2520
2562
  }
2521
2563
  const schemaVersion = typeof parsed.schema_version === "number" ? parsed.schema_version : null;
2522
- 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"] : [];
2523
- 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
+ };
2524
2600
  }
2525
2601
  async function readContextIndexBundleHash(root) {
2526
2602
  const content = await readOptionalText(join6(root, CONTEXT_INDEX_PATH));
@@ -2578,16 +2654,18 @@ function conflict(target, reason, oldSha, incomingSha) {
2578
2654
  function sortByTarget(items) {
2579
2655
  return [...items].sort((left, right) => left.target_path.localeCompare(right.target_path));
2580
2656
  }
2581
- async function reconcileContextIndex(root, profile, agents, manifests, codebuddySurface2) {
2657
+ async function reconcileContextIndex(root, agents, profiles, manifests, codebuddySurface2) {
2582
2658
  const existing = await readOptionalText(join6(root, CONTEXT_INDEX_PATH));
2583
- const context = { profile, codebuddySurface: codebuddySurface2 };
2584
2659
  const record = {
2585
2660
  schema_version: 2,
2586
2661
  project: {
2587
2662
  shared_instructions: "AGENTS.md",
2588
2663
  adapters: Object.fromEntries(agents.map((agent) => [
2589
2664
  agent,
2590
- getAdapter(agent).contextIndex(context)
2665
+ getAdapter(agent).contextIndex({
2666
+ profile: profiles.get(agent) ?? "general",
2667
+ codebuddySurface: codebuddySurface2
2668
+ })
2591
2669
  ]))
2592
2670
  },
2593
2671
  knowledge: { index: ".harness/knowledge/index.json" },
@@ -2606,23 +2684,25 @@ async function reconcileContextIndex(root, profile, agents, manifests, codebuddy
2606
2684
  content: next
2607
2685
  };
2608
2686
  }
2609
- async function projectTransitionOperation(root, previousProfile, profile, oldAgents, agents, codebuddySurface2) {
2610
- if (previousProfile === profile && oldAgents.length === agents.length && oldAgents.every((agent, index) => agent === agents[index]))
2611
- return null;
2687
+ async function projectTransitionOperation(root, agents, profiles, codebuddySurface2) {
2612
2688
  const path = ".harness/project.yaml";
2613
2689
  const content = await readOptionalText(join6(root, path));
2614
2690
  if (content === "")
2615
2691
  return null;
2616
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;
2617
2702
  return {
2618
2703
  operation: "modify",
2619
2704
  path,
2620
- content: stringifyYaml2({
2621
- ...project,
2622
- project: { ...project.project, profiles: [profile] },
2623
- adapters: { enabled: agents },
2624
- ...agents.includes("codebuddy") ? { adapter_options: { codebuddy: { surface: codebuddySurface2 } } } : { adapter_options: void 0 }
2625
- }, { sortMapEntries: true })
2705
+ content: next
2626
2706
  };
2627
2707
  }
2628
2708
  function mergeTargets(targets) {
@@ -2681,30 +2761,42 @@ function stateWithoutInstalledAt(value) {
2681
2761
  }
2682
2762
  async function refreshProject(options) {
2683
2763
  const root = resolve4(options.projectRoot);
2684
- const profile = options.profile;
2685
2764
  const installed = await readInstalledState(root);
2686
- const previousProfile = installed.profile;
2687
- const agents = sortHarnessAgents(options.agents);
2688
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;
2689
2776
  const codebuddySurface2 = options.codebuddySurface ?? "both";
2690
- const context = { profile, codebuddySurface: codebuddySurface2 };
2691
2777
  const owned = [];
2692
2778
  const manifests = [];
2693
2779
  for (const agent of agents) {
2694
- 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);
2695
2783
  manifests.push({
2696
2784
  adapter: agent,
2785
+ profile: agentProfile,
2697
2786
  bundle_version: bundle.manifest.bundle_version,
2698
2787
  bundle_manifest_hash: sha256Bytes(canonicalJson(bundle.manifest.files))
2699
2788
  });
2700
- for (const target of managedTargetsFor(getAdapter(agent), bundle, context)) {
2701
- 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
+ }
2702
2794
  }
2703
2795
  }
2704
2796
  const newManaged = mergeTargets(owned);
2705
2797
  let trusted = installed.trusted;
2706
2798
  let migrationOldPaths = null;
2707
- if (installed.schemaVersion === 1) {
2799
+ if (installed.schemaVersion === 1 && selectedSet.has("claude-code")) {
2708
2800
  const contextHash = await readContextIndexBundleHash(root);
2709
2801
  if (contextHash !== null) {
2710
2802
  const migrations = await loadMigrationManifests(options.resourcesRoot);
@@ -2728,12 +2820,18 @@ async function refreshProject(options) {
2728
2820
  });
2729
2821
  }
2730
2822
  }
2731
- } else if (previousProfile !== null) {
2732
- const oldContext = { profile: previousProfile, codebuddySurface: codebuddySurface2 };
2823
+ } else {
2733
2824
  const oldTargets = [];
2734
- for (const agent of oldAgents) {
2735
- const bundle = await loadAgentBundle(options.resourcesRoot, previousProfile, agent);
2736
- 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
+ }));
2737
2835
  }
2738
2836
  oldOnly = oldTargets.filter((target) => !newTargetSet.has(target.target_path));
2739
2837
  }
@@ -2743,7 +2841,7 @@ async function refreshProject(options) {
2743
2841
  const unchanged = [];
2744
2842
  const conflicts = [];
2745
2843
  const ops = [];
2746
- const newStateFiles = [];
2844
+ const newStateFiles = installed.files.filter((entry) => entry.owner === "shared" || !selectedSet.has(entry.owner));
2747
2845
  for (const target of newManaged) {
2748
2846
  const incoming = target.sha256;
2749
2847
  const current = await fileHex(join6(root, target.target_path));
@@ -2791,41 +2889,50 @@ async function refreshProject(options) {
2791
2889
  }
2792
2890
  }
2793
2891
  await reconcileMarkdownBlock(root, "AGENTS.md", AGENTS_CORE_BLOCK_ID, AGENTS_MANAGED_BLOCK_CONTENT, false, ops, conflicts, preserved);
2794
- await reconcileMarkdownBlock(root, "CLAUDE.md", CLAUDE_BLOCK_ID, CLAUDE_MANAGED_BLOCK_CONTENT, !agents.includes("claude-code"), ops, conflicts, preserved);
2795
- await reconcileMarkdownBlock(root, "CODEBUDDY.md", CODEBUDDY_BLOCK_ID, CODEBUDDY_MANAGED_BLOCK_CONTENT, !agents.includes("codebuddy"), ops, conflicts, preserved);
2796
- 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);
2797
2899
  if (projectOperation !== null)
2798
2900
  ops.push(projectOperation);
2799
- const contextOperation = await reconcileContextIndex(root, profile, agents, manifests, codebuddySurface2);
2901
+ const contextOperation = await reconcileContextIndex(root, agents, profiles, manifests, codebuddySurface2);
2800
2902
  if (contextOperation !== null)
2801
2903
  ops.push(contextOperation);
2802
2904
  const managedBlocks = [
2905
+ ...installed.managedBlocks.filter((entry) => entry.owner !== "shared" && !selectedSet.has(entry.owner)),
2803
2906
  {
2804
2907
  owner: "shared",
2805
2908
  target_path: "AGENTS.md",
2806
2909
  block_id: AGENTS_CORE_BLOCK_ID,
2807
2910
  content_sha256: createHash5("sha256").update(AGENTS_MANAGED_BLOCK_CONTENT).digest("hex")
2808
2911
  },
2809
- ...agents.includes("claude-code") ? [{
2912
+ ...selectedSet.has("claude-code") ? [{
2810
2913
  owner: "claude-code",
2811
2914
  target_path: "CLAUDE.md",
2812
2915
  block_id: CLAUDE_BLOCK_ID,
2813
2916
  content_sha256: createHash5("sha256").update(CLAUDE_MANAGED_BLOCK_CONTENT).digest("hex")
2814
2917
  }] : [],
2815
- ...agents.includes("codebuddy") ? [{
2918
+ ...selectedSet.has("codebuddy") ? [{
2816
2919
  owner: "codebuddy",
2817
2920
  target_path: "CODEBUDDY.md",
2818
2921
  block_id: CODEBUDDY_BLOCK_ID,
2819
2922
  content_sha256: createHash5("sha256").update(CODEBUDDY_MANAGED_BLOCK_CONTENT).digest("hex")
2820
2923
  }] : []
2821
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]));
2822
2926
  const installedState = {
2823
- schema_version: 3,
2824
- profile,
2927
+ schema_version: 4,
2825
2928
  adapters: agents,
2929
+ profiles: Object.fromEntries(agents.map((agent) => [
2930
+ agent,
2931
+ profiles.get(agent) ?? "general"
2932
+ ])),
2826
2933
  installed_at: (/* @__PURE__ */ new Date()).toISOString(),
2827
- manifests,
2828
- 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)),
2829
2936
  managed_blocks: managedBlocks
2830
2937
  };
2831
2938
  const existingState = await readOptionalText(join6(root, INSTALLED_STATE_PATH));
@@ -2843,7 +2950,10 @@ async function refreshProject(options) {
2843
2950
  }
2844
2951
  if (!options.dryRun) {
2845
2952
  await runTransaction(root, ops, { kind: "refresh" });
2846
- 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
+ })));
2847
2957
  }
2848
2958
  return {
2849
2959
  profile,
@@ -3100,8 +3210,9 @@ function scanSensitiveFiles(files, options = {}) {
3100
3210
  const path = normalizeManagedPath(inputPath);
3101
3211
  const ignores = parseInlineIgnores(content);
3102
3212
  const knowledgeMetadata = path === ".harness/knowledge" || path.startsWith(".harness/knowledge/");
3213
+ const archiveMetadata = path === ".harness/archive" || path.startsWith(".harness/archive/");
3103
3214
  for (const raw of rawFindings(content)) {
3104
- if (knowledgeMetadata && raw.ruleId === "HH_WINDOWS_ABSOLUTE_PATH") {
3215
+ if ((knowledgeMetadata || archiveMetadata) && raw.ruleId === "HH_WINDOWS_ABSOLUTE_PATH") {
3105
3216
  continue;
3106
3217
  }
3107
3218
  const position = location(content, raw.offset);
@@ -3172,29 +3283,147 @@ function generateProposalPreview(input, scanOptions = {}) {
3172
3283
  };
3173
3284
  }
3174
3285
 
3175
- // ../core/dist/push/push.js
3176
- import { lstat as lstat2, readFile as readFile8, readdir as readdir4, rm as rm4 } from "node:fs/promises";
3177
- import { join as join9, relative, resolve as resolve5 } from "node:path";
3286
+ // ../core/dist/push/credentials.js
3287
+ import { readFile as readFile6, writeFile as writeFile3 } from "node:fs/promises";
3288
+ import { join as join7 } from "node:path";
3178
3289
  import { parse as parseYaml4, stringify as stringifyYaml3 } from "yaml";
3290
+ var CREDENTIALS_LOCAL_RELATIVE = ".harness/credentials.local.yaml";
3291
+ var CREDENTIALS_GITIGNORE_LINES = [
3292
+ ".harness/credentials.local.yaml",
3293
+ ".harness/credentials.local.*"
3294
+ ];
3295
+ var InvalidCredentialsError = class extends Error {
3296
+ constructor(message) {
3297
+ super(message);
3298
+ this.name = "InvalidCredentialsError";
3299
+ }
3300
+ };
3301
+ function assertHttpsServerUrl(url) {
3302
+ try {
3303
+ const parsed = new URL(url.trim());
3304
+ if (parsed.protocol !== "https:") {
3305
+ throw new InvalidCredentialsError("server_url must use HTTPS");
3306
+ }
3307
+ return parsed.toString().replace(/\/$/, "");
3308
+ } catch (error) {
3309
+ if (error instanceof InvalidCredentialsError) {
3310
+ throw error;
3311
+ }
3312
+ throw new InvalidCredentialsError("server_url is invalid");
3313
+ }
3314
+ }
3315
+ function validateLocalCredentials(credentials) {
3316
+ const token = typeof credentials.token === "string" && credentials.token.trim().length > 0 ? credentials.token.trim() : void 0;
3317
+ const serverUrl = typeof credentials.server_url === "string" && credentials.server_url.trim().length > 0 ? assertHttpsServerUrl(credentials.server_url) : void 0;
3318
+ if (token === void 0 && serverUrl === void 0) {
3319
+ throw new InvalidCredentialsError("credentials.local must include token and/or server_url");
3320
+ }
3321
+ return {
3322
+ ...token === void 0 ? {} : { token },
3323
+ ...serverUrl === void 0 ? {} : { server_url: serverUrl }
3324
+ };
3325
+ }
3326
+ function parseLocalCredentials(raw) {
3327
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
3328
+ return null;
3329
+ }
3330
+ const record = raw;
3331
+ const token = typeof record.token === "string" && record.token.trim().length > 0 ? record.token.trim() : void 0;
3332
+ const serverUrlRaw = typeof record.server_url === "string" && record.server_url.trim().length > 0 ? record.server_url.trim() : void 0;
3333
+ let serverUrl;
3334
+ if (serverUrlRaw !== void 0) {
3335
+ try {
3336
+ serverUrl = assertHttpsServerUrl(serverUrlRaw);
3337
+ } catch {
3338
+ return null;
3339
+ }
3340
+ }
3341
+ if (token === void 0 && serverUrl === void 0) {
3342
+ return null;
3343
+ }
3344
+ return {
3345
+ ...token === void 0 ? {} : { token },
3346
+ ...serverUrl === void 0 ? {} : { server_url: serverUrl }
3347
+ };
3348
+ }
3349
+ function mergeLocalCredentials(existing, patch) {
3350
+ return validateLocalCredentials({
3351
+ ...existing?.token === void 0 ? {} : { token: existing.token },
3352
+ ...existing?.server_url === void 0 ? {} : { server_url: existing.server_url },
3353
+ ...patch
3354
+ });
3355
+ }
3356
+ async function readLocalCredentials(projectRoot) {
3357
+ try {
3358
+ const raw = await readFile6(join7(projectRoot, CREDENTIALS_LOCAL_RELATIVE), "utf8");
3359
+ return parseLocalCredentials(parseYaml4(raw));
3360
+ } catch (error) {
3361
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
3362
+ return null;
3363
+ }
3364
+ throw error;
3365
+ }
3366
+ }
3367
+ async function writeLocalCredentials(projectRoot, credentials) {
3368
+ const normalized = validateLocalCredentials(credentials);
3369
+ await writeFile3(join7(projectRoot, CREDENTIALS_LOCAL_RELATIVE), stringifyYaml3(normalized, { sortMapEntries: true }) + "\n", "utf8");
3370
+ }
3371
+ async function ensureCredentialsGitignore(projectRoot) {
3372
+ const gitignorePath = join7(projectRoot, ".gitignore");
3373
+ let content = "";
3374
+ try {
3375
+ content = await readFile6(gitignorePath, "utf8");
3376
+ } catch (error) {
3377
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) {
3378
+ throw error;
3379
+ }
3380
+ }
3381
+ const existing = new Set(content.split("\n").map((line) => line.trim()));
3382
+ const missing = CREDENTIALS_GITIGNORE_LINES.filter((line) => !existing.has(line));
3383
+ if (missing.length === 0) {
3384
+ return;
3385
+ }
3386
+ const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
3387
+ await writeFile3(gitignorePath, content + suffix + missing.join("\n") + "\n", "utf8");
3388
+ }
3389
+ function resolvePushAuth(input) {
3390
+ const tokenEnv = input.tokenEnv ?? input.projectTokenEnv;
3391
+ const envToken = input.env[tokenEnv]?.trim();
3392
+ const localToken = input.local?.token?.trim();
3393
+ const token = envToken && envToken.length > 0 ? envToken : localToken && localToken.length > 0 ? localToken : void 0;
3394
+ const serverUrl = input.serverUrlFlag ?? input.local?.server_url ?? input.projectUrl ?? void 0;
3395
+ if (serverUrl === void 0 || serverUrl === null || serverUrl.trim() === "") {
3396
+ return { code: "SERVER_URL_REQUIRED" };
3397
+ }
3398
+ if (token === void 0) {
3399
+ return { code: "TOKEN_INVALID" };
3400
+ }
3401
+ return { serverUrl, token };
3402
+ }
3403
+
3404
+ // ../core/dist/push/push.js
3405
+ import { lstat as lstat2, readFile as readFile9, readdir as readdir4, rm as rm4 } from "node:fs/promises";
3406
+ import { join as join10, relative, resolve as resolve5 } from "node:path";
3407
+ import { parse as parseYaml5, stringify as stringifyYaml4 } from "yaml";
3179
3408
 
3180
3409
  // ../core/dist/state/baseline.js
3181
- import { readFile as readFile6 } from "node:fs/promises";
3182
- import { join as join7 } from "node:path";
3410
+ import { readFile as readFile7 } from "node:fs/promises";
3411
+ import { join as join8 } from "node:path";
3183
3412
  async function readBaseline(projectRoot) {
3184
- const content = await readFile6(join7(stateLayout(projectRoot).baseline, "manifest.json"), "utf8");
3413
+ const content = await readFile7(join8(stateLayout(projectRoot).baseline, "manifest.json"), "utf8");
3185
3414
  return baselineManifestSchema.parse(JSON.parse(content));
3186
3415
  }
3187
3416
 
3188
3417
  // ../core/dist/state/locks.js
3189
3418
  import { randomUUID as randomUUID3 } from "node:crypto";
3190
- import { readFile as readFile7, rename as rename3, rm as rm3, writeFile as writeFile3 } from "node:fs/promises";
3191
- import { join as join8 } from "node:path";
3419
+ import { readFile as readFile8, rename as rename3, rm as rm3, writeFile as writeFile4 } from "node:fs/promises";
3420
+ import { join as join9 } from "node:path";
3192
3421
  async function readLock(path) {
3193
- return JSON.parse(await readFile7(path, "utf8"));
3422
+ return JSON.parse(await readFile8(path, "utf8"));
3194
3423
  }
3195
3424
  async function acquireProtocolLock(projectRoot, operation, options = {}) {
3196
3425
  const layout = await ensureStateLayout(projectRoot);
3197
- const lockPath = join8(layout.locks, "protocol.lock");
3426
+ const lockPath = join9(layout.locks, "protocol.lock");
3198
3427
  const now = options.now ?? Date.now();
3199
3428
  const staleAfterMs = options.staleAfterMs ?? 15 * 60 * 1e3;
3200
3429
  const record = {
@@ -3207,7 +3436,7 @@ async function acquireProtocolLock(projectRoot, operation, options = {}) {
3207
3436
  };
3208
3437
  for (let attempt = 0; attempt < 2; attempt += 1) {
3209
3438
  try {
3210
- await writeFile3(lockPath, JSON.stringify(record, null, 2), { flag: "wx" });
3439
+ await writeFile4(lockPath, JSON.stringify(record, null, 2), { flag: "wx" });
3211
3440
  return {
3212
3441
  path: lockPath,
3213
3442
  operation,
@@ -3239,11 +3468,15 @@ async function acquireProtocolLock(projectRoot, operation, options = {}) {
3239
3468
  var PushWorkflowError = class extends Error {
3240
3469
  exitCode;
3241
3470
  code;
3242
- constructor(message, exitCode, code) {
3471
+ details;
3472
+ constructor(message, exitCode, code, details) {
3243
3473
  super(message);
3244
3474
  this.name = "PushWorkflowError";
3245
3475
  this.exitCode = exitCode;
3246
3476
  this.code = code;
3477
+ if (details !== void 0) {
3478
+ this.details = details;
3479
+ }
3247
3480
  }
3248
3481
  };
3249
3482
  var SHARED_MANAGED_ROOTS = [
@@ -3271,7 +3504,7 @@ async function walkFiles(root, current, output) {
3271
3504
  return;
3272
3505
  }
3273
3506
  for (const item2 of await readdir4(current, { withFileTypes: true })) {
3274
- const path = join9(current, item2.name);
3507
+ const path = join10(current, item2.name);
3275
3508
  if (item2.isSymbolicLink()) {
3276
3509
  throw new PushWorkflowError("symlink is not pushable", 6, "UNSAFE_SYMLINK");
3277
3510
  }
@@ -3293,7 +3526,7 @@ async function walkHarnessEntries(root, directory, output) {
3293
3526
  return;
3294
3527
  for (const item2 of await readdir4(directory, { withFileTypes: true })) {
3295
3528
  if (item2.name.startsWith("harness-")) {
3296
- const path = join9(directory, item2.name);
3529
+ const path = join10(directory, item2.name);
3297
3530
  if (item2.isDirectory()) {
3298
3531
  await walkFiles(root, path, output);
3299
3532
  } else if (item2.isFile()) {
@@ -3312,25 +3545,25 @@ async function managedFiles(projectRoot, project) {
3312
3545
  ...adapters.some((adapter) => adapter.name === "codebuddy") ? ["CODEBUDDY.md"] : []
3313
3546
  ];
3314
3547
  for (const path of managedFiles2) {
3315
- if (await exists3(join9(root, path))) {
3548
+ if (await exists3(join10(root, path))) {
3316
3549
  paths.push(path);
3317
3550
  }
3318
3551
  }
3319
3552
  for (const path of SHARED_MANAGED_ROOTS) {
3320
- await walkFiles(root, join9(root, path), paths);
3553
+ await walkFiles(root, join10(root, path), paths);
3321
3554
  }
3322
3555
  for (const adapter of adapters) {
3323
3556
  if (adapter.rulesRoot !== null) {
3324
- await walkFiles(root, join9(root, adapter.rulesRoot), paths);
3557
+ await walkFiles(root, join10(root, adapter.rulesRoot), paths);
3325
3558
  }
3326
- await walkHarnessEntries(root, join9(root, adapter.skillsRoot), paths);
3559
+ await walkHarnessEntries(root, join10(root, adapter.skillsRoot), paths);
3327
3560
  if (adapter.agentsRoot !== null) {
3328
- await walkHarnessEntries(root, join9(root, adapter.agentsRoot), paths);
3561
+ await walkHarnessEntries(root, join10(root, adapter.agentsRoot), paths);
3329
3562
  }
3330
3563
  }
3331
3564
  const result = {};
3332
3565
  for (const path of [...new Set(paths)].sort()) {
3333
- result[path] = await readFile8(join9(root, path), "utf8");
3566
+ result[path] = await readFile9(join10(root, path), "utf8");
3334
3567
  }
3335
3568
  return result;
3336
3569
  }
@@ -3339,14 +3572,14 @@ function proposalBaseline(baseline) {
3339
3572
  }
3340
3573
  async function readProject(root) {
3341
3574
  try {
3342
- return projectConfigSchema.parse(parseYaml4(await readFile8(join9(root, ".harness", "project.yaml"), "utf8")));
3575
+ return projectConfigSchema.parse(parseYaml5(await readFile9(join10(root, ".harness", "project.yaml"), "utf8")));
3343
3576
  } catch {
3344
3577
  throw new PushWorkflowError("project configuration is missing or invalid", 3, "PROJECT_CONFIG_INVALID");
3345
3578
  }
3346
3579
  }
3347
3580
  async function readOptionalJson(path) {
3348
3581
  try {
3349
- return JSON.parse(await readFile8(path, "utf8"));
3582
+ return JSON.parse(await readFile9(path, "utf8"));
3350
3583
  } catch (error) {
3351
3584
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
3352
3585
  return null;
@@ -3358,7 +3591,7 @@ async function clientIdFor(root, explicit) {
3358
3591
  if (explicit !== void 0) {
3359
3592
  return explicit;
3360
3593
  }
3361
- const path = join9(root, ".harness", "state", "local", "client.json");
3594
+ const path = join10(root, ".harness", "state", "local", "client.json");
3362
3595
  const existing = await readOptionalJson(path);
3363
3596
  if (typeof existing?.client_id === "string" && /^cli_[A-Za-z0-9_-]+$/.test(existing.client_id)) {
3364
3597
  return existing.client_id;
@@ -3403,6 +3636,50 @@ function resetSession(state, proposalManifestHash) {
3403
3636
  chunk_idempotency_keys: {}
3404
3637
  };
3405
3638
  }
3639
+ function summarizeFindings(security) {
3640
+ const blocked = security.findings.filter((finding) => finding.disposition === "blocked");
3641
+ return {
3642
+ findings: blocked.map((finding) => ({
3643
+ path: finding.path,
3644
+ rule_id: finding.rule_id,
3645
+ severity: finding.severity,
3646
+ overridable: finding.overridable,
3647
+ fingerprint: finding.fingerprint,
3648
+ line: finding.line,
3649
+ column: finding.column
3650
+ })),
3651
+ finding_count: blocked.length,
3652
+ scanner_version: security.scanner_version
3653
+ };
3654
+ }
3655
+ function sensitiveBlockedError(preview) {
3656
+ return new PushWorkflowError("sensitive information scan blocked the proposal", 6, "SENSITIVE_CONTENT_BLOCKED", summarizeFindings(preview.security));
3657
+ }
3658
+ async function resolveSensitiveScanSkip(preview, options) {
3659
+ if (!preview.blocked) {
3660
+ return { skip: false };
3661
+ }
3662
+ if (options.sensitiveScanSkip === true) {
3663
+ return {
3664
+ skip: true,
3665
+ ...options.sensitiveScanSkipReason === void 0 ? {} : { reason: options.sensitiveScanSkipReason }
3666
+ };
3667
+ }
3668
+ if (options.confirmSensitiveScanSkip !== void 0) {
3669
+ const answer = await options.confirmSensitiveScanSkip(preview);
3670
+ if (answer === "cancelled") {
3671
+ return { skip: false, cancelled: true };
3672
+ }
3673
+ return answer.skip ? { skip: true, ...answer.reason === void 0 ? {} : { reason: answer.reason } } : { skip: false };
3674
+ }
3675
+ throw sensitiveBlockedError(preview);
3676
+ }
3677
+ function assertPreviewAllowed(preview, skip) {
3678
+ if (preview.blocked && !skip) {
3679
+ throw sensitiveBlockedError(preview);
3680
+ }
3681
+ }
3682
+ var CREDENTIALS_HINT = "\u53EF\u5728\u4EA4\u4E92\u6A21\u5F0F\u4E0B\u5F55\u5165\uFF0C\u6216\u5199\u5165 .harness/credentials.local.yaml\uFF08\u52FF\u63D0\u4EA4 git\uFF09";
3406
3683
  function makePreview(baseline, files, confirmedProjectLocal, ignorePaths, deletedAt = (/* @__PURE__ */ new Date()).toISOString()) {
3407
3684
  const filteredFiles = {};
3408
3685
  for (const [path, content] of Object.entries(files)) {
@@ -3430,7 +3707,7 @@ async function bindProject(root, project, baseline, projectId) {
3430
3707
  {
3431
3708
  operation: "modify",
3432
3709
  path: ".harness/project.yaml",
3433
- content: stringifyYaml3(nextProject, { sortMapEntries: true })
3710
+ content: stringifyYaml4(nextProject, { sortMapEntries: true })
3434
3711
  },
3435
3712
  {
3436
3713
  operation: "modify",
@@ -3447,24 +3724,38 @@ async function pushProject(options) {
3447
3724
  const profile = parseHarnessProfile(project.project.profiles[0]);
3448
3725
  const installedPaths = profile === null ? /* @__PURE__ */ new Set() : new Set(await Promise.all(enabledHarnessAgents(project).map((agent) => managedBundleTargets(options.resourcesRoot, profile, agent))).then((targets) => targets.flatMap((target) => [...target])));
3449
3726
  let preview = makePreview(baseline, await managedFiles(root, project), options.confirmedProjectLocal ?? [], installedPaths);
3450
- if (preview.blocked) {
3451
- throw new PushWorkflowError("sensitive information scan blocked the proposal", 6, "SENSITIVE_CONTENT_BLOCKED");
3727
+ const initialSkip = await resolveSensitiveScanSkip(preview, options);
3728
+ if (initialSkip.cancelled === true) {
3729
+ return { preview, proposalId: null, projectId: project.project.project_id, cancelled: true };
3452
3730
  }
3731
+ let sensitiveScanSkip = initialSkip.skip;
3732
+ let sensitiveScanSkipReason = initialSkip.reason;
3733
+ assertPreviewAllowed(preview, sensitiveScanSkip);
3453
3734
  if (options.dryRun) {
3454
3735
  return { preview, proposalId: null, projectId: project.project.project_id };
3455
3736
  }
3456
3737
  if (options.confirmProposal !== void 0 && !await options.confirmProposal(preview)) {
3457
3738
  return { preview, proposalId: null, projectId: project.project.project_id, cancelled: true };
3458
3739
  }
3459
- const serverUrl = options.serverUrl ?? project.server.url;
3460
- if (serverUrl === null || serverUrl === void 0) {
3461
- throw new PushWorkflowError("server_url is required", 3, "SERVER_URL_REQUIRED");
3740
+ const localCredentials = await readLocalCredentials(root);
3741
+ const auth = resolvePushAuth({
3742
+ ...options.serverUrl === void 0 ? {} : { serverUrlFlag: options.serverUrl },
3743
+ ...options.tokenEnv === void 0 ? {} : { tokenEnv: options.tokenEnv },
3744
+ env: options.env,
3745
+ local: localCredentials,
3746
+ projectUrl: project.server.url,
3747
+ projectTokenEnv: project.server.token_env
3748
+ });
3749
+ if ("code" in auth) {
3750
+ if (auth.code === "SERVER_URL_REQUIRED") {
3751
+ throw new PushWorkflowError("server_url is required; use --server-url, project.yaml server.url, or " + CREDENTIALS_HINT, 3, "SERVER_URL_REQUIRED");
3752
+ }
3753
+ const tokenEnv2 = options.tokenEnv ?? project.server.token_env;
3754
+ throw new PushWorkflowError("API token is unset; set " + tokenEnv2 + " or " + CREDENTIALS_HINT, 8, "TOKEN_INVALID");
3462
3755
  }
3756
+ const serverUrl = auth.serverUrl;
3757
+ const token = auth.token;
3463
3758
  const tokenEnv = options.tokenEnv ?? project.server.token_env;
3464
- const token = options.env[tokenEnv];
3465
- if (token === void 0 || token.trim() === "") {
3466
- throw new PushWorkflowError("API token environment variable is unset", 8, "TOKEN_INVALID");
3467
- }
3468
3759
  if (!/^[A-Z_][A-Z0-9_]*$/.test(tokenEnv)) {
3469
3760
  throw new PushWorkflowError("token_env is invalid", 3, "TOKEN_ENV_INVALID");
3470
3761
  }
@@ -3477,7 +3768,7 @@ async function pushProject(options) {
3477
3768
  if (parsedServerUrl.protocol !== "https:") {
3478
3769
  throw new PushWorkflowError("server_url must use HTTPS", 3, "SERVER_URL_INVALID");
3479
3770
  }
3480
- const workflowPath = join9(root, ".harness", "state", "local", "push-workflow.json");
3771
+ const workflowPath = join10(root, ".harness", "state", "local", "push-workflow.json");
3481
3772
  const priorWorkflow = await readOptionalJson(workflowPath);
3482
3773
  const provisionalRequestId = priorWorkflow?.local_project_key === project.project.local_project_key ? priorWorkflow.request_id : uuidV7();
3483
3774
  const lock = await acquireProtocolLock(root, "push", { requestId: provisionalRequestId });
@@ -3505,9 +3796,15 @@ async function pushProject(options) {
3505
3796
  workflow.project_id = resolved.project_id;
3506
3797
  await atomicWriteJson(workflowPath, workflow);
3507
3798
  preview = makePreview(baseline, await managedFiles(root, project), options.confirmedProjectLocal ?? [], installedPaths, workflow.created_at);
3508
- if (preview.blocked) {
3509
- throw new PushWorkflowError("sensitive information scan blocked the proposal", 6, "SENSITIVE_CONTENT_BLOCKED");
3799
+ if (!sensitiveScanSkip) {
3800
+ const reboundSkip = await resolveSensitiveScanSkip(preview, options);
3801
+ if (reboundSkip.cancelled === true) {
3802
+ return { preview, proposalId: null, projectId: project.project.project_id, cancelled: true };
3803
+ }
3804
+ sensitiveScanSkip = reboundSkip.skip;
3805
+ sensitiveScanSkipReason = reboundSkip.reason;
3510
3806
  }
3807
+ assertPreviewAllowed(preview, sensitiveScanSkip);
3511
3808
  }
3512
3809
  const projectId = project.project.project_id;
3513
3810
  if (projectId === null) {
@@ -3570,9 +3867,13 @@ async function pushProject(options) {
3570
3867
  const finalized = await client.finalizeProposal(session.session_id, {
3571
3868
  schema_version: 1,
3572
3869
  manifest_sha256: proposalManifestHash,
3573
- base_artifact_id: baseline.latest_artifact_id ?? null
3870
+ base_artifact_id: baseline.latest_artifact_id ?? null,
3871
+ ...sensitiveScanSkip ? {
3872
+ sensitive_scan_skip: true,
3873
+ ...sensitiveScanSkipReason === void 0 ? {} : { sensitive_scan_skip_reason: sensitiveScanSkipReason }
3874
+ } : {}
3574
3875
  }, requestId, workflow.finalize_idempotency_key);
3575
- await atomicWriteJson(join9(root, ".harness", "state", "local", "push-results", finalized.proposal_id + ".json"), {
3876
+ await atomicWriteJson(join10(root, ".harness", "state", "local", "push-results", finalized.proposal_id + ".json"), {
3576
3877
  schema_version: 1,
3577
3878
  request_id: requestId,
3578
3879
  project_id: projectId,
@@ -3592,6 +3893,14 @@ async function pushProject(options) {
3592
3893
  if (error.code === "STALE_PUSH") {
3593
3894
  throw new PushWorkflowError("\u670D\u52A1\u7AEF\u5DF2\u6709\u66F4\u65B0\u7684\u63A8\u9001\uFF0C\u8BF7\u5148 git pull \u540E\u6267\u884C npx hunter-harness update \u518D\u63A8", 5, "STALE_PUSH");
3594
3895
  }
3896
+ if (error.code === "SENSITIVE_CONTENT_BLOCKED") {
3897
+ const details = error.details;
3898
+ throw new PushWorkflowError(error.message, 6, "SENSITIVE_CONTENT_BLOCKED", details === null || typeof details !== "object" ? void 0 : {
3899
+ ...typeof details.finding_count === "number" ? { finding_count: details.finding_count } : {},
3900
+ ...typeof details.scanner_version === "string" ? { scanner_version: details.scanner_version } : {},
3901
+ ...Array.isArray(details.findings) ? { findings: details.findings } : {}
3902
+ });
3903
+ }
3595
3904
  const exitCode = error.status === 401 ? 8 : error.status === 409 ? 5 : 4;
3596
3905
  throw new PushWorkflowError(error.message, exitCode, error.code);
3597
3906
  }
@@ -3602,8 +3911,8 @@ async function pushProject(options) {
3602
3911
  }
3603
3912
 
3604
3913
  // ../core/dist/state/cleanup.js
3605
- import { readFile as readFile9, readdir as readdir5, rm as rm5 } from "node:fs/promises";
3606
- import { join as join10 } from "node:path";
3914
+ import { readFile as readFile10, readdir as readdir5, rm as rm5 } from "node:fs/promises";
3915
+ import { join as join11 } from "node:path";
3607
3916
  function isSafeEntryName(name) {
3608
3917
  return name.length > 0 && !name.includes("/") && !name.includes("\\") && name !== "." && name !== ".." && !name.includes("\0");
3609
3918
  }
@@ -3627,7 +3936,7 @@ async function cleanupProject(options) {
3627
3936
  continue;
3628
3937
  let journal;
3629
3938
  try {
3630
- journal = JSON.parse(await readFile9(join10(layout.transactions, name, "journal.json"), "utf8"));
3939
+ journal = JSON.parse(await readFile10(join11(layout.transactions, name, "journal.json"), "utf8"));
3631
3940
  } catch {
3632
3941
  continue;
3633
3942
  }
@@ -3645,7 +3954,7 @@ async function cleanupProject(options) {
3645
3954
  continue;
3646
3955
  pruned.push(entry.id);
3647
3956
  if (!options.dryRun) {
3648
- await rm5(join10(layout.transactions, entry.id), { recursive: true, force: true });
3957
+ await rm5(join11(layout.transactions, entry.id), { recursive: true, force: true });
3649
3958
  }
3650
3959
  }
3651
3960
  }
@@ -3654,7 +3963,7 @@ async function cleanupProject(options) {
3654
3963
  continue;
3655
3964
  removedCache.push(name);
3656
3965
  if (!options.dryRun) {
3657
- await rm5(join10(layout.serverArtifacts, name), { recursive: true, force: true });
3966
+ await rm5(join11(layout.serverArtifacts, name), { recursive: true, force: true });
3658
3967
  }
3659
3968
  }
3660
3969
  return {
@@ -3665,11 +3974,11 @@ async function cleanupProject(options) {
3665
3974
  }
3666
3975
 
3667
3976
  // ../core/dist/skill/frontmatter.js
3668
- import { parse as parseYaml5 } from "yaml";
3977
+ import { parse as parseYaml6 } from "yaml";
3669
3978
  import { z as z14 } from "zod";
3670
3979
 
3671
3980
  // ../core/dist/skill/fixer.js
3672
- import { parse as parseYaml6, stringify as stringifyYaml4 } from "yaml";
3981
+ import { parse as parseYaml7, stringify as stringifyYaml5 } from "yaml";
3673
3982
 
3674
3983
  // ../core/dist/skill/agents.js
3675
3984
  var AGENT_DESCRIPTORS = {
@@ -3708,10 +4017,10 @@ var AGENT_DESCRIPTORS = {
3708
4017
  var INSTALLABLE_AGENTS = Object.keys(AGENT_DESCRIPTORS).filter((agent) => AGENT_DESCRIPTORS[agent]?.installable === true);
3709
4018
 
3710
4019
  // ../core/dist/transaction/recovery.js
3711
- import { readFile as readFile10, readdir as readdir6, rm as rm6, stat as stat3 } from "node:fs/promises";
3712
- import { join as join11 } from "node:path";
4020
+ import { readFile as readFile11, readdir as readdir6, rm as rm6, stat as stat3 } from "node:fs/promises";
4021
+ import { join as join12 } from "node:path";
3713
4022
  async function recoverTransaction(projectRoot, transactionId) {
3714
- const journal = JSON.parse(await readFile10(join11(stateLayout(projectRoot).transactions, transactionId, "journal.json"), "utf8"));
4023
+ const journal = JSON.parse(await readFile11(join12(stateLayout(projectRoot).transactions, transactionId, "journal.json"), "utf8"));
3715
4024
  if (journal.state === "committed") {
3716
4025
  return { transactionId, status: "committed" };
3717
4026
  }
@@ -3738,7 +4047,7 @@ async function listTransactions(projectRoot) {
3738
4047
  const transactions = [];
3739
4048
  for (const transactionId of names) {
3740
4049
  try {
3741
- const journal = JSON.parse(await readFile10(join11(root, transactionId, "journal.json"), "utf8"));
4050
+ const journal = JSON.parse(await readFile11(join12(root, transactionId, "journal.json"), "utf8"));
3742
4051
  transactions.push({
3743
4052
  transactionId,
3744
4053
  kind: journal.kind,
@@ -3769,11 +4078,11 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
3769
4078
  if (latest === void 0) {
3770
4079
  throw new Error("no committed update transaction is available for rollback");
3771
4080
  }
3772
- const transactionRoot = join11(stateLayout(projectRoot).transactions, latest.transactionId);
3773
- const journal = JSON.parse(await readFile10(join11(transactionRoot, "journal.json"), "utf8"));
3774
- const after = JSON.parse(await readFile10(join11(transactionRoot, "after", "manifest.json"), "utf8"));
4081
+ const transactionRoot = join12(stateLayout(projectRoot).transactions, latest.transactionId);
4082
+ const journal = JSON.parse(await readFile11(join12(transactionRoot, "journal.json"), "utf8"));
4083
+ const after = JSON.parse(await readFile11(join12(transactionRoot, "after", "manifest.json"), "utf8"));
3775
4084
  for (const entry of after) {
3776
- const target = join11(projectRoot, entry.path);
4085
+ const target = join12(projectRoot, entry.path);
3777
4086
  const exists5 = await pathExists(target);
3778
4087
  if (exists5 !== entry.exists || exists5 && await sha256File(target) !== entry.hash) {
3779
4088
  throw new Error("cannot rollback dirty path: " + entry.path);
@@ -3786,10 +4095,10 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
3786
4095
  continue;
3787
4096
  }
3788
4097
  seen.add(snapshot.path);
3789
- const target = join11(projectRoot, snapshot.path);
4098
+ const target = join12(projectRoot, snapshot.path);
3790
4099
  const exists5 = await pathExists(target);
3791
4100
  if (snapshot.existed && snapshot.snapshot_name !== null) {
3792
- const content = await readFile10(join11(transactionRoot, "before", snapshot.snapshot_name));
4101
+ const content = await readFile11(join12(transactionRoot, "before", snapshot.snapshot_name));
3793
4102
  operations.push({
3794
4103
  operation: exists5 ? "modify" : "add",
3795
4104
  path: snapshot.path,
@@ -3818,7 +4127,7 @@ async function cleanupOldTransactions(projectRoot, now = /* @__PURE__ */ new Dat
3818
4127
  if (!removable) {
3819
4128
  continue;
3820
4129
  }
3821
- await rm6(join11(stateLayout(projectRoot).transactions, item2.transactionId), {
4130
+ await rm6(join12(stateLayout(projectRoot).transactions, item2.transactionId), {
3822
4131
  recursive: true,
3823
4132
  force: true
3824
4133
  });
@@ -3850,9 +4159,9 @@ function managedBlockDirty(currentContent, managedBlockHash) {
3850
4159
  }
3851
4160
 
3852
4161
  // ../core/dist/update/update.js
3853
- import { lstat as lstat3, readFile as readFile11, rm as rm7 } from "node:fs/promises";
3854
- import { join as join12, resolve as resolve6 } from "node:path";
3855
- import { parse as parseYaml7 } from "yaml";
4162
+ import { lstat as lstat3, readFile as readFile12, rm as rm7 } from "node:fs/promises";
4163
+ import { join as join13, resolve as resolve6 } from "node:path";
4164
+ import { parse as parseYaml8 } from "yaml";
3856
4165
  var UpdateWorkflowError = class extends Error {
3857
4166
  exitCode;
3858
4167
  code;
@@ -3875,7 +4184,7 @@ async function pathExists2(path) {
3875
4184
  }
3876
4185
  }
3877
4186
  async function optionalContent(path) {
3878
- return await pathExists2(path) ? readFile11(path, "utf8") : null;
4187
+ return await pathExists2(path) ? readFile12(path, "utf8") : null;
3879
4188
  }
3880
4189
  function manifestPayloadHash(manifest) {
3881
4190
  const payload = { ...manifest };
@@ -3893,10 +4202,10 @@ async function loadBlob(root, client, artifactId, operation, requestId, dryRun)
3893
4202
  return null;
3894
4203
  }
3895
4204
  const hash = operation.content_sha256;
3896
- const cacheRoot = join12(root, ".harness", "cache", "server-artifacts", artifactId);
3897
- const cachePath = join12(cacheRoot, "blobs", hash.replace(":", "_"));
4205
+ const cacheRoot = join13(root, ".harness", "cache", "server-artifacts", artifactId);
4206
+ const cachePath = join13(cacheRoot, "blobs", hash.replace(":", "_"));
3898
4207
  if (await pathExists2(cachePath) && await sha256File(cachePath) === hash) {
3899
- return readFile11(cachePath, "utf8");
4208
+ return readFile12(cachePath, "utf8");
3900
4209
  }
3901
4210
  const bytes = await client.downloadArtifactBlob(artifactId, hash, requestId);
3902
4211
  if (bytes.byteLength !== operation.size_bytes || sha256Bytes(bytes) !== hash) {
@@ -3946,7 +4255,7 @@ async function updateProject(options) {
3946
4255
  const root = resolve6(options.projectRoot);
3947
4256
  let project;
3948
4257
  try {
3949
- project = projectConfigSchema.parse(parseYaml7(await readFile11(join12(root, ".harness", "project.yaml"), "utf8")));
4258
+ project = projectConfigSchema.parse(parseYaml8(await readFile12(join13(root, ".harness", "project.yaml"), "utf8")));
3950
4259
  } catch {
3951
4260
  throw new UpdateWorkflowError("project configuration is invalid", 3, "PROJECT_CONFIG_INVALID");
3952
4261
  }
@@ -4031,8 +4340,8 @@ async function updateProject(options) {
4031
4340
  skipped.push({ path: target, operation: operation.operation, reason: "baseline-diverged" });
4032
4341
  continue;
4033
4342
  }
4034
- const sourceContent = await optionalContent(join12(root, source));
4035
- const targetContent = target === source ? sourceContent : await optionalContent(join12(root, target));
4343
+ const sourceContent = await optionalContent(join13(root, source));
4344
+ const targetContent = target === source ? sourceContent : await optionalContent(join13(root, target));
4036
4345
  const incoming = blobs.get(operation) ?? null;
4037
4346
  const incomingHash = contentHash(operation);
4038
4347
  let equivalent = incomingHash !== null && targetContent !== null && sha256Bytes(targetContent) === incomingHash;
@@ -4092,7 +4401,7 @@ async function updateProject(options) {
4092
4401
  dryRun: true
4093
4402
  };
4094
4403
  }
4095
- await atomicWriteJson(join12(root, ".harness", "cache", "server-artifacts", manifest.artifact_id, "manifest.json"), manifest);
4404
+ await atomicWriteJson(join13(root, ".harness", "cache", "server-artifacts", manifest.artifact_id, "manifest.json"), manifest);
4096
4405
  const lock = await acquireProtocolLock(root, "update", { requestId });
4097
4406
  try {
4098
4407
  const nextBaseline = baselineManifestSchema.parse(structuredClone(baseline));
@@ -4174,8 +4483,8 @@ async function updateProject(options) {
4174
4483
  }
4175
4484
 
4176
4485
  // src/config/init-config.ts
4177
- import { isAbsolute as isAbsolute2, join as join13 } from "node:path";
4178
- import { readFile as readFile12 } from "node:fs/promises";
4486
+ import { isAbsolute as isAbsolute2, join as join14 } from "node:path";
4487
+ import { readFile as readFile13 } from "node:fs/promises";
4179
4488
  var InitConfigurationError = class extends Error {
4180
4489
  exitCode;
4181
4490
  code;
@@ -4256,9 +4565,9 @@ function hasOwn(record, key) {
4256
4565
  async function resolveInitConfig(cwd, flags, prompts = {}, warnings = []) {
4257
4566
  let fileConfig = {};
4258
4567
  if (flags.config !== void 0) {
4259
- const path = isAbsolute2(flags.config) ? flags.config : join13(cwd, flags.config);
4568
+ const path = isAbsolute2(flags.config) ? flags.config : join14(cwd, flags.config);
4260
4569
  try {
4261
- fileConfig = JSON.parse(await readFile12(path, "utf8"));
4570
+ fileConfig = JSON.parse(await readFile13(path, "utf8"));
4262
4571
  } catch (error) {
4263
4572
  throw new InitConfigurationError(
4264
4573
  "unable to read init config: " + (error instanceof Error ? error.message : String(error)),
@@ -4340,28 +4649,28 @@ function serializeCliResult(result) {
4340
4649
  }
4341
4650
 
4342
4651
  // src/commands/refresh.ts
4343
- import { readFile as readFile13 } from "node:fs/promises";
4344
- import { join as join14 } from "node:path";
4345
- import { parse as parseYaml8 } from "yaml";
4652
+ import { readFile as readFile14 } from "node:fs/promises";
4653
+ import { join as join15 } from "node:path";
4654
+ import { parse as parseYaml9 } from "yaml";
4346
4655
  async function detectProject(root) {
4347
4656
  let content;
4348
4657
  try {
4349
- content = await readFile13(join14(root, ".harness", "project.yaml"), "utf8");
4658
+ content = await readFile14(join15(root, ".harness", "project.yaml"), "utf8");
4350
4659
  } catch (error) {
4351
4660
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
4352
4661
  return { status: "absent" };
4353
4662
  }
4354
4663
  throw error;
4355
4664
  }
4356
- const parsed = projectConfigSchema.safeParse(parseYaml8(content));
4665
+ const parsed = projectConfigSchema.safeParse(parseYaml9(content));
4357
4666
  if (!parsed.success) {
4358
4667
  return { status: "invalid" };
4359
4668
  }
4360
4669
  return { status: "valid", config: parsed.data };
4361
4670
  }
4362
- function parseProfile(value, current) {
4671
+ function parseProfile(value) {
4363
4672
  if (value === void 0 || value === "") {
4364
- return current;
4673
+ return void 0;
4365
4674
  }
4366
4675
  if (value === "1" || value === "general") return "general";
4367
4676
  if (value === "2" || value === "java") return "java";
@@ -4446,7 +4755,7 @@ async function runRefresh(options, dependencies) {
4446
4755
  let targetProfile;
4447
4756
  let targetAgents;
4448
4757
  try {
4449
- targetProfile = parseProfile(options.profile, currentProfile);
4758
+ targetProfile = parseProfile(options.profile);
4450
4759
  targetAgents = options.agents === void 0 ? refreshAgents(detection.config) : parseAgentsInput(options.agents);
4451
4760
  } catch (error) {
4452
4761
  const message = error instanceof Error ? error.message : String(error);
@@ -4455,12 +4764,12 @@ async function runRefresh(options, dependencies) {
4455
4764
  return error instanceof InitConfigurationError ? error.exitCode : 3;
4456
4765
  }
4457
4766
  const dryRun = options.dryRun === true;
4458
- 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) {
4459
4768
  try {
4460
4769
  const preview = await refreshProject({
4461
4770
  projectRoot: dependencies.cwd,
4462
4771
  resourcesRoot: dependencies.resourcesRoot,
4463
- profile: targetProfile,
4772
+ ...targetProfile === void 0 ? {} : { profile: targetProfile },
4464
4773
  agents: targetAgents,
4465
4774
  codebuddySurface: codebuddySurface(detection.config),
4466
4775
  dryRun: true,
@@ -4485,7 +4794,7 @@ async function runRefresh(options, dependencies) {
4485
4794
  return 2;
4486
4795
  }
4487
4796
  } else if (!options.yes && !dryRun) {
4488
- 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}`;
4489
4798
  const answer = await dependencies.prompt(`${label}\uFF1F[y/N]\uFF1A`);
4490
4799
  if (!/^(?:y|yes)$/i.test(answer.trim())) {
4491
4800
  return 2;
@@ -4496,7 +4805,7 @@ async function runRefresh(options, dependencies) {
4496
4805
  const result = await refreshProject({
4497
4806
  projectRoot: dependencies.cwd,
4498
4807
  resourcesRoot: dependencies.resourcesRoot,
4499
- profile: targetProfile,
4808
+ ...targetProfile === void 0 ? {} : { profile: targetProfile },
4500
4809
  agents: targetAgents,
4501
4810
  codebuddySurface: codebuddySurface(detection.config),
4502
4811
  dryRun,
@@ -4511,7 +4820,7 @@ async function runRefresh(options, dependencies) {
4511
4820
  if (result.removed.length > 0) parts.push(`\u5DF2\u5220\u9664 ${result.removed.length} \u4E2A`);
4512
4821
  if (result.preserved.length > 0) parts.push(`\u5DF2\u4FDD\u7559 ${result.preserved.length} \u4E2A`);
4513
4822
  if (result.unchanged.length > 0) parts.push(`\u65E0\u9700\u53D8\u66F4 ${result.unchanged.length} \u4E2A`);
4514
- 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
4515
4824
  `);
4516
4825
  }
4517
4826
  return output.exit_code;
@@ -4541,9 +4850,12 @@ async function runRefresh(options, dependencies) {
4541
4850
  }
4542
4851
 
4543
4852
  // src/commands/configure.ts
4544
- function otherProfile(current) {
4545
- return current === "general" ? "java" : "general";
4546
- }
4853
+ var AGENT_LABELS = {
4854
+ "claude-code": "Claude Code",
4855
+ codex: "Codex",
4856
+ cursor: "Cursor",
4857
+ codebuddy: "CodeBuddy"
4858
+ };
4547
4859
  async function runFirstInstall(options, dependencies) {
4548
4860
  const requestId = uuidV7();
4549
4861
  try {
@@ -4625,21 +4937,40 @@ async function runExistingProject(options, dependencies, currentProfile) {
4625
4937
  if (options.nonInteractive === true) {
4626
4938
  return runRefresh(refreshOptions, dependencies);
4627
4939
  }
4628
- const menu = await dependencies.prompt(
4629
- `Hunter Harness \u5DF2\u521D\u59CB\u5316\uFF08profile: ${currentProfile}\uFF09\u3002
4630
- 1. \u5237\u65B0\u5F53\u524D\u914D\u7F6E\uFF08\u9ED8\u8BA4\u4E14\u63A8\u8350\uFF09
4631
- 2. \u5207\u6362\u5230\u53E6\u4E00\u79CD\u914D\u7F6E
4632
- 3. \u53D6\u6D88
4633
- \u8BF7\u9009\u62E9 [1]: `
4634
- );
4635
- const choice = menu.trim();
4636
- if (choice === "3" || /^c/i.test(choice)) {
4637
- return 2;
4638
- }
4639
- if (choice === "2" || /^s/i.test(choice)) {
4640
- refreshOptions.profile = otherProfile(currentProfile);
4641
- } else {
4642
- 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();
4643
4974
  }
4644
4975
  refreshOptions.confirmed = true;
4645
4976
  return runRefresh(refreshOptions, dependencies);
@@ -4733,82 +5064,192 @@ async function runCleanup(options, dependencies) {
4733
5064
  }
4734
5065
 
4735
5066
  // src/commands/push.ts
5067
+ function formatFindings(details) {
5068
+ if (details?.findings === void 0 || details.findings.length === 0) {
5069
+ return "";
5070
+ }
5071
+ const lines = [
5072
+ "\u654F\u611F\u4FE1\u606F\u626B\u63CF\u53D1\u73B0 " + details.findings.length + " \u4E2A\u95EE\u9898\uFF1A"
5073
+ ];
5074
+ for (const finding of details.findings) {
5075
+ lines.push(
5076
+ " - " + finding.path + ":" + finding.line + " " + finding.rule_id + " (" + finding.severity + (finding.overridable ? ", \u53EF\u8986\u76D6" : ", \u4E0D\u53EF\u8986\u76D6") + ")"
5077
+ );
5078
+ }
5079
+ return lines.join("\n") + "\n";
5080
+ }
5081
+ function errorPayload(error) {
5082
+ const exitCode = error instanceof PushWorkflowError ? error.exitCode : 1;
5083
+ const message = error instanceof Error ? error.message : String(error);
5084
+ const code = error instanceof PushWorkflowError ? error.code : "GENERAL_FAILURE";
5085
+ if (error instanceof PushWorkflowError && error.details !== void 0) {
5086
+ return { exitCode, message, code, details: error.details };
5087
+ }
5088
+ return { exitCode, message, code };
5089
+ }
5090
+ async function promptForCredentials(dependencies, missing) {
5091
+ const serverUrl = missing === "token" ? void 0 : (await dependencies.prompt("\u670D\u52A1\u7AEF URL (https://...): ")).trim();
5092
+ const token = missing === "url" ? void 0 : (await dependencies.prompt("API Token: ")).trim();
5093
+ if ((missing === "url" || missing === "both") && (serverUrl === void 0 || serverUrl === "")) {
5094
+ return null;
5095
+ }
5096
+ if ((missing === "token" || missing === "both") && (token === void 0 || token === "")) {
5097
+ return null;
5098
+ }
5099
+ if (serverUrl !== void 0 && serverUrl !== "") {
5100
+ try {
5101
+ assertHttpsServerUrl(serverUrl);
5102
+ } catch (error) {
5103
+ if (error instanceof InvalidCredentialsError) {
5104
+ dependencies.stderr(error.message + "\n");
5105
+ return null;
5106
+ }
5107
+ throw error;
5108
+ }
5109
+ }
5110
+ return {
5111
+ ...serverUrl === void 0 ? {} : { serverUrl },
5112
+ ...token === void 0 ? {} : { token }
5113
+ };
5114
+ }
4736
5115
  async function runPush(options, dependencies) {
4737
5116
  const requestId = uuidV7();
4738
5117
  if (options.nonInteractive === true && options.yes !== true && options.dryRun !== true) {
4739
5118
  dependencies.stderr("\u975E\u4EA4\u4E92\u6A21\u5F0F\u63A8\u9001\u9700\u8981 --yes\n");
4740
5119
  return 2;
4741
5120
  }
4742
- try {
4743
- const result = await pushProject({
4744
- projectRoot: dependencies.cwd,
4745
- resourcesRoot: dependencies.resourcesRoot,
4746
- ...options.serverUrl === void 0 ? {} : { serverUrl: options.serverUrl },
4747
- ...options.tokenEnv === void 0 ? {} : { tokenEnv: options.tokenEnv },
4748
- env: dependencies.env,
4749
- dryRun: options.dryRun === true,
4750
- fetch: dependencies.fetch,
4751
- ...options.yes === true || options.nonInteractive === true ? {} : { confirmProposal: async () => {
4752
- const answer = await dependencies.prompt("Create this proposal? [y/N]: ");
4753
- return /^(?:y|yes)$/i.test(answer.trim());
4754
- } }
4755
- });
4756
- if ("cancelled" in result && result.cancelled === true) {
4757
- return 2;
4758
- }
4759
- const items = result.preview.operations.map((operation) => ({
4760
- path: operation.operation === "rename" ? operation.to_path : operation.path,
4761
- operation: operation.operation,
4762
- file_kind: operation.file_kind,
4763
- status: options.dryRun === true ? "planned" : "submitted",
4764
- reason: null,
4765
- size_bytes: "size_bytes" in operation ? operation.size_bytes : 0,
4766
- content_sha256: "content_sha256" in operation ? operation.content_sha256 : operation.tombstone.previous_sha256
4767
- }));
4768
- const output = {
4769
- schema_version: 1,
4770
- command: "push",
4771
- request_id: requestId,
4772
- dry_run: options.dryRun === true,
4773
- ok: true,
4774
- exit_code: 0,
4775
- project_id: result.projectId,
4776
- summary: {
4777
- planned: result.preview.operations.length,
4778
- submitted: options.dryRun === true ? 0 : result.preview.operations.length,
4779
- skipped: result.preview.skipped.length,
4780
- findings: result.preview.security.findings.length
4781
- },
4782
- items,
4783
- warnings: result.preview.skipped,
4784
- errors: []
4785
- };
4786
- dependencies.stdout(options.json === true ? serializeCliResult(output) : options.dryRun === true ? "Push preview contains " + items.length + " operations.\n" : "Pushed artifact " + result.artifactId + " (proposal " + result.proposalId + ").\n");
4787
- return 0;
4788
- } catch (error) {
4789
- const exitCode = error instanceof PushWorkflowError ? error.exitCode : 1;
4790
- const message = error instanceof Error ? error.message : String(error);
4791
- dependencies.stderr(message + "\n");
4792
- if (options.json === true) {
4793
- dependencies.stdout(serializeCliResult({
5121
+ if (options.nonInteractive === true && options.skipSensitiveScan === true && options.yes !== true) {
5122
+ dependencies.stderr("\u975E\u4EA4\u4E92\u8DF3\u8FC7\u654F\u611F\u626B\u63CF\u9700\u8981 --yes\n");
5123
+ return 2;
5124
+ }
5125
+ async function executePush(attempt = 0) {
5126
+ try {
5127
+ const result = await pushProject({
5128
+ projectRoot: dependencies.cwd,
5129
+ resourcesRoot: dependencies.resourcesRoot,
5130
+ ...options.serverUrl === void 0 ? {} : { serverUrl: options.serverUrl },
5131
+ ...options.tokenEnv === void 0 ? {} : { tokenEnv: options.tokenEnv },
5132
+ env: dependencies.env,
5133
+ dryRun: options.dryRun === true,
5134
+ fetch: dependencies.fetch,
5135
+ ...options.skipSensitiveScan === true ? { sensitiveScanSkip: true } : {},
5136
+ ...options.yes === true || options.nonInteractive === true ? {} : { confirmProposal: async () => {
5137
+ const answer = await dependencies.prompt("Create this proposal? [y/N]: ");
5138
+ return /^(?:y|yes)$/i.test(answer.trim());
5139
+ } },
5140
+ ...options.yes === true || options.nonInteractive === true || options.skipSensitiveScan === true ? {} : { confirmSensitiveScanSkip: async (preview) => {
5141
+ dependencies.stderr(formatFindings({
5142
+ findings: preview.security.findings.filter((finding) => finding.disposition === "blocked").map((finding) => ({
5143
+ path: finding.path,
5144
+ rule_id: finding.rule_id,
5145
+ severity: finding.severity,
5146
+ overridable: finding.overridable,
5147
+ fingerprint: finding.fingerprint,
5148
+ line: finding.line,
5149
+ column: finding.column
5150
+ })),
5151
+ finding_count: preview.security.findings.filter(
5152
+ (finding) => finding.disposition === "blocked"
5153
+ ).length,
5154
+ scanner_version: preview.security.scanner_version
5155
+ }));
5156
+ const answer = await dependencies.prompt(
5157
+ "\u654F\u611F\u626B\u63CF\u5DF2\u963B\u65AD\u63A8\u9001\u3002\u662F\u5426\u663E\u5F0F\u8DF3\u8FC7\u5E76\u7EE7\u7EED\uFF1F[y/N]: "
5158
+ );
5159
+ if (!/^(?:y|yes)$/i.test(answer.trim())) {
5160
+ return "cancelled";
5161
+ }
5162
+ const reasonAnswer = await dependencies.prompt("\u8DF3\u8FC7\u539F\u56E0\uFF08\u53EF\u9009\uFF0C\u56DE\u8F66\u8DF3\u8FC7\uFF09: ");
5163
+ const reason = reasonAnswer.trim();
5164
+ return reason === "" ? { skip: true } : { skip: true, reason };
5165
+ } }
5166
+ });
5167
+ if ("cancelled" in result && result.cancelled === true) {
5168
+ return 2;
5169
+ }
5170
+ const items = result.preview.operations.map((operation) => ({
5171
+ path: operation.operation === "rename" ? operation.to_path : operation.path,
5172
+ operation: operation.operation,
5173
+ file_kind: operation.file_kind,
5174
+ status: options.dryRun === true ? "planned" : "submitted",
5175
+ reason: null,
5176
+ size_bytes: "size_bytes" in operation ? operation.size_bytes : 0,
5177
+ content_sha256: "content_sha256" in operation ? operation.content_sha256 : operation.tombstone.previous_sha256
5178
+ }));
5179
+ const output = {
4794
5180
  schema_version: 1,
4795
5181
  command: "push",
4796
5182
  request_id: requestId,
4797
5183
  dry_run: options.dryRun === true,
4798
- ok: false,
4799
- exit_code: exitCode,
4800
- project_id: null,
4801
- summary: { planned: 0, submitted: 0 },
4802
- items: [],
4803
- warnings: [],
4804
- errors: [{
4805
- code: error instanceof PushWorkflowError ? error.code : "GENERAL_FAILURE",
4806
- message
4807
- }]
4808
- }));
5184
+ ok: true,
5185
+ exit_code: 0,
5186
+ project_id: result.projectId,
5187
+ summary: {
5188
+ planned: result.preview.operations.length,
5189
+ submitted: options.dryRun === true ? 0 : result.preview.operations.length,
5190
+ skipped: result.preview.skipped.length,
5191
+ findings: result.preview.security.findings.length
5192
+ },
5193
+ items,
5194
+ warnings: result.preview.skipped,
5195
+ errors: []
5196
+ };
5197
+ dependencies.stdout(options.json === true ? serializeCliResult(output) : options.dryRun === true ? "Push preview contains " + items.length + " operations.\n" : "Pushed artifact " + result.artifactId + " (proposal " + result.proposalId + ").\n");
5198
+ return 0;
5199
+ } catch (error) {
5200
+ if (attempt === 0 && options.nonInteractive !== true && options.dryRun !== true && error instanceof PushWorkflowError && (error.code === "SERVER_URL_REQUIRED" || error.code === "TOKEN_INVALID")) {
5201
+ const missing = error.code === "SERVER_URL_REQUIRED" ? "url" : error.code === "TOKEN_INVALID" ? "token" : "both";
5202
+ dependencies.stderr(
5203
+ error.message + "\n\u53EF\u5728\u4E0B\u65B9\u5F55\u5165\u5E76\u5199\u5165 .harness/credentials.local.yaml\u3002\n"
5204
+ );
5205
+ const entered = await promptForCredentials(dependencies, missing);
5206
+ if (entered !== null) {
5207
+ const existing = await readLocalCredentials(dependencies.cwd);
5208
+ try {
5209
+ await writeLocalCredentials(dependencies.cwd, mergeLocalCredentials(existing, {
5210
+ ...entered.serverUrl === void 0 ? {} : { server_url: entered.serverUrl },
5211
+ ...entered.token === void 0 ? {} : { token: entered.token }
5212
+ }));
5213
+ } catch (error2) {
5214
+ if (error2 instanceof InvalidCredentialsError) {
5215
+ dependencies.stderr(error2.message + "\n");
5216
+ return 3;
5217
+ }
5218
+ throw error2;
5219
+ }
5220
+ await ensureCredentialsGitignore(dependencies.cwd);
5221
+ dependencies.stderr(
5222
+ "\u5DF2\u5199\u5165 .harness/credentials.local.yaml\uFF1Bupload session \u7531 CLI \u81EA\u52A8\u7BA1\u7406\uFF0C\u65E0\u9700\u624B\u914D\u3002\n"
5223
+ );
5224
+ return executePush(attempt + 1);
5225
+ }
5226
+ }
5227
+ const payload = errorPayload(error);
5228
+ dependencies.stderr(payload.message + "\n");
5229
+ dependencies.stderr(formatFindings(payload.details));
5230
+ if (options.json === true) {
5231
+ dependencies.stdout(serializeCliResult({
5232
+ schema_version: 1,
5233
+ command: "push",
5234
+ request_id: requestId,
5235
+ dry_run: options.dryRun === true,
5236
+ ok: false,
5237
+ exit_code: payload.exitCode,
5238
+ project_id: null,
5239
+ summary: { planned: 0, submitted: 0 },
5240
+ items: [],
5241
+ warnings: [],
5242
+ errors: [{
5243
+ code: payload.code,
5244
+ message: payload.message,
5245
+ ...payload.details === void 0 ? {} : { details: payload.details }
5246
+ }]
5247
+ }));
5248
+ }
5249
+ return payload.exitCode;
4809
5250
  }
4810
- return exitCode;
4811
5251
  }
5252
+ return executePush();
4812
5253
  }
4813
5254
 
4814
5255
  // src/commands/update.ts
@@ -4904,7 +5345,7 @@ async function runUpdate(options, dependencies) {
4904
5345
 
4905
5346
  // src/commands/recovery.ts
4906
5347
  import { stat as stat4 } from "node:fs/promises";
4907
- import { join as join15 } from "node:path";
5348
+ import { join as join16 } from "node:path";
4908
5349
  async function exists4(path) {
4909
5350
  try {
4910
5351
  await stat4(path);
@@ -4964,7 +5405,7 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
4964
5405
  return 5;
4965
5406
  }
4966
5407
  }
4967
- const initialized = await exists4(join15(dependencies.cwd, ".harness", "project.yaml"));
5408
+ const initialized = await exists4(join16(dependencies.cwd, ".harness", "project.yaml"));
4968
5409
  if (!initialized || options.nonInteractive === true || explicitConfigure(options)) {
4969
5410
  return null;
4970
5411
  }
@@ -5007,8 +5448,8 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
5007
5448
  }
5008
5449
 
5009
5450
  // src/workflow-data/resolve.ts
5010
- import { cp, mkdir as mkdir4, readdir as readdir7, readFile as readFile14, rm as rm8, stat as stat5 } from "node:fs/promises";
5011
- import { dirname as dirname4, join as join16, relative as relative2 } from "node:path";
5451
+ import { cp, mkdir as mkdir4, readdir as readdir7, readFile as readFile15, rm as rm8, stat as stat5 } from "node:fs/promises";
5452
+ import { dirname as dirname4, join as join17, relative as relative2 } from "node:path";
5012
5453
  import { fileURLToPath } from "node:url";
5013
5454
  var WorkflowDataResolutionError = class extends Error {
5014
5455
  code;
@@ -5045,14 +5486,14 @@ async function listFilesRecursive(root, base = root) {
5045
5486
  const entries = await readdir7(root, { withFileTypes: true });
5046
5487
  const files = [];
5047
5488
  for (const entry of entries) {
5048
- const absolute = join16(root, entry.name);
5489
+ const absolute = join17(root, entry.name);
5049
5490
  if (entry.isDirectory()) {
5050
5491
  files.push(...await listFilesRecursive(absolute, base));
5051
5492
  continue;
5052
5493
  }
5053
5494
  if (!entry.isFile()) continue;
5054
5495
  const rel = relative2(base, absolute).replaceAll("\\", "/");
5055
- files.push({ path: rel, content: await readFile14(absolute, "utf8") });
5496
+ files.push({ path: rel, content: await readFile15(absolute, "utf8") });
5056
5497
  }
5057
5498
  return files;
5058
5499
  }
@@ -5065,7 +5506,7 @@ async function verifyWorkflowPackageIntegrity(resourcesRoot) {
5065
5506
  const manifest = await readWorkflowFamilyManifest(resourcesRoot);
5066
5507
  const expected = manifest.content_sha256;
5067
5508
  if (typeof expected !== "string" || expected.length === 0) return;
5068
- const harnessRoot = join16(resourcesRoot, "harness");
5509
+ const harnessRoot = join17(resourcesRoot, "harness");
5069
5510
  if (!await pathExists3(harnessRoot)) {
5070
5511
  throw new WorkflowDataResolutionError(
5071
5512
  "\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u7F3A\u5C11 harness/\uFF0C\u65E0\u6CD5\u6821\u9A8C SHA-256",
@@ -5084,31 +5525,40 @@ async function verifyWorkflowPackageIntegrity(resourcesRoot) {
5084
5525
  }
5085
5526
  }
5086
5527
  async function monorepoResourcesRoot() {
5528
+ const here = dirname4(fileURLToPath(import.meta.url));
5087
5529
  const candidates = [
5088
- fileURLToPath(new URL("../../../resources", import.meta.url)),
5089
- fileURLToPath(new URL("../../../workflow-data-harness", import.meta.url)),
5090
- fileURLToPath(new URL("../../../../resources", import.meta.url))
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")
5091
5536
  ];
5092
5537
  for (const candidate of candidates) {
5093
- if (await pathExists3(join16(candidate, "harness", "manifests"))) return candidate;
5538
+ if (await pathExists3(join17(candidate, "harness", "manifests"))) return candidate;
5094
5539
  }
5095
5540
  return null;
5096
5541
  }
5097
5542
  async function siblingWorkflowPackage(cwd) {
5543
+ const here = dirname4(fileURLToPath(import.meta.url));
5098
5544
  const candidates = [
5099
- join16(cwd, "node_modules", "@hunter-harness", "workflow-harness"),
5100
- join16(dirname4(fileURLToPath(import.meta.url)), "..", "..", "workflow-data-harness")
5545
+ join17(cwd, "node_modules", "@hunter-harness", "workflow-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")
5101
5551
  ];
5102
5552
  for (const candidate of candidates) {
5103
- if (await pathExists3(join16(candidate, "harness", "manifests"))) return candidate;
5553
+ if (await pathExists3(join17(candidate, "harness", "manifests"))) return candidate;
5104
5554
  }
5105
5555
  return null;
5106
5556
  }
5107
5557
  async function readCachedNpmPackageVersion(cacheRoot) {
5108
- const manifestPath = join16(cacheRoot, "package.json");
5558
+ const manifestPath = join17(cacheRoot, "package.json");
5109
5559
  if (!await pathExists3(manifestPath)) return null;
5110
5560
  try {
5111
- const pkg = JSON.parse(await readFile14(manifestPath, "utf8"));
5561
+ const pkg = JSON.parse(await readFile15(manifestPath, "utf8"));
5112
5562
  return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : null;
5113
5563
  } catch {
5114
5564
  return null;
@@ -5131,12 +5581,12 @@ async function latestWorkflowCacheIsStale(cacheRoot, packageName, resolveManifes
5131
5581
  }
5132
5582
  async function materializeWorkflowPackageCache(cacheRoot, packageSpec, extract) {
5133
5583
  await mkdir4(cacheRoot, { recursive: true });
5134
- const staging = join16(cacheRoot, ".extract");
5584
+ const staging = join17(cacheRoot, ".extract");
5135
5585
  await rm8(staging, { recursive: true, force: true });
5136
5586
  await mkdir4(staging, { recursive: true });
5137
5587
  await extract(packageSpec, staging);
5138
- const packageDir = await pathExists3(join16(staging, "harness", "manifests")) ? staging : join16(staging, "package");
5139
- if (!await pathExists3(join16(packageDir, "harness", "manifests"))) {
5588
+ const packageDir = await pathExists3(join17(staging, "harness", "manifests")) ? staging : join17(staging, "package");
5589
+ if (!await pathExists3(join17(packageDir, "harness", "manifests"))) {
5140
5590
  throw new WorkflowDataResolutionError(
5141
5591
  "\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u5185\u5BB9\u65E0\u6548\uFF1A\u7F3A\u5C11 harness/manifests",
5142
5592
  "WORKFLOW_DATA_INVALID",
@@ -5165,8 +5615,8 @@ async function resolveWorkflowResourcesRoot(options, argv = []) {
5165
5615
  const packageName = workflowPackageName(family, options.env);
5166
5616
  const packageSpec = version === "latest" ? packageName : `${packageName}@${version}`;
5167
5617
  const cacheKey = packageSpec.replace("/", "+");
5168
- const cacheRoot = join16(options.cwd, ".harness", "cache", "workflow-packages", cacheKey);
5169
- if (await pathExists3(join16(cacheRoot, "harness", "manifests"))) {
5618
+ const cacheRoot = join17(options.cwd, ".harness", "cache", "workflow-packages", cacheKey);
5619
+ if (await pathExists3(join17(cacheRoot, "harness", "manifests"))) {
5170
5620
  if (version === "latest") {
5171
5621
  const stale = await latestWorkflowCacheIsStale(cacheRoot, packageName, options.pacoteManifest);
5172
5622
  if (stale) {
@@ -5212,9 +5662,9 @@ function describeWorkflowDataFetchFailure(error, packageSpec) {
5212
5662
  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}`;
5213
5663
  }
5214
5664
  async function readWorkflowFamilyManifest(resourcesRoot) {
5215
- const manifestPath = join16(resourcesRoot, "hunter-workflow-family.json");
5665
+ const manifestPath = join17(resourcesRoot, "hunter-workflow-family.json");
5216
5666
  if (!await pathExists3(manifestPath)) return {};
5217
- return JSON.parse(await readFile14(manifestPath, "utf8"));
5667
+ return JSON.parse(await readFile15(manifestPath, "utf8"));
5218
5668
  }
5219
5669
 
5220
5670
  // src/bin.ts
@@ -5283,7 +5733,7 @@ async function runCli(argv, overrides = {}) {
5283
5733
  dependencies
5284
5734
  );
5285
5735
  });
5286
- addCommonOptions(program.command("push")).description("\u521B\u5EFA\u53D7\u6CBB\u7406\u7684\u53D8\u66F4\u63D0\u6848").action(async (options) => {
5736
+ addCommonOptions(program.command("push")).description("\u521B\u5EFA\u53D7\u6CBB\u7406\u7684\u53D8\u66F4\u63D0\u6848").option("--skip-sensitive-scan", "\u663E\u5F0F\u8DF3\u8FC7\u654F\u611F\u626B\u63CF\u963B\u65AD\uFF08\u975E\u4EA4\u4E92\u9700\u914D\u5408 --yes\uFF09").action(async (options) => {
5287
5737
  exitCode = await runPush({ ...program.opts(), ...options }, dependencies);
5288
5738
  });
5289
5739
  addCommonOptions(program.command("cleanup")).description("\u6E05\u7406\u5DF2\u5B8C\u6210\u4E8B\u52A1\u548C\u8FC7\u671F\u670D\u52A1\u7AEF\u7F13\u5B58").action(async (options) => {