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.
- package/dist/bin.js +690 -240
- 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
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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,
|
|
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(
|
|
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,
|
|
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:
|
|
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
|
|
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
|
-
|
|
2701
|
-
|
|
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
|
|
2732
|
-
const oldContext = { profile: previousProfile, codebuddySurface: codebuddySurface2 };
|
|
2823
|
+
} else {
|
|
2733
2824
|
const oldTargets = [];
|
|
2734
|
-
for (const agent of
|
|
2735
|
-
const
|
|
2736
|
-
|
|
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
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
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,
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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:
|
|
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:
|
|
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(
|
|
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/
|
|
3176
|
-
import {
|
|
3177
|
-
import { join as
|
|
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
|
|
3182
|
-
import { join as
|
|
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
|
|
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
|
|
3191
|
-
import { join as
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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(
|
|
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,
|
|
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,
|
|
3557
|
+
await walkFiles(root, join10(root, adapter.rulesRoot), paths);
|
|
3325
3558
|
}
|
|
3326
|
-
await walkHarnessEntries(root,
|
|
3559
|
+
await walkHarnessEntries(root, join10(root, adapter.skillsRoot), paths);
|
|
3327
3560
|
if (adapter.agentsRoot !== null) {
|
|
3328
|
-
await walkHarnessEntries(root,
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
3451
|
-
|
|
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
|
|
3460
|
-
|
|
3461
|
-
|
|
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 =
|
|
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 (
|
|
3509
|
-
|
|
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(
|
|
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
|
|
3606
|
-
import { join as
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
3712
|
-
import { join as
|
|
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
|
|
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
|
|
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 =
|
|
3773
|
-
const journal = JSON.parse(await
|
|
3774
|
-
const after = JSON.parse(await
|
|
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 =
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
3854
|
-
import { join as
|
|
3855
|
-
import { parse as
|
|
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) ?
|
|
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 =
|
|
3897
|
-
const cachePath =
|
|
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
|
|
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(
|
|
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(
|
|
4035
|
-
const targetContent = target === source ? sourceContent : await optionalContent(
|
|
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(
|
|
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
|
|
4178
|
-
import { readFile as
|
|
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 :
|
|
4568
|
+
const path = isAbsolute2(flags.config) ? flags.config : join14(cwd, flags.config);
|
|
4260
4569
|
try {
|
|
4261
|
-
fileConfig = JSON.parse(await
|
|
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
|
|
4344
|
-
import { join as
|
|
4345
|
-
import { parse as
|
|
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
|
|
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(
|
|
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
|
|
4671
|
+
function parseProfile(value) {
|
|
4363
4672
|
if (value === void 0 || value === "") {
|
|
4364
|
-
return
|
|
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
|
|
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` : `\
|
|
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${
|
|
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
|
-
|
|
4545
|
-
|
|
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
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
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
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
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:
|
|
4799
|
-
exit_code:
|
|
4800
|
-
project_id:
|
|
4801
|
-
summary: {
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
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
|
|
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(
|
|
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
|
|
5011
|
-
import { dirname as dirname4, join as
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
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(
|
|
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
|
-
|
|
5100
|
-
|
|
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(
|
|
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 =
|
|
5558
|
+
const manifestPath = join17(cacheRoot, "package.json");
|
|
5109
5559
|
if (!await pathExists3(manifestPath)) return null;
|
|
5110
5560
|
try {
|
|
5111
|
-
const pkg = JSON.parse(await
|
|
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 =
|
|
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(
|
|
5139
|
-
if (!await pathExists3(
|
|
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 =
|
|
5169
|
-
if (await pathExists3(
|
|
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 =
|
|
5665
|
+
const manifestPath = join17(resourcesRoot, "hunter-workflow-family.json");
|
|
5216
5666
|
if (!await pathExists3(manifestPath)) return {};
|
|
5217
|
-
return JSON.parse(await
|
|
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) => {
|