hunter-harness 0.2.27 → 0.2.28
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 +402 -191
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -1233,7 +1233,7 @@ async function assertNoSymlinks(root, relativePath) {
|
|
|
1233
1233
|
async function withRetry(action, options) {
|
|
1234
1234
|
const attempts = options.attempts ?? 3;
|
|
1235
1235
|
const sleep = options.sleep ?? (async (milliseconds) => {
|
|
1236
|
-
await new Promise((
|
|
1236
|
+
await new Promise((resolve10) => setTimeout(resolve10, milliseconds));
|
|
1237
1237
|
});
|
|
1238
1238
|
let lastError;
|
|
1239
1239
|
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
@@ -1684,6 +1684,9 @@ function classifyFile(input) {
|
|
|
1684
1684
|
if (under(path, ".harness/knowledge/")) {
|
|
1685
1685
|
return USER_DIFF;
|
|
1686
1686
|
}
|
|
1687
|
+
if (under(path, ".harness/rules/")) {
|
|
1688
|
+
return USER_DIFF;
|
|
1689
|
+
}
|
|
1687
1690
|
if (/^\.harness\/archive\/[^/]+\/reports\/final\/summary-data\.json$/u.test(path)) {
|
|
1688
1691
|
return GENERATED_REVIEWABLE;
|
|
1689
1692
|
}
|
|
@@ -1696,7 +1699,7 @@ function classifyFile(input) {
|
|
|
1696
1699
|
if (under(path, ".harness/reports/")) {
|
|
1697
1700
|
return REPORT_CACHE;
|
|
1698
1701
|
}
|
|
1699
|
-
if (under(path, ".harness/state/")
|
|
1702
|
+
if (under(path, ".harness/state/")) {
|
|
1700
1703
|
return INTERNAL_STATE;
|
|
1701
1704
|
}
|
|
1702
1705
|
if (under(path, ".harness/generated/") || under(path, ".harness/cache/")) {
|
|
@@ -1730,9 +1733,9 @@ function decideUpdate(policy, dirty) {
|
|
|
1730
1733
|
}
|
|
1731
1734
|
|
|
1732
1735
|
// ../core/dist/project/initialize.js
|
|
1733
|
-
import { createHash as
|
|
1734
|
-
import { readFile as
|
|
1735
|
-
import { basename, join as
|
|
1736
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
1737
|
+
import { readFile as readFile5, stat as stat2, writeFile as writeFile4 } from "node:fs/promises";
|
|
1738
|
+
import { basename as basename2, join as join6, resolve as resolve5 } from "node:path";
|
|
1736
1739
|
import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
|
|
1737
1740
|
|
|
1738
1741
|
// ../core/dist/transaction/transaction.js
|
|
@@ -2247,10 +2250,178 @@ function managedTargetsFor(adapter, bundle, context) {
|
|
|
2247
2250
|
return records.sort((left, right) => left.target_path.localeCompare(right.target_path));
|
|
2248
2251
|
}
|
|
2249
2252
|
|
|
2250
|
-
// ../core/dist/project/
|
|
2253
|
+
// ../core/dist/project/project-rules.js
|
|
2251
2254
|
import { createHash as createHash3 } from "node:crypto";
|
|
2252
|
-
import { readFile as readFile3, readdir as readdir2 } from "node:fs/promises";
|
|
2253
|
-
import { join as join4, resolve as resolve3 } from "node:path";
|
|
2255
|
+
import { mkdir as mkdir4, readFile as readFile3, readdir as readdir2, rename as rename3, unlink, writeFile as writeFile3 } from "node:fs/promises";
|
|
2256
|
+
import { basename, dirname as dirname3, extname, join as join4, resolve as resolve3 } from "node:path";
|
|
2257
|
+
var RULES_ROOT = ".harness/rules";
|
|
2258
|
+
var RECEIPT_PATH = ".harness/state/local/rule-projections.json";
|
|
2259
|
+
var CODEX_BLOCK_ID = "hunter-harness-project-rules";
|
|
2260
|
+
var MANAGED_NAMES = /* @__PURE__ */ new Set([
|
|
2261
|
+
"harness-general.md",
|
|
2262
|
+
"harness-general.mdc",
|
|
2263
|
+
"harness-profile-java.md",
|
|
2264
|
+
"harness-profile-java.mdc"
|
|
2265
|
+
]);
|
|
2266
|
+
function sha256(content) {
|
|
2267
|
+
return createHash3("sha256").update(content).digest("hex");
|
|
2268
|
+
}
|
|
2269
|
+
async function optionalText(path) {
|
|
2270
|
+
try {
|
|
2271
|
+
return await readFile3(path, "utf8");
|
|
2272
|
+
} catch (error) {
|
|
2273
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
2274
|
+
return null;
|
|
2275
|
+
throw error;
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
async function readReceipt(root) {
|
|
2279
|
+
try {
|
|
2280
|
+
const parsed = JSON.parse(await readFile3(join4(root, RECEIPT_PATH), "utf8"));
|
|
2281
|
+
if (parsed.schema_version === 1 && parsed.targets && parsed.source_hashes)
|
|
2282
|
+
return parsed;
|
|
2283
|
+
} catch {
|
|
2284
|
+
}
|
|
2285
|
+
return { schema_version: 1, source_hashes: {}, targets: {} };
|
|
2286
|
+
}
|
|
2287
|
+
async function markdownFiles(root) {
|
|
2288
|
+
try {
|
|
2289
|
+
return (await readdir2(root, { withFileTypes: true })).filter((entry) => entry.isFile() && [".md", ".mdc"].includes(extname(entry.name).toLowerCase())).map((entry) => entry.name).filter((name) => !MANAGED_NAMES.has(name)).sort();
|
|
2290
|
+
} catch (error) {
|
|
2291
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
2292
|
+
return [];
|
|
2293
|
+
throw error;
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
function targetsFor(name, agents, surface) {
|
|
2297
|
+
const stem = basename(name, extname(name));
|
|
2298
|
+
const targets = [];
|
|
2299
|
+
if (agents.includes("claude-code"))
|
|
2300
|
+
targets.push(`.claude/rules/${stem}.md`);
|
|
2301
|
+
if (agents.includes("cursor"))
|
|
2302
|
+
targets.push(`.cursor/rules/${stem}.mdc`);
|
|
2303
|
+
if (agents.includes("codebuddy") && surface !== "cli") {
|
|
2304
|
+
targets.push(`.codebuddy/.rules/${stem}.mdc`);
|
|
2305
|
+
}
|
|
2306
|
+
if (agents.includes("codebuddy") && surface !== "ide") {
|
|
2307
|
+
targets.push(`.codebuddy/rules/${stem}.md`);
|
|
2308
|
+
}
|
|
2309
|
+
return targets;
|
|
2310
|
+
}
|
|
2311
|
+
async function atomicWrite(path, content) {
|
|
2312
|
+
await mkdir4(dirname3(path), { recursive: true });
|
|
2313
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
2314
|
+
await writeFile3(temporary, content, "utf8");
|
|
2315
|
+
await rename3(temporary, path);
|
|
2316
|
+
}
|
|
2317
|
+
async function synchronizeProjectRules(projectRoot, agents, surface = "both") {
|
|
2318
|
+
const root = resolve3(projectRoot);
|
|
2319
|
+
const canonicalRoot = join4(root, RULES_ROOT);
|
|
2320
|
+
const result = {
|
|
2321
|
+
migrated: [],
|
|
2322
|
+
written: [],
|
|
2323
|
+
removed: [],
|
|
2324
|
+
unchanged: [],
|
|
2325
|
+
conflicts: []
|
|
2326
|
+
};
|
|
2327
|
+
await mkdir4(canonicalRoot, { recursive: true });
|
|
2328
|
+
if ((await markdownFiles(canonicalRoot)).length === 0) {
|
|
2329
|
+
for (const name of await markdownFiles(join4(root, ".claude", "rules"))) {
|
|
2330
|
+
const destination = join4(canonicalRoot, `${basename(name, extname(name))}.md`);
|
|
2331
|
+
if (await optionalText(destination) !== null)
|
|
2332
|
+
continue;
|
|
2333
|
+
const content = await readFile3(join4(root, ".claude", "rules", name), "utf8");
|
|
2334
|
+
await atomicWrite(destination, content);
|
|
2335
|
+
result.migrated.push(`${RULES_ROOT}/${basename(destination)}`);
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
const previous = await readReceipt(root);
|
|
2339
|
+
const next = { schema_version: 1, source_hashes: {}, targets: {} };
|
|
2340
|
+
const desired = /* @__PURE__ */ new Map();
|
|
2341
|
+
for (const name of await markdownFiles(canonicalRoot)) {
|
|
2342
|
+
const sourcePath = `${RULES_ROOT}/${name}`;
|
|
2343
|
+
const content = await readFile3(join4(canonicalRoot, name), "utf8");
|
|
2344
|
+
next.source_hashes[sourcePath] = sha256(content);
|
|
2345
|
+
for (const target of targetsFor(name, agents, surface))
|
|
2346
|
+
desired.set(target, content);
|
|
2347
|
+
}
|
|
2348
|
+
for (const [target, content] of desired) {
|
|
2349
|
+
const path = join4(root, target);
|
|
2350
|
+
const current = await optionalText(path);
|
|
2351
|
+
const incomingHash = sha256(content);
|
|
2352
|
+
if (current === content) {
|
|
2353
|
+
result.unchanged.push(target);
|
|
2354
|
+
next.targets[target] = incomingHash;
|
|
2355
|
+
} else if (current === null || previous.targets[target] === sha256(current)) {
|
|
2356
|
+
await atomicWrite(path, content);
|
|
2357
|
+
result.written.push(target);
|
|
2358
|
+
next.targets[target] = incomingHash;
|
|
2359
|
+
} else {
|
|
2360
|
+
result.conflicts.push(target);
|
|
2361
|
+
next.targets[target] = previous.targets[target] ?? sha256(current);
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
for (const [target, trustedHash] of Object.entries(previous.targets)) {
|
|
2365
|
+
if (target === "AGENTS.md")
|
|
2366
|
+
continue;
|
|
2367
|
+
if (desired.has(target))
|
|
2368
|
+
continue;
|
|
2369
|
+
const path = join4(root, target);
|
|
2370
|
+
const current = await optionalText(path);
|
|
2371
|
+
if (current === null)
|
|
2372
|
+
continue;
|
|
2373
|
+
if (sha256(current) === trustedHash) {
|
|
2374
|
+
await unlink(path);
|
|
2375
|
+
result.removed.push(target);
|
|
2376
|
+
} else {
|
|
2377
|
+
result.conflicts.push(target);
|
|
2378
|
+
next.targets[target] = trustedHash;
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
if (agents.includes("codex")) {
|
|
2382
|
+
const rules = Object.keys(next.source_hashes).sort();
|
|
2383
|
+
const body = [
|
|
2384
|
+
"Before project work, read and follow these shared project rules:",
|
|
2385
|
+
...rules.map((path) => `- \`${path}\``)
|
|
2386
|
+
].join("\n");
|
|
2387
|
+
const agentsPath = join4(root, "AGENTS.md");
|
|
2388
|
+
const current = await optionalText(agentsPath) ?? "";
|
|
2389
|
+
const updated = rules.length > 0 ? upsertManagedBlockById(current, CODEX_BLOCK_ID, body) : removeManagedBlockById(current, CODEX_BLOCK_ID);
|
|
2390
|
+
const target = "AGENTS.md";
|
|
2391
|
+
const semanticallyEqual = updated.replace(/\r\n/g, "\n").trimEnd() === current.replace(/\r\n/g, "\n").trimEnd();
|
|
2392
|
+
if (semanticallyEqual)
|
|
2393
|
+
result.unchanged.push(target);
|
|
2394
|
+
else {
|
|
2395
|
+
await atomicWrite(agentsPath, updated);
|
|
2396
|
+
result.written.push(target);
|
|
2397
|
+
}
|
|
2398
|
+
if (rules.length > 0) {
|
|
2399
|
+
next.targets[target] = sha256(semanticallyEqual ? current : updated);
|
|
2400
|
+
}
|
|
2401
|
+
} else if (Object.prototype.hasOwnProperty.call(previous.targets, "AGENTS.md")) {
|
|
2402
|
+
const agentsPath = join4(root, "AGENTS.md");
|
|
2403
|
+
const current = await optionalText(agentsPath);
|
|
2404
|
+
if (current !== null) {
|
|
2405
|
+
const updated = removeManagedBlockById(current, CODEX_BLOCK_ID);
|
|
2406
|
+
if (updated !== current) {
|
|
2407
|
+
await atomicWrite(agentsPath, updated);
|
|
2408
|
+
result.written.push("AGENTS.md");
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
await atomicWrite(join4(root, RECEIPT_PATH), JSON.stringify(next, null, 2) + "\n");
|
|
2413
|
+
result.migrated.sort();
|
|
2414
|
+
result.written.sort();
|
|
2415
|
+
result.removed.sort();
|
|
2416
|
+
result.unchanged.sort();
|
|
2417
|
+
result.conflicts = [...new Set(result.conflicts)].sort();
|
|
2418
|
+
return result;
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
// ../core/dist/project/profile-bundle.js
|
|
2422
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
2423
|
+
import { readFile as readFile4, readdir as readdir3 } from "node:fs/promises";
|
|
2424
|
+
import { join as join5, resolve as resolve4 } from "node:path";
|
|
2254
2425
|
var AdapterBundleError = class extends Error {
|
|
2255
2426
|
code;
|
|
2256
2427
|
exitCode = 7;
|
|
@@ -2276,15 +2447,15 @@ function validateRelativeBundlePath2(path) {
|
|
|
2276
2447
|
}
|
|
2277
2448
|
var bundleCache = /* @__PURE__ */ new Map();
|
|
2278
2449
|
async function loadAgentBundle(resourcesRoot, profile, agent) {
|
|
2279
|
-
const cacheKey = `${
|
|
2450
|
+
const cacheKey = `${resolve4(resourcesRoot)}:${profile}:${agent}`;
|
|
2280
2451
|
const cached = bundleCache.get(cacheKey);
|
|
2281
2452
|
if (cached) {
|
|
2282
2453
|
return { manifest: cached.manifest, files: new Map(cached.files) };
|
|
2283
2454
|
}
|
|
2284
|
-
const manifestPath =
|
|
2455
|
+
const manifestPath = join5(resourcesRoot, "harness", "manifests", profile, `${agent}.json`);
|
|
2285
2456
|
let manifestText;
|
|
2286
2457
|
try {
|
|
2287
|
-
manifestText = await
|
|
2458
|
+
manifestText = await readFile4(manifestPath, "utf8");
|
|
2288
2459
|
} catch (error) {
|
|
2289
2460
|
if (isEnoent(error)) {
|
|
2290
2461
|
throw new AdapterBundleError("ADAPTER_BUNDLE_MISSING", `offline Harness Bundle manifest missing: ${profile}/${agent}`);
|
|
@@ -2312,14 +2483,14 @@ async function loadAgentBundle(resourcesRoot, profile, agent) {
|
|
|
2312
2483
|
}
|
|
2313
2484
|
let bytes;
|
|
2314
2485
|
try {
|
|
2315
|
-
bytes = await
|
|
2486
|
+
bytes = await readFile4(join5(resourcesRoot, "harness", "bundles", profile, agent, item2.path));
|
|
2316
2487
|
} catch (error) {
|
|
2317
2488
|
if (isEnoent(error)) {
|
|
2318
2489
|
throw new AdapterBundleError("ADAPTER_BUNDLE_MISSING", `offline Harness Bundle file missing: ${profile}/${agent}/${item2.path}`);
|
|
2319
2490
|
}
|
|
2320
2491
|
throw error;
|
|
2321
2492
|
}
|
|
2322
|
-
if (
|
|
2493
|
+
if (createHash4("sha256").update(bytes).digest("hex") !== item2.sha256) {
|
|
2323
2494
|
throw new AdapterBundleError("ADAPTER_BUNDLE_INVALID", `Harness Bundle hash mismatch: ${profile}/${agent}/${item2.path}`);
|
|
2324
2495
|
}
|
|
2325
2496
|
files.set(item2.path, bytes);
|
|
@@ -2384,10 +2555,10 @@ function parseMigrationManifest(raw) {
|
|
|
2384
2555
|
};
|
|
2385
2556
|
}
|
|
2386
2557
|
async function loadMigrationManifests(resourcesRoot) {
|
|
2387
|
-
const migrationsRoot =
|
|
2558
|
+
const migrationsRoot = join5(resourcesRoot, "harness", "migrations");
|
|
2388
2559
|
let versionDirs;
|
|
2389
2560
|
try {
|
|
2390
|
-
versionDirs = await
|
|
2561
|
+
versionDirs = await readdir3(migrationsRoot);
|
|
2391
2562
|
} catch (error) {
|
|
2392
2563
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
2393
2564
|
return [];
|
|
@@ -2398,7 +2569,7 @@ async function loadMigrationManifests(resourcesRoot) {
|
|
|
2398
2569
|
for (const versionDir of versionDirs) {
|
|
2399
2570
|
let files;
|
|
2400
2571
|
try {
|
|
2401
|
-
files = await
|
|
2572
|
+
files = await readdir3(join5(migrationsRoot, versionDir));
|
|
2402
2573
|
} catch (error) {
|
|
2403
2574
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
2404
2575
|
continue;
|
|
@@ -2408,7 +2579,7 @@ async function loadMigrationManifests(resourcesRoot) {
|
|
|
2408
2579
|
for (const file of files) {
|
|
2409
2580
|
if (!file.endsWith(".json"))
|
|
2410
2581
|
continue;
|
|
2411
|
-
manifests.push(parseMigrationManifest(JSON.parse(await
|
|
2582
|
+
manifests.push(parseMigrationManifest(JSON.parse(await readFile4(join5(migrationsRoot, versionDir, file), "utf8"))));
|
|
2412
2583
|
}
|
|
2413
2584
|
}
|
|
2414
2585
|
return manifests;
|
|
@@ -2440,14 +2611,14 @@ function uuidV7(now = Date.now()) {
|
|
|
2440
2611
|
var CONTEXT_INDEX_PATH = ".harness/context-index.json";
|
|
2441
2612
|
async function fileHex(path) {
|
|
2442
2613
|
try {
|
|
2443
|
-
const data = await
|
|
2444
|
-
return
|
|
2614
|
+
const data = await readFile5(path);
|
|
2615
|
+
return createHash5("sha256").update(data).digest("hex");
|
|
2445
2616
|
} catch {
|
|
2446
2617
|
return null;
|
|
2447
2618
|
}
|
|
2448
2619
|
}
|
|
2449
2620
|
async function readExistingSkillBundles(root) {
|
|
2450
|
-
const text = await readOptional(
|
|
2621
|
+
const text = await readOptional(join6(root, CONTEXT_INDEX_PATH));
|
|
2451
2622
|
if (text === "")
|
|
2452
2623
|
return /* @__PURE__ */ new Map();
|
|
2453
2624
|
try {
|
|
@@ -2467,7 +2638,7 @@ var TargetCollisionError = class extends Error {
|
|
|
2467
2638
|
}
|
|
2468
2639
|
};
|
|
2469
2640
|
function hex(bytes) {
|
|
2470
|
-
return
|
|
2641
|
+
return createHash5("sha256").update(bytes).digest("hex");
|
|
2471
2642
|
}
|
|
2472
2643
|
function bytesEqual(left, right) {
|
|
2473
2644
|
if (left.byteLength !== right.byteLength)
|
|
@@ -2480,7 +2651,7 @@ function bytesEqual(left, right) {
|
|
|
2480
2651
|
}
|
|
2481
2652
|
async function readOptional(path) {
|
|
2482
2653
|
try {
|
|
2483
|
-
return await
|
|
2654
|
+
return await readFile5(path, "utf8");
|
|
2484
2655
|
} catch (error) {
|
|
2485
2656
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
2486
2657
|
return "";
|
|
@@ -2500,12 +2671,12 @@ async function exists2(path) {
|
|
|
2500
2671
|
}
|
|
2501
2672
|
}
|
|
2502
2673
|
async function existingProjectConfig(root) {
|
|
2503
|
-
const path =
|
|
2674
|
+
const path = join6(root, ".harness", "project.yaml");
|
|
2504
2675
|
const content = await readOptional(path);
|
|
2505
2676
|
return content === "" ? null : projectConfigSchema.parse(parseYaml2(content));
|
|
2506
2677
|
}
|
|
2507
2678
|
async function operationFor(root, path, content) {
|
|
2508
|
-
return await exists2(
|
|
2679
|
+
return await exists2(join6(root, path)) ? { operation: "modify", path, content } : { operation: "add", path, content };
|
|
2509
2680
|
}
|
|
2510
2681
|
var INSTALLED_BUNDLE_PATH = ".harness/state/local/installed-harness-bundle.json";
|
|
2511
2682
|
function mergeOwnedTargets(owned) {
|
|
@@ -2541,7 +2712,7 @@ function mergeOwnedTargets(owned) {
|
|
|
2541
2712
|
});
|
|
2542
2713
|
}
|
|
2543
2714
|
async function initializeProject(options) {
|
|
2544
|
-
const root =
|
|
2715
|
+
const root = resolve5(options.projectRoot);
|
|
2545
2716
|
const config = initConfigSchema.parse(options.config);
|
|
2546
2717
|
const existing = await existingProjectConfig(root);
|
|
2547
2718
|
const profile = config.profile;
|
|
@@ -2584,7 +2755,7 @@ async function initializeProject(options) {
|
|
|
2584
2755
|
const projectConfig = projectConfigSchema.parse({
|
|
2585
2756
|
harness: { name: "hunter-harness", schema_version: 1 },
|
|
2586
2757
|
project: {
|
|
2587
|
-
name: existing?.project.name ??
|
|
2758
|
+
name: existing?.project.name ?? basename2(root),
|
|
2588
2759
|
root: ".",
|
|
2589
2760
|
local_project_key: existing?.project.local_project_key ?? uuidV7(),
|
|
2590
2761
|
project_id: config.project_id ?? existing?.project.project_id ?? null,
|
|
@@ -2606,7 +2777,7 @@ async function initializeProject(options) {
|
|
|
2606
2777
|
files: {}
|
|
2607
2778
|
});
|
|
2608
2779
|
const managedBlocks = [];
|
|
2609
|
-
let agentsContent = await readOptional(
|
|
2780
|
+
let agentsContent = await readOptional(join6(root, "AGENTS.md"));
|
|
2610
2781
|
agentsContent = upsertManagedBlockById(agentsContent, AGENTS_CORE_BLOCK_ID, AGENTS_MANAGED_BLOCK_CONTENT);
|
|
2611
2782
|
managedBlocks.push({
|
|
2612
2783
|
owner: "shared",
|
|
@@ -2643,7 +2814,7 @@ async function initializeProject(options) {
|
|
|
2643
2814
|
["AGENTS.md", agentsContent]
|
|
2644
2815
|
]);
|
|
2645
2816
|
if (enabledAgents.includes("claude-code")) {
|
|
2646
|
-
let claudeContent = await readOptional(
|
|
2817
|
+
let claudeContent = await readOptional(join6(root, "CLAUDE.md"));
|
|
2647
2818
|
claudeContent = upsertManagedBlockById(claudeContent, CLAUDE_BLOCK_ID, CLAUDE_MANAGED_BLOCK_CONTENT);
|
|
2648
2819
|
files.set("CLAUDE.md", claudeContent);
|
|
2649
2820
|
managedBlocks.push({
|
|
@@ -2654,7 +2825,7 @@ async function initializeProject(options) {
|
|
|
2654
2825
|
});
|
|
2655
2826
|
}
|
|
2656
2827
|
if (enabledAgents.includes("codebuddy")) {
|
|
2657
|
-
let codebuddyContent = await readOptional(
|
|
2828
|
+
let codebuddyContent = await readOptional(join6(root, "CODEBUDDY.md"));
|
|
2658
2829
|
codebuddyContent = upsertManagedBlockById(codebuddyContent, CODEBUDDY_BLOCK_ID, CODEBUDDY_MANAGED_BLOCK_CONTENT);
|
|
2659
2830
|
files.set("CODEBUDDY.md", codebuddyContent);
|
|
2660
2831
|
managedBlocks.push({
|
|
@@ -2691,6 +2862,7 @@ async function initializeProject(options) {
|
|
|
2691
2862
|
const writeOperations = await Promise.all([...files.entries()].map(async ([path, content]) => operationFor(root, path, content)));
|
|
2692
2863
|
await runTransaction(root, writeOperations, { kind: "init" });
|
|
2693
2864
|
await enrichContextIndexWithVerification(root, enabledAgents, profile, options.resourcesRoot, adapterContext);
|
|
2865
|
+
await synchronizeProjectRules(root, enabledAgents, surface);
|
|
2694
2866
|
}
|
|
2695
2867
|
return {
|
|
2696
2868
|
projectConfig,
|
|
@@ -2700,8 +2872,8 @@ async function initializeProject(options) {
|
|
|
2700
2872
|
};
|
|
2701
2873
|
}
|
|
2702
2874
|
async function enrichContextIndexWithVerification(root, agents, profile, resourcesRoot, adapterContext) {
|
|
2703
|
-
const contextPath =
|
|
2704
|
-
const existing = await readOptional(
|
|
2875
|
+
const contextPath = join6(root, CONTEXT_INDEX_PATH);
|
|
2876
|
+
const existing = await readOptional(join6(contextPath));
|
|
2705
2877
|
if (existing === "")
|
|
2706
2878
|
return;
|
|
2707
2879
|
let parsed;
|
|
@@ -2726,7 +2898,7 @@ async function enrichContextIndexWithVerification(root, agents, profile, resourc
|
|
|
2726
2898
|
const entries = [];
|
|
2727
2899
|
for (const target of targets) {
|
|
2728
2900
|
const rel = target.target_path.replace(/\\/g, "/");
|
|
2729
|
-
const actual = await fileHex(
|
|
2901
|
+
const actual = await fileHex(join6(root, target.target_path));
|
|
2730
2902
|
entries.push({ relpath: rel, sha256: actual ?? "" });
|
|
2731
2903
|
if (actual === null) {
|
|
2732
2904
|
mismatches.push({ relpath: rel, expected: target.sha256, actual: "<missing>" });
|
|
@@ -2743,19 +2915,19 @@ async function enrichContextIndexWithVerification(root, agents, profile, resourc
|
|
|
2743
2915
|
entry.verificationStatus = newStatus;
|
|
2744
2916
|
entry.mismatchDetails = mismatches;
|
|
2745
2917
|
}
|
|
2746
|
-
await
|
|
2918
|
+
await writeFile4(contextPath, JSON.stringify(parsed, null, 2) + "\n", "utf8");
|
|
2747
2919
|
}
|
|
2748
2920
|
|
|
2749
2921
|
// ../core/dist/project/refresh.js
|
|
2750
|
-
import { createHash as
|
|
2751
|
-
import { readFile as
|
|
2752
|
-
import { dirname as
|
|
2922
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
2923
|
+
import { readFile as readFile6, readdir as readdir4, rmdir } from "node:fs/promises";
|
|
2924
|
+
import { dirname as dirname4, join as join7, resolve as resolve6 } from "node:path";
|
|
2753
2925
|
import { parse as parseYaml3, stringify as stringifyYaml2 } from "yaml";
|
|
2754
2926
|
var INSTALLED_STATE_PATH = ".harness/state/local/installed-harness-bundle.json";
|
|
2755
2927
|
var CONTEXT_INDEX_PATH2 = ".harness/context-index.json";
|
|
2756
2928
|
async function fileHex2(path) {
|
|
2757
2929
|
try {
|
|
2758
|
-
return
|
|
2930
|
+
return createHash6("sha256").update(await readFile6(path)).digest("hex");
|
|
2759
2931
|
} catch (error) {
|
|
2760
2932
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
2761
2933
|
return null;
|
|
@@ -2765,7 +2937,7 @@ async function fileHex2(path) {
|
|
|
2765
2937
|
}
|
|
2766
2938
|
async function readOptionalText(path) {
|
|
2767
2939
|
try {
|
|
2768
|
-
return await
|
|
2940
|
+
return await readFile6(path, "utf8");
|
|
2769
2941
|
} catch (error) {
|
|
2770
2942
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
2771
2943
|
return "";
|
|
@@ -2774,7 +2946,7 @@ async function readOptionalText(path) {
|
|
|
2774
2946
|
}
|
|
2775
2947
|
}
|
|
2776
2948
|
async function readInstalledState(root) {
|
|
2777
|
-
const content = await readOptionalText(
|
|
2949
|
+
const content = await readOptionalText(join7(root, INSTALLED_STATE_PATH));
|
|
2778
2950
|
if (content === "") {
|
|
2779
2951
|
return {
|
|
2780
2952
|
profile: null,
|
|
@@ -2844,7 +3016,7 @@ async function readInstalledState(root) {
|
|
|
2844
3016
|
};
|
|
2845
3017
|
}
|
|
2846
3018
|
async function readInstalledAgentConfiguration(projectRoot) {
|
|
2847
|
-
const installed = await readInstalledState(
|
|
3019
|
+
const installed = await readInstalledState(resolve6(projectRoot));
|
|
2848
3020
|
return {
|
|
2849
3021
|
agents: installed.adapters,
|
|
2850
3022
|
profiles: Object.fromEntries(installed.adapters.map((agent) => [
|
|
@@ -2854,7 +3026,7 @@ async function readInstalledAgentConfiguration(projectRoot) {
|
|
|
2854
3026
|
};
|
|
2855
3027
|
}
|
|
2856
3028
|
async function readContextIndexBundleHash(root) {
|
|
2857
|
-
const content = await readOptionalText(
|
|
3029
|
+
const content = await readOptionalText(join7(root, CONTEXT_INDEX_PATH2));
|
|
2858
3030
|
if (content === "")
|
|
2859
3031
|
return null;
|
|
2860
3032
|
try {
|
|
@@ -2866,13 +3038,13 @@ async function readContextIndexBundleHash(root) {
|
|
|
2866
3038
|
}
|
|
2867
3039
|
}
|
|
2868
3040
|
async function pruneEmptyParentDirs(root, deletedPaths, boundaryPaths) {
|
|
2869
|
-
const boundaries = new Set(boundaryPaths.map((path) =>
|
|
3041
|
+
const boundaries = new Set(boundaryPaths.map((path) => join7(root, path)));
|
|
2870
3042
|
for (const deleted of deletedPaths) {
|
|
2871
|
-
let dir =
|
|
3043
|
+
let dir = dirname4(join7(root, deleted));
|
|
2872
3044
|
while (dir.startsWith(root) && !boundaries.has(dir)) {
|
|
2873
3045
|
let entries;
|
|
2874
3046
|
try {
|
|
2875
|
-
entries = await
|
|
3047
|
+
entries = await readdir4(dir);
|
|
2876
3048
|
} catch {
|
|
2877
3049
|
break;
|
|
2878
3050
|
}
|
|
@@ -2883,7 +3055,7 @@ async function pruneEmptyParentDirs(root, deletedPaths, boundaryPaths) {
|
|
|
2883
3055
|
} catch {
|
|
2884
3056
|
break;
|
|
2885
3057
|
}
|
|
2886
|
-
dir =
|
|
3058
|
+
dir = dirname4(dir);
|
|
2887
3059
|
}
|
|
2888
3060
|
}
|
|
2889
3061
|
}
|
|
@@ -2910,7 +3082,7 @@ function sortByTarget(items) {
|
|
|
2910
3082
|
return [...items].sort((left, right) => left.target_path.localeCompare(right.target_path));
|
|
2911
3083
|
}
|
|
2912
3084
|
async function reconcileContextIndex(root, agents, profiles, manifests, codebuddySurface2, verifications) {
|
|
2913
|
-
const existing = await readOptionalText(
|
|
3085
|
+
const existing = await readOptionalText(join7(root, CONTEXT_INDEX_PATH2));
|
|
2914
3086
|
let codebase = {
|
|
2915
3087
|
map: ".harness/codebase/map",
|
|
2916
3088
|
status: "missing"
|
|
@@ -2963,7 +3135,7 @@ async function reconcileContextIndex(root, agents, profiles, manifests, codebudd
|
|
|
2963
3135
|
}
|
|
2964
3136
|
async function projectTransitionOperation(root, agents, profiles, codebuddySurface2) {
|
|
2965
3137
|
const path = ".harness/project.yaml";
|
|
2966
|
-
const content = await readOptionalText(
|
|
3138
|
+
const content = await readOptionalText(join7(root, path));
|
|
2967
3139
|
if (content === "")
|
|
2968
3140
|
return null;
|
|
2969
3141
|
const project = parseYaml3(content);
|
|
@@ -2998,11 +3170,11 @@ function mergeTargets(targets) {
|
|
|
2998
3170
|
}).sort((left, right) => left.target_path.localeCompare(right.target_path));
|
|
2999
3171
|
}
|
|
3000
3172
|
async function reconcileMarkdownBlock(root, fileName, blockId, content, remove, ops, conflicts, preserved) {
|
|
3001
|
-
const original = await readOptionalText(
|
|
3173
|
+
const original = await readOptionalText(join7(root, fileName));
|
|
3002
3174
|
const synthetic = {
|
|
3003
3175
|
source_path: fileName,
|
|
3004
3176
|
target_path: fileName,
|
|
3005
|
-
sha256:
|
|
3177
|
+
sha256: createHash6("sha256").update(content).digest("hex"),
|
|
3006
3178
|
bytes: new TextEncoder().encode(content)
|
|
3007
3179
|
};
|
|
3008
3180
|
let next;
|
|
@@ -3017,7 +3189,7 @@ async function reconcileMarkdownBlock(root, fileName, blockId, content, remove,
|
|
|
3017
3189
|
next = refreshed.content;
|
|
3018
3190
|
}
|
|
3019
3191
|
} catch {
|
|
3020
|
-
const current = original === "" ? null :
|
|
3192
|
+
const current = original === "" ? null : createHash6("sha256").update(original).digest("hex");
|
|
3021
3193
|
preserved.push(item(synthetic, "preserve", "MALFORMED_MANAGED_BLOCK", current, synthetic.sha256));
|
|
3022
3194
|
conflicts.push(conflict(synthetic, "MALFORMED_MANAGED_BLOCK", current, synthetic.sha256));
|
|
3023
3195
|
return;
|
|
@@ -3037,7 +3209,7 @@ function stateWithoutInstalledAt(value) {
|
|
|
3037
3209
|
return copy;
|
|
3038
3210
|
}
|
|
3039
3211
|
async function refreshProject(options) {
|
|
3040
|
-
const root =
|
|
3212
|
+
const root = resolve6(options.projectRoot);
|
|
3041
3213
|
const installed = await readInstalledState(root);
|
|
3042
3214
|
const oldAgents = installed.adapters.length > 0 ? installed.adapters : ["claude-code"];
|
|
3043
3215
|
const selectedAgents = sortHarnessAgents(options.agents);
|
|
@@ -3121,7 +3293,7 @@ async function refreshProject(options) {
|
|
|
3121
3293
|
const newStateFiles = installed.files.filter((entry) => entry.owner === "shared" || !selectedSet.has(entry.owner));
|
|
3122
3294
|
for (const target of newManaged) {
|
|
3123
3295
|
const incoming = target.sha256;
|
|
3124
|
-
const current = await fileHex2(
|
|
3296
|
+
const current = await fileHex2(join7(root, target.target_path));
|
|
3125
3297
|
if (current === null) {
|
|
3126
3298
|
applied.push(item(target, "add", "MISSING_TARGET", null, incoming));
|
|
3127
3299
|
ops.push({ operation: "add", path: target.target_path, content: target.bytes });
|
|
@@ -3149,7 +3321,7 @@ async function refreshProject(options) {
|
|
|
3149
3321
|
}
|
|
3150
3322
|
}
|
|
3151
3323
|
for (const target of oldOnly) {
|
|
3152
|
-
const current = await fileHex2(
|
|
3324
|
+
const current = await fileHex2(join7(root, target.target_path));
|
|
3153
3325
|
if (current === null) {
|
|
3154
3326
|
continue;
|
|
3155
3327
|
}
|
|
@@ -3192,7 +3364,7 @@ async function refreshProject(options) {
|
|
|
3192
3364
|
const verifyEntries = [];
|
|
3193
3365
|
for (const target of verifyTargets) {
|
|
3194
3366
|
const rel = target.target_path.replace(/\\/g, "/");
|
|
3195
|
-
const actual = await fileHex2(
|
|
3367
|
+
const actual = await fileHex2(join7(root, target.target_path));
|
|
3196
3368
|
verifyEntries.push({ relpath: rel, sha256: actual ?? "" });
|
|
3197
3369
|
if (actual === null) {
|
|
3198
3370
|
verifyMismatches.push({ relpath: rel, expected: target.sha256, actual: "<missing>" });
|
|
@@ -3225,19 +3397,19 @@ async function refreshProject(options) {
|
|
|
3225
3397
|
owner: "shared",
|
|
3226
3398
|
target_path: "AGENTS.md",
|
|
3227
3399
|
block_id: AGENTS_CORE_BLOCK_ID,
|
|
3228
|
-
content_sha256:
|
|
3400
|
+
content_sha256: createHash6("sha256").update(AGENTS_MANAGED_BLOCK_CONTENT).digest("hex")
|
|
3229
3401
|
},
|
|
3230
3402
|
...selectedSet.has("claude-code") ? [{
|
|
3231
3403
|
owner: "claude-code",
|
|
3232
3404
|
target_path: "CLAUDE.md",
|
|
3233
3405
|
block_id: CLAUDE_BLOCK_ID,
|
|
3234
|
-
content_sha256:
|
|
3406
|
+
content_sha256: createHash6("sha256").update(CLAUDE_MANAGED_BLOCK_CONTENT).digest("hex")
|
|
3235
3407
|
}] : [],
|
|
3236
3408
|
...selectedSet.has("codebuddy") ? [{
|
|
3237
3409
|
owner: "codebuddy",
|
|
3238
3410
|
target_path: "CODEBUDDY.md",
|
|
3239
3411
|
block_id: CODEBUDDY_BLOCK_ID,
|
|
3240
|
-
content_sha256:
|
|
3412
|
+
content_sha256: createHash6("sha256").update(CODEBUDDY_MANAGED_BLOCK_CONTENT).digest("hex")
|
|
3241
3413
|
}] : []
|
|
3242
3414
|
].sort((left, right) => left.target_path.localeCompare(right.target_path) || left.block_id.localeCompare(right.block_id));
|
|
3243
3415
|
const filesByTarget = new Map(newStateFiles.map((entry) => [entry.target_path, entry]));
|
|
@@ -3253,7 +3425,7 @@ async function refreshProject(options) {
|
|
|
3253
3425
|
files: [...filesByTarget.values()].sort((left, right) => left.target_path.localeCompare(right.target_path) || left.source_path.localeCompare(right.source_path)),
|
|
3254
3426
|
managed_blocks: managedBlocks
|
|
3255
3427
|
};
|
|
3256
|
-
const existingState = await readOptionalText(
|
|
3428
|
+
const existingState = await readOptionalText(join7(root, INSTALLED_STATE_PATH));
|
|
3257
3429
|
let existingParsed = null;
|
|
3258
3430
|
try {
|
|
3259
3431
|
existingParsed = existingState === "" ? null : JSON.parse(existingState);
|
|
@@ -3268,6 +3440,7 @@ async function refreshProject(options) {
|
|
|
3268
3440
|
}
|
|
3269
3441
|
if (!options.dryRun) {
|
|
3270
3442
|
await runTransaction(root, ops, { kind: "refresh" });
|
|
3443
|
+
await synchronizeProjectRules(root, agents, codebuddySurface2);
|
|
3271
3444
|
await pruneEmptyParentDirs(root, removed.map((item2) => item2.target_path), getAdapters(selectedAgents).flatMap((adapter) => adapter.pruneBoundaries({
|
|
3272
3445
|
profile: profiles.get(adapter.name) ?? profile,
|
|
3273
3446
|
codebuddySurface: codebuddySurface2
|
|
@@ -3307,7 +3480,7 @@ function buildMarkerCoreHash(text) {
|
|
|
3307
3480
|
}
|
|
3308
3481
|
}
|
|
3309
3482
|
async function collectFreshness(options) {
|
|
3310
|
-
const root =
|
|
3483
|
+
const root = resolve6(options.projectRoot);
|
|
3311
3484
|
const installed = await readInstalledState(root);
|
|
3312
3485
|
const codebuddySurface2 = options.codebuddySurface ?? "both";
|
|
3313
3486
|
const agents = [];
|
|
@@ -3352,18 +3525,18 @@ async function collectFreshness(options) {
|
|
|
3352
3525
|
identity.adapterHash = sha256Bytes(canonicalJson(targets.map((target) => ({ path: target.target_path.replace(/\\/g, "/"), sha256: target.sha256 })).sort((a, b) => a.path.localeCompare(b.path))));
|
|
3353
3526
|
const installedProjection = await Promise.all(targets.map(async (target) => ({
|
|
3354
3527
|
path: target.target_path.replace(/\\/g, "/"),
|
|
3355
|
-
sha256: await fileHex2(
|
|
3528
|
+
sha256: await fileHex2(join7(root, target.target_path))
|
|
3356
3529
|
})));
|
|
3357
3530
|
identity.installedAdapterHash = sha256Bytes(canonicalJson(installedProjection.sort((a, b) => a.path.localeCompare(b.path))));
|
|
3358
3531
|
const markerTarget = targets.find((target) => target.target_path.replace(/\\/g, "/").endsWith(`/${BUILD_MARKER_BUNDLE_PATH}`) || target.target_path === BUILD_MARKER_BUNDLE_PATH);
|
|
3359
3532
|
if (markerTarget !== void 0) {
|
|
3360
|
-
identity.installedCoreHash = buildMarkerCoreHash(await readOptionalText(
|
|
3533
|
+
identity.installedCoreHash = buildMarkerCoreHash(await readOptionalText(join7(root, markerTarget.target_path)));
|
|
3361
3534
|
}
|
|
3362
3535
|
const mismatchDetails = [];
|
|
3363
3536
|
const contentEntries = [];
|
|
3364
3537
|
for (const target of targets) {
|
|
3365
3538
|
const rel = target.target_path.replace(/\\/g, "/");
|
|
3366
|
-
const actual = await fileHex2(
|
|
3539
|
+
const actual = await fileHex2(join7(root, target.target_path));
|
|
3367
3540
|
contentEntries.push({ relpath: rel, sha256: actual ?? "" });
|
|
3368
3541
|
if (actual === null) {
|
|
3369
3542
|
mismatchDetails.push({ relpath: rel, expected: target.sha256, actual: "<missing>" });
|
|
@@ -3386,7 +3559,7 @@ async function collectFreshness(options) {
|
|
|
3386
3559
|
const drifted = [];
|
|
3387
3560
|
const missing = [];
|
|
3388
3561
|
for (const target of targets) {
|
|
3389
|
-
const current = await fileHex2(
|
|
3562
|
+
const current = await fileHex2(join7(root, target.target_path));
|
|
3390
3563
|
if (current === null) {
|
|
3391
3564
|
missing.push(target.target_path);
|
|
3392
3565
|
} else if (current !== target.sha256) {
|
|
@@ -3750,8 +3923,8 @@ function generateProposalPreview(input, scanOptions = {}) {
|
|
|
3750
3923
|
}
|
|
3751
3924
|
|
|
3752
3925
|
// ../core/dist/push/credentials.js
|
|
3753
|
-
import { readFile as
|
|
3754
|
-
import { join as
|
|
3926
|
+
import { readFile as readFile7, writeFile as writeFile5 } from "node:fs/promises";
|
|
3927
|
+
import { join as join8 } from "node:path";
|
|
3755
3928
|
import { parse as parseYaml4, stringify as stringifyYaml3 } from "yaml";
|
|
3756
3929
|
var CREDENTIALS_LOCAL_RELATIVE = ".harness/credentials.local.yaml";
|
|
3757
3930
|
var CREDENTIALS_GITIGNORE_LINES = [
|
|
@@ -3821,7 +3994,7 @@ function mergeLocalCredentials(existing, patch) {
|
|
|
3821
3994
|
}
|
|
3822
3995
|
async function readLocalCredentials(projectRoot) {
|
|
3823
3996
|
try {
|
|
3824
|
-
const raw = await
|
|
3997
|
+
const raw = await readFile7(join8(projectRoot, CREDENTIALS_LOCAL_RELATIVE), "utf8");
|
|
3825
3998
|
return parseLocalCredentials(parseYaml4(raw));
|
|
3826
3999
|
} catch (error) {
|
|
3827
4000
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
@@ -3832,13 +4005,13 @@ async function readLocalCredentials(projectRoot) {
|
|
|
3832
4005
|
}
|
|
3833
4006
|
async function writeLocalCredentials(projectRoot, credentials) {
|
|
3834
4007
|
const normalized = validateLocalCredentials(credentials);
|
|
3835
|
-
await
|
|
4008
|
+
await writeFile5(join8(projectRoot, CREDENTIALS_LOCAL_RELATIVE), stringifyYaml3(normalized, { sortMapEntries: true }) + "\n", "utf8");
|
|
3836
4009
|
}
|
|
3837
4010
|
async function ensureCredentialsGitignore(projectRoot) {
|
|
3838
|
-
const gitignorePath =
|
|
4011
|
+
const gitignorePath = join8(projectRoot, ".gitignore");
|
|
3839
4012
|
let content = "";
|
|
3840
4013
|
try {
|
|
3841
|
-
content = await
|
|
4014
|
+
content = await readFile7(gitignorePath, "utf8");
|
|
3842
4015
|
} catch (error) {
|
|
3843
4016
|
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) {
|
|
3844
4017
|
throw error;
|
|
@@ -3861,7 +4034,7 @@ async function ensureCredentialsGitignore(projectRoot) {
|
|
|
3861
4034
|
return;
|
|
3862
4035
|
}
|
|
3863
4036
|
const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
|
|
3864
|
-
await
|
|
4037
|
+
await writeFile5(gitignorePath, content + suffix + missing.join("\n") + "\n", "utf8");
|
|
3865
4038
|
}
|
|
3866
4039
|
function resolvePushAuth(input) {
|
|
3867
4040
|
const tokenEnv = input.tokenEnv ?? input.projectTokenEnv;
|
|
@@ -3889,28 +4062,28 @@ function resolvePushAuth(input) {
|
|
|
3889
4062
|
}
|
|
3890
4063
|
|
|
3891
4064
|
// ../core/dist/push/push.js
|
|
3892
|
-
import { lstat as lstat3, readFile as
|
|
3893
|
-
import { join as
|
|
4065
|
+
import { lstat as lstat3, readFile as readFile11, readdir as readdir5, rm as rm5 } from "node:fs/promises";
|
|
4066
|
+
import { join as join12, relative, resolve as resolve8 } from "node:path";
|
|
3894
4067
|
import { parse as parseYaml5, stringify as stringifyYaml4 } from "yaml";
|
|
3895
4068
|
|
|
3896
4069
|
// ../core/dist/state/baseline.js
|
|
3897
|
-
import { readFile as
|
|
3898
|
-
import { join as
|
|
4070
|
+
import { readFile as readFile8 } from "node:fs/promises";
|
|
4071
|
+
import { join as join9 } from "node:path";
|
|
3899
4072
|
async function readBaseline(projectRoot) {
|
|
3900
|
-
const content = await
|
|
4073
|
+
const content = await readFile8(join9(stateLayout(projectRoot).baseline, "manifest.json"), "utf8");
|
|
3901
4074
|
return baselineManifestSchema.parse(JSON.parse(content));
|
|
3902
4075
|
}
|
|
3903
4076
|
|
|
3904
4077
|
// ../core/dist/state/locks.js
|
|
3905
4078
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
3906
|
-
import { readFile as
|
|
3907
|
-
import { join as
|
|
4079
|
+
import { readFile as readFile9, rename as rename4, rm as rm3, writeFile as writeFile6 } from "node:fs/promises";
|
|
4080
|
+
import { join as join10 } from "node:path";
|
|
3908
4081
|
async function readLock(path) {
|
|
3909
|
-
return JSON.parse(await
|
|
4082
|
+
return JSON.parse(await readFile9(path, "utf8"));
|
|
3910
4083
|
}
|
|
3911
4084
|
async function acquireProtocolLock(projectRoot, operation, options = {}) {
|
|
3912
4085
|
const layout = await ensureStateLayout(projectRoot);
|
|
3913
|
-
const lockPath =
|
|
4086
|
+
const lockPath = join10(layout.locks, "protocol.lock");
|
|
3914
4087
|
const now = options.now ?? Date.now();
|
|
3915
4088
|
const staleAfterMs = options.staleAfterMs ?? 15 * 60 * 1e3;
|
|
3916
4089
|
const record = {
|
|
@@ -3923,7 +4096,7 @@ async function acquireProtocolLock(projectRoot, operation, options = {}) {
|
|
|
3923
4096
|
};
|
|
3924
4097
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
3925
4098
|
try {
|
|
3926
|
-
await
|
|
4099
|
+
await writeFile6(lockPath, JSON.stringify(record, null, 2), { flag: "wx" });
|
|
3927
4100
|
return {
|
|
3928
4101
|
path: lockPath,
|
|
3929
4102
|
operation,
|
|
@@ -3945,15 +4118,15 @@ async function acquireProtocolLock(projectRoot, operation, options = {}) {
|
|
|
3945
4118
|
if (now - current.heartbeat_at_ms <= staleAfterMs) {
|
|
3946
4119
|
throw new Error("protocol lock is active for operation " + current.operation, { cause: error });
|
|
3947
4120
|
}
|
|
3948
|
-
await
|
|
4121
|
+
await rename4(lockPath, lockPath + ".stale-" + randomUUID3());
|
|
3949
4122
|
}
|
|
3950
4123
|
}
|
|
3951
4124
|
throw new Error("unable to acquire protocol lock");
|
|
3952
4125
|
}
|
|
3953
4126
|
|
|
3954
4127
|
// ../core/dist/sync/synchronize.js
|
|
3955
|
-
import { lstat as lstat2, readFile as
|
|
3956
|
-
import { join as
|
|
4128
|
+
import { lstat as lstat2, readFile as readFile10, rm as rm4 } from "node:fs/promises";
|
|
4129
|
+
import { join as join11, resolve as resolve7 } from "node:path";
|
|
3957
4130
|
|
|
3958
4131
|
// ../core/dist/update/conflicts.js
|
|
3959
4132
|
function operationTargetPath(operation) {
|
|
@@ -4235,8 +4408,8 @@ async function pathExists(path) {
|
|
|
4235
4408
|
}
|
|
4236
4409
|
}
|
|
4237
4410
|
async function optionalContent(root, path) {
|
|
4238
|
-
const full =
|
|
4239
|
-
return await pathExists(full) ?
|
|
4411
|
+
const full = join11(root, path);
|
|
4412
|
+
return await pathExists(full) ? readFile10(full, "utf8") : null;
|
|
4240
4413
|
}
|
|
4241
4414
|
function manifestPayloadHash(manifest) {
|
|
4242
4415
|
const payload = { ...manifest };
|
|
@@ -4248,10 +4421,10 @@ async function loadBlob(root, client, artifactId, operation, requestId, dryRun)
|
|
|
4248
4421
|
return null;
|
|
4249
4422
|
}
|
|
4250
4423
|
const hash = operation.content_sha256;
|
|
4251
|
-
const cacheRoot =
|
|
4252
|
-
const cachePath =
|
|
4424
|
+
const cacheRoot = join11(root, ".harness", "cache", "server-artifacts", artifactId);
|
|
4425
|
+
const cachePath = join11(cacheRoot, "blobs", hash.replace(":", "_"));
|
|
4253
4426
|
if (await pathExists(cachePath) && await sha256File(cachePath) === hash) {
|
|
4254
|
-
return
|
|
4427
|
+
return readFile10(cachePath, "utf8");
|
|
4255
4428
|
}
|
|
4256
4429
|
const bytes = await client.downloadArtifactBlob(artifactId, hash, requestId);
|
|
4257
4430
|
if (bytes.byteLength !== operation.size_bytes || sha256Bytes(bytes) !== hash) {
|
|
@@ -4356,7 +4529,7 @@ async function planSingleArtifact(root, baseline, manifest, client, requestId, d
|
|
|
4356
4529
|
});
|
|
4357
4530
|
}
|
|
4358
4531
|
async function saveConflictReport(root, requestId, manifest, plan) {
|
|
4359
|
-
const reportPath =
|
|
4532
|
+
const reportPath = join11(root, ".harness", "reports", "conflicts-" + requestId + ".json");
|
|
4360
4533
|
await atomicWriteJson(reportPath, {
|
|
4361
4534
|
schema_version: 1,
|
|
4362
4535
|
request_id: requestId,
|
|
@@ -4367,7 +4540,7 @@ async function saveConflictReport(root, requestId, manifest, plan) {
|
|
|
4367
4540
|
});
|
|
4368
4541
|
}
|
|
4369
4542
|
async function synchronizeArtifacts(options, initialBaseline) {
|
|
4370
|
-
const root =
|
|
4543
|
+
const root = resolve7(options.projectRoot);
|
|
4371
4544
|
const projectId = options.project.project.project_id;
|
|
4372
4545
|
if (projectId === null) {
|
|
4373
4546
|
throw new Error("PROJECT_NOT_BOUND");
|
|
@@ -4474,7 +4647,7 @@ async function synchronizeArtifacts(options, initialBaseline) {
|
|
|
4474
4647
|
}
|
|
4475
4648
|
continue;
|
|
4476
4649
|
}
|
|
4477
|
-
await atomicWriteJson(
|
|
4650
|
+
await atomicWriteJson(join11(root, ".harness", "cache", "server-artifacts", manifest.artifact_id, "manifest.json"), manifest);
|
|
4478
4651
|
const lock = await acquireProtocolLock(root, "update", { requestId: options.requestId });
|
|
4479
4652
|
try {
|
|
4480
4653
|
const nextBaseline = applyBaselineUpdates(baseline, plan);
|
|
@@ -4544,7 +4717,7 @@ function baselineEntryFromOperation(manifest, operation, localContent) {
|
|
|
4544
4717
|
};
|
|
4545
4718
|
}
|
|
4546
4719
|
async function advanceBaselineFromArtifact(options, baseline) {
|
|
4547
|
-
const root =
|
|
4720
|
+
const root = resolve7(options.projectRoot);
|
|
4548
4721
|
if (options.manifest.project_version === null) {
|
|
4549
4722
|
throw new Error("artifact manifest missing project_version");
|
|
4550
4723
|
}
|
|
@@ -4621,7 +4794,8 @@ var PushWorkflowError = class extends Error {
|
|
|
4621
4794
|
};
|
|
4622
4795
|
var SHARED_MANAGED_ROOTS = [
|
|
4623
4796
|
".harness/knowledge",
|
|
4624
|
-
".harness/codebase"
|
|
4797
|
+
".harness/codebase",
|
|
4798
|
+
".harness/rules"
|
|
4625
4799
|
];
|
|
4626
4800
|
var SHARED_MANAGED_FILES = [
|
|
4627
4801
|
"AGENTS.md",
|
|
@@ -4644,8 +4818,8 @@ async function walkFiles(root, current, output) {
|
|
|
4644
4818
|
if (!await exists3(current)) {
|
|
4645
4819
|
return;
|
|
4646
4820
|
}
|
|
4647
|
-
for (const item2 of await
|
|
4648
|
-
const path =
|
|
4821
|
+
for (const item2 of await readdir5(current, { withFileTypes: true })) {
|
|
4822
|
+
const path = join12(current, item2.name);
|
|
4649
4823
|
if (item2.isSymbolicLink()) {
|
|
4650
4824
|
throw new PushWorkflowError("symlink is not pushable", 6, "UNSAFE_SYMLINK");
|
|
4651
4825
|
}
|
|
@@ -4657,16 +4831,16 @@ async function walkFiles(root, current, output) {
|
|
|
4657
4831
|
}
|
|
4658
4832
|
}
|
|
4659
4833
|
async function walkArchiveSummaries(root, output) {
|
|
4660
|
-
const archiveRoot =
|
|
4834
|
+
const archiveRoot = join12(root, ".harness", "archive");
|
|
4661
4835
|
if (!await exists3(archiveRoot))
|
|
4662
4836
|
return;
|
|
4663
|
-
for (const item2 of await
|
|
4837
|
+
for (const item2 of await readdir5(archiveRoot, { withFileTypes: true })) {
|
|
4664
4838
|
if (item2.isSymbolicLink()) {
|
|
4665
4839
|
throw new PushWorkflowError("symlink is not pushable", 6, "UNSAFE_SYMLINK");
|
|
4666
4840
|
}
|
|
4667
4841
|
if (!item2.isDirectory())
|
|
4668
4842
|
continue;
|
|
4669
|
-
const summaryPath =
|
|
4843
|
+
const summaryPath = join12(archiveRoot, item2.name, "reports", "final", "summary-data.json");
|
|
4670
4844
|
try {
|
|
4671
4845
|
const stats = await lstat3(summaryPath);
|
|
4672
4846
|
if (stats.isSymbolicLink()) {
|
|
@@ -4694,9 +4868,9 @@ function enabledHarnessAgents(project) {
|
|
|
4694
4868
|
async function walkHarnessEntries(root, directory, output) {
|
|
4695
4869
|
if (!await exists3(directory))
|
|
4696
4870
|
return;
|
|
4697
|
-
for (const item2 of await
|
|
4871
|
+
for (const item2 of await readdir5(directory, { withFileTypes: true })) {
|
|
4698
4872
|
if (item2.name.startsWith("harness-")) {
|
|
4699
|
-
const path =
|
|
4873
|
+
const path = join12(directory, item2.name);
|
|
4700
4874
|
if (item2.isDirectory()) {
|
|
4701
4875
|
await walkFiles(root, path, output);
|
|
4702
4876
|
} else if (item2.isFile()) {
|
|
@@ -4706,7 +4880,7 @@ async function walkHarnessEntries(root, directory, output) {
|
|
|
4706
4880
|
}
|
|
4707
4881
|
}
|
|
4708
4882
|
async function managedFiles(projectRoot, project) {
|
|
4709
|
-
const root =
|
|
4883
|
+
const root = resolve8(projectRoot);
|
|
4710
4884
|
const paths = [];
|
|
4711
4885
|
const adapters = getAdapters(enabledHarnessAgents(project));
|
|
4712
4886
|
const managedFiles2 = [
|
|
@@ -4715,26 +4889,26 @@ async function managedFiles(projectRoot, project) {
|
|
|
4715
4889
|
...adapters.some((adapter) => adapter.name === "codebuddy") ? ["CODEBUDDY.md"] : []
|
|
4716
4890
|
];
|
|
4717
4891
|
for (const path of managedFiles2) {
|
|
4718
|
-
if (await exists3(
|
|
4892
|
+
if (await exists3(join12(root, path))) {
|
|
4719
4893
|
paths.push(path);
|
|
4720
4894
|
}
|
|
4721
4895
|
}
|
|
4722
4896
|
for (const path of SHARED_MANAGED_ROOTS) {
|
|
4723
|
-
await walkFiles(root,
|
|
4897
|
+
await walkFiles(root, join12(root, path), paths);
|
|
4724
4898
|
}
|
|
4725
4899
|
await walkArchiveSummaries(root, paths);
|
|
4726
4900
|
for (const adapter of adapters) {
|
|
4727
4901
|
if (adapter.rulesRoot !== null) {
|
|
4728
|
-
await
|
|
4902
|
+
await walkHarnessEntries(root, join12(root, adapter.rulesRoot), paths);
|
|
4729
4903
|
}
|
|
4730
|
-
await walkHarnessEntries(root,
|
|
4904
|
+
await walkHarnessEntries(root, join12(root, adapter.skillsRoot), paths);
|
|
4731
4905
|
if (adapter.agentsRoot !== null) {
|
|
4732
|
-
await walkHarnessEntries(root,
|
|
4906
|
+
await walkHarnessEntries(root, join12(root, adapter.agentsRoot), paths);
|
|
4733
4907
|
}
|
|
4734
4908
|
}
|
|
4735
4909
|
const result = {};
|
|
4736
4910
|
for (const path of [...new Set(paths)].sort()) {
|
|
4737
|
-
result[path] = await
|
|
4911
|
+
result[path] = await readFile11(join12(root, path), "utf8");
|
|
4738
4912
|
}
|
|
4739
4913
|
return result;
|
|
4740
4914
|
}
|
|
@@ -4743,14 +4917,14 @@ function proposalBaseline(baseline) {
|
|
|
4743
4917
|
}
|
|
4744
4918
|
async function readProject(root) {
|
|
4745
4919
|
try {
|
|
4746
|
-
return projectConfigSchema.parse(parseYaml5(await
|
|
4920
|
+
return projectConfigSchema.parse(parseYaml5(await readFile11(join12(root, ".harness", "project.yaml"), "utf8")));
|
|
4747
4921
|
} catch {
|
|
4748
4922
|
throw new PushWorkflowError("project configuration is missing or invalid", 3, "PROJECT_CONFIG_INVALID");
|
|
4749
4923
|
}
|
|
4750
4924
|
}
|
|
4751
4925
|
async function readOptionalJson(path) {
|
|
4752
4926
|
try {
|
|
4753
|
-
return JSON.parse(await
|
|
4927
|
+
return JSON.parse(await readFile11(path, "utf8"));
|
|
4754
4928
|
} catch (error) {
|
|
4755
4929
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
4756
4930
|
return null;
|
|
@@ -4762,7 +4936,7 @@ async function clientIdFor(root, explicit) {
|
|
|
4762
4936
|
if (explicit !== void 0) {
|
|
4763
4937
|
return explicit;
|
|
4764
4938
|
}
|
|
4765
|
-
const path =
|
|
4939
|
+
const path = join12(root, ".harness", "state", "local", "client.json");
|
|
4766
4940
|
const existing = await readOptionalJson(path);
|
|
4767
4941
|
if (typeof existing?.client_id === "string" && /^cli_[A-Za-z0-9_-]+$/.test(existing.client_id)) {
|
|
4768
4942
|
return existing.client_id;
|
|
@@ -4958,7 +5132,7 @@ async function bindProject(root, project, baseline, projectId) {
|
|
|
4958
5132
|
return { project: nextProject, baseline: nextBaseline };
|
|
4959
5133
|
}
|
|
4960
5134
|
async function pushProject(options) {
|
|
4961
|
-
const root =
|
|
5135
|
+
const root = resolve8(options.projectRoot);
|
|
4962
5136
|
let project = await readProject(root);
|
|
4963
5137
|
let baseline = await readBaseline(root);
|
|
4964
5138
|
const profile = parseHarnessProfile(project.project.profiles[0]);
|
|
@@ -5024,7 +5198,7 @@ async function pushProject(options) {
|
|
|
5024
5198
|
if (options.confirmProposal !== void 0 && !await options.confirmProposal(preview)) {
|
|
5025
5199
|
return { preview, proposalId: null, projectId: project.project.project_id, cancelled: true };
|
|
5026
5200
|
}
|
|
5027
|
-
const workflowPath =
|
|
5201
|
+
const workflowPath = join12(root, ".harness", "state", "local", "push-workflow.json");
|
|
5028
5202
|
const priorWorkflow = await readOptionalJson(workflowPath);
|
|
5029
5203
|
const provisionalRequestId = priorWorkflow?.local_project_key === project.project.local_project_key ? priorWorkflow.request_id : uuidV7();
|
|
5030
5204
|
const lock = await acquireProtocolLock(root, "push", { requestId: provisionalRequestId });
|
|
@@ -5179,7 +5353,7 @@ async function pushProject(options) {
|
|
|
5179
5353
|
pushWarning = "BASELINE_ADVANCE_DEFERRED";
|
|
5180
5354
|
}
|
|
5181
5355
|
}
|
|
5182
|
-
await atomicWriteJson(
|
|
5356
|
+
await atomicWriteJson(join12(root, ".harness", "state", "local", "push-results", finalized.proposal_id + ".json"), {
|
|
5183
5357
|
schema_version: 1,
|
|
5184
5358
|
request_id: requestId,
|
|
5185
5359
|
project_id: projectId,
|
|
@@ -5224,14 +5398,14 @@ async function pushProject(options) {
|
|
|
5224
5398
|
}
|
|
5225
5399
|
|
|
5226
5400
|
// ../core/dist/state/cleanup.js
|
|
5227
|
-
import { readFile as
|
|
5228
|
-
import { join as
|
|
5401
|
+
import { readFile as readFile12, readdir as readdir6, rm as rm6 } from "node:fs/promises";
|
|
5402
|
+
import { join as join13 } from "node:path";
|
|
5229
5403
|
function isSafeEntryName(name) {
|
|
5230
5404
|
return name.length > 0 && !name.includes("/") && !name.includes("\\") && name !== "." && name !== ".." && !name.includes("\0");
|
|
5231
5405
|
}
|
|
5232
5406
|
async function listDir(path) {
|
|
5233
5407
|
try {
|
|
5234
|
-
return await
|
|
5408
|
+
return await readdir6(path);
|
|
5235
5409
|
} catch (error) {
|
|
5236
5410
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
5237
5411
|
return [];
|
|
@@ -5249,7 +5423,7 @@ async function cleanupProject(options) {
|
|
|
5249
5423
|
continue;
|
|
5250
5424
|
let journal;
|
|
5251
5425
|
try {
|
|
5252
|
-
journal = JSON.parse(await
|
|
5426
|
+
journal = JSON.parse(await readFile12(join13(layout.transactions, name, "journal.json"), "utf8"));
|
|
5253
5427
|
} catch {
|
|
5254
5428
|
continue;
|
|
5255
5429
|
}
|
|
@@ -5267,7 +5441,7 @@ async function cleanupProject(options) {
|
|
|
5267
5441
|
continue;
|
|
5268
5442
|
pruned.push(entry.id);
|
|
5269
5443
|
if (!options.dryRun) {
|
|
5270
|
-
await rm6(
|
|
5444
|
+
await rm6(join13(layout.transactions, entry.id), { recursive: true, force: true });
|
|
5271
5445
|
}
|
|
5272
5446
|
}
|
|
5273
5447
|
}
|
|
@@ -5276,7 +5450,7 @@ async function cleanupProject(options) {
|
|
|
5276
5450
|
continue;
|
|
5277
5451
|
removedCache.push(name);
|
|
5278
5452
|
if (!options.dryRun) {
|
|
5279
|
-
await rm6(
|
|
5453
|
+
await rm6(join13(layout.serverArtifacts, name), { recursive: true, force: true });
|
|
5280
5454
|
}
|
|
5281
5455
|
}
|
|
5282
5456
|
return {
|
|
@@ -5336,10 +5510,10 @@ var SKILL_TARGET_AGENTS = Object.freeze([
|
|
|
5336
5510
|
]);
|
|
5337
5511
|
|
|
5338
5512
|
// ../core/dist/transaction/recovery.js
|
|
5339
|
-
import { readFile as
|
|
5340
|
-
import { join as
|
|
5513
|
+
import { readFile as readFile13, readdir as readdir7, rm as rm7, stat as stat3 } from "node:fs/promises";
|
|
5514
|
+
import { join as join14 } from "node:path";
|
|
5341
5515
|
async function recoverTransaction(projectRoot, transactionId) {
|
|
5342
|
-
const journal = JSON.parse(await
|
|
5516
|
+
const journal = JSON.parse(await readFile13(join14(stateLayout(projectRoot).transactions, transactionId, "journal.json"), "utf8"));
|
|
5343
5517
|
if (journal.state === "committed") {
|
|
5344
5518
|
return { transactionId, status: "committed" };
|
|
5345
5519
|
}
|
|
@@ -5356,7 +5530,7 @@ async function listTransactions(projectRoot) {
|
|
|
5356
5530
|
const root = stateLayout(projectRoot).transactions;
|
|
5357
5531
|
let names;
|
|
5358
5532
|
try {
|
|
5359
|
-
names = await
|
|
5533
|
+
names = await readdir7(root);
|
|
5360
5534
|
} catch (error) {
|
|
5361
5535
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
5362
5536
|
return [];
|
|
@@ -5366,7 +5540,7 @@ async function listTransactions(projectRoot) {
|
|
|
5366
5540
|
const transactions = [];
|
|
5367
5541
|
for (const transactionId of names) {
|
|
5368
5542
|
try {
|
|
5369
|
-
const journal = JSON.parse(await
|
|
5543
|
+
const journal = JSON.parse(await readFile13(join14(root, transactionId, "journal.json"), "utf8"));
|
|
5370
5544
|
transactions.push({
|
|
5371
5545
|
transactionId,
|
|
5372
5546
|
kind: journal.kind,
|
|
@@ -5397,11 +5571,11 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
|
|
|
5397
5571
|
if (latest === void 0) {
|
|
5398
5572
|
throw new Error("no committed update transaction is available for rollback");
|
|
5399
5573
|
}
|
|
5400
|
-
const transactionRoot =
|
|
5401
|
-
const journal = JSON.parse(await
|
|
5402
|
-
const after = JSON.parse(await
|
|
5574
|
+
const transactionRoot = join14(stateLayout(projectRoot).transactions, latest.transactionId);
|
|
5575
|
+
const journal = JSON.parse(await readFile13(join14(transactionRoot, "journal.json"), "utf8"));
|
|
5576
|
+
const after = JSON.parse(await readFile13(join14(transactionRoot, "after", "manifest.json"), "utf8"));
|
|
5403
5577
|
for (const entry of after) {
|
|
5404
|
-
const target =
|
|
5578
|
+
const target = join14(projectRoot, entry.path);
|
|
5405
5579
|
const exists6 = await pathExists2(target);
|
|
5406
5580
|
if (exists6 !== entry.exists || exists6 && await sha256File(target) !== entry.hash) {
|
|
5407
5581
|
throw new Error("cannot rollback dirty path: " + entry.path);
|
|
@@ -5414,10 +5588,10 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
|
|
|
5414
5588
|
continue;
|
|
5415
5589
|
}
|
|
5416
5590
|
seen.add(snapshot.path);
|
|
5417
|
-
const target =
|
|
5591
|
+
const target = join14(projectRoot, snapshot.path);
|
|
5418
5592
|
const exists6 = await pathExists2(target);
|
|
5419
5593
|
if (snapshot.existed && snapshot.snapshot_name !== null) {
|
|
5420
|
-
const content = await
|
|
5594
|
+
const content = await readFile13(join14(transactionRoot, "before", snapshot.snapshot_name));
|
|
5421
5595
|
operations.push({
|
|
5422
5596
|
operation: exists6 ? "modify" : "add",
|
|
5423
5597
|
path: snapshot.path,
|
|
@@ -5446,7 +5620,7 @@ async function cleanupOldTransactions(projectRoot, now = /* @__PURE__ */ new Dat
|
|
|
5446
5620
|
if (!removable) {
|
|
5447
5621
|
continue;
|
|
5448
5622
|
}
|
|
5449
|
-
await rm7(
|
|
5623
|
+
await rm7(join14(stateLayout(projectRoot).transactions, item2.transactionId), {
|
|
5450
5624
|
recursive: true,
|
|
5451
5625
|
force: true
|
|
5452
5626
|
});
|
|
@@ -5456,8 +5630,8 @@ async function cleanupOldTransactions(projectRoot, now = /* @__PURE__ */ new Dat
|
|
|
5456
5630
|
}
|
|
5457
5631
|
|
|
5458
5632
|
// ../core/dist/update/update.js
|
|
5459
|
-
import { readFile as
|
|
5460
|
-
import { join as
|
|
5633
|
+
import { readFile as readFile14 } from "node:fs/promises";
|
|
5634
|
+
import { join as join15, resolve as resolve9 } from "node:path";
|
|
5461
5635
|
import { parse as parseYaml8 } from "yaml";
|
|
5462
5636
|
var UpdateWorkflowError = class extends Error {
|
|
5463
5637
|
exitCode;
|
|
@@ -5470,10 +5644,10 @@ var UpdateWorkflowError = class extends Error {
|
|
|
5470
5644
|
}
|
|
5471
5645
|
};
|
|
5472
5646
|
async function updateProject(options) {
|
|
5473
|
-
const root =
|
|
5647
|
+
const root = resolve9(options.projectRoot);
|
|
5474
5648
|
let project;
|
|
5475
5649
|
try {
|
|
5476
|
-
project = projectConfigSchema.parse(parseYaml8(await
|
|
5650
|
+
project = projectConfigSchema.parse(parseYaml8(await readFile14(join15(root, ".harness", "project.yaml"), "utf8")));
|
|
5477
5651
|
} catch {
|
|
5478
5652
|
throw new UpdateWorkflowError("project configuration is invalid", 3, "PROJECT_CONFIG_INVALID");
|
|
5479
5653
|
}
|
|
@@ -5523,6 +5697,13 @@ async function updateProject(options) {
|
|
|
5523
5697
|
...options.confirmConflictStrategy === void 0 ? {} : { confirmConflictStrategy: options.confirmConflictStrategy },
|
|
5524
5698
|
...options.transactionOptions === void 0 ? {} : { transactionOptions: options.transactionOptions }
|
|
5525
5699
|
}, baseline);
|
|
5700
|
+
if (!options.dryRun) {
|
|
5701
|
+
const agents = project.adapters.enabled.flatMap((agent) => {
|
|
5702
|
+
const parsed = harnessAgentSchema.safeParse(agent);
|
|
5703
|
+
return parsed.success ? [parsed.data] : [];
|
|
5704
|
+
});
|
|
5705
|
+
await synchronizeProjectRules(root, agents, project.adapter_options?.codebuddy?.surface ?? "both");
|
|
5706
|
+
}
|
|
5526
5707
|
return result;
|
|
5527
5708
|
} catch (error) {
|
|
5528
5709
|
if (error instanceof UpdateWorkflowError) {
|
|
@@ -5548,8 +5729,8 @@ async function updateProject(options) {
|
|
|
5548
5729
|
}
|
|
5549
5730
|
|
|
5550
5731
|
// src/config/init-config.ts
|
|
5551
|
-
import { isAbsolute as isAbsolute2, join as
|
|
5552
|
-
import { readFile as
|
|
5732
|
+
import { isAbsolute as isAbsolute2, join as join16 } from "node:path";
|
|
5733
|
+
import { readFile as readFile15 } from "node:fs/promises";
|
|
5553
5734
|
var InitConfigurationError = class extends Error {
|
|
5554
5735
|
exitCode;
|
|
5555
5736
|
code;
|
|
@@ -5632,9 +5813,9 @@ function hasOwn(record, key) {
|
|
|
5632
5813
|
async function resolveInitConfig(cwd, flags, prompts = {}, warnings = []) {
|
|
5633
5814
|
let fileConfig = {};
|
|
5634
5815
|
if (flags.config !== void 0) {
|
|
5635
|
-
const path = isAbsolute2(flags.config) ? flags.config :
|
|
5816
|
+
const path = isAbsolute2(flags.config) ? flags.config : join16(cwd, flags.config);
|
|
5636
5817
|
try {
|
|
5637
|
-
fileConfig = JSON.parse(await
|
|
5818
|
+
fileConfig = JSON.parse(await readFile15(path, "utf8"));
|
|
5638
5819
|
} catch (error) {
|
|
5639
5820
|
throw new InitConfigurationError(
|
|
5640
5821
|
"unable to read init config: " + (error instanceof Error ? error.message : String(error)),
|
|
@@ -5716,8 +5897,8 @@ function serializeCliResult(result) {
|
|
|
5716
5897
|
}
|
|
5717
5898
|
|
|
5718
5899
|
// src/config/codebuddy-setup.ts
|
|
5719
|
-
import { mkdir as
|
|
5720
|
-
import { basename as
|
|
5900
|
+
import { mkdir as mkdir5, readFile as readFile16, readdir as readdir8, stat as stat4, writeFile as writeFile7 } from "node:fs/promises";
|
|
5901
|
+
import { basename as basename3, dirname as dirname5, extname as extname2, join as join17 } from "node:path";
|
|
5721
5902
|
var MANAGED_RULE_NAMES = /* @__PURE__ */ new Set([
|
|
5722
5903
|
"harness-general.md",
|
|
5723
5904
|
"harness-general.mdc",
|
|
@@ -5736,35 +5917,59 @@ async function exists4(path) {
|
|
|
5736
5917
|
}
|
|
5737
5918
|
async function readJsonObject(path) {
|
|
5738
5919
|
try {
|
|
5739
|
-
const parsed = JSON.parse(await
|
|
5920
|
+
const parsed = JSON.parse(await readFile16(path, "utf8"));
|
|
5740
5921
|
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
5741
5922
|
} catch (error) {
|
|
5742
5923
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") return {};
|
|
5743
5924
|
return null;
|
|
5744
5925
|
}
|
|
5745
5926
|
}
|
|
5746
|
-
async function inspectCodeBuddySetup(projectRoot) {
|
|
5747
|
-
const rulesRoot =
|
|
5748
|
-
let
|
|
5927
|
+
async function inspectCodeBuddySetup(projectRoot, surface = "both") {
|
|
5928
|
+
const rulesRoot = join17(projectRoot, ".claude", "rules");
|
|
5929
|
+
let ruleNames = [];
|
|
5749
5930
|
try {
|
|
5750
|
-
|
|
5931
|
+
ruleNames = (await readdir8(rulesRoot, { withFileTypes: true })).filter((entry) => entry.isFile() && [".md", ".mdc"].includes(extname2(entry.name).toLowerCase())).map((entry) => entry.name).filter((name) => !MANAGED_RULE_NAMES.has(name)).sort();
|
|
5751
5932
|
} catch (error) {
|
|
5752
5933
|
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
|
|
5753
5934
|
}
|
|
5754
|
-
const
|
|
5935
|
+
const claudeRules = [];
|
|
5936
|
+
const currentClaudeRules = [];
|
|
5937
|
+
const conflictingClaudeRules = [];
|
|
5938
|
+
for (const name of ruleNames) {
|
|
5939
|
+
const sourceContent = await readFile16(join17(rulesRoot, name), "utf8");
|
|
5940
|
+
const targetContents = await Promise.all(
|
|
5941
|
+
destinationTargets(projectRoot, surface, name).map(async (target) => {
|
|
5942
|
+
try {
|
|
5943
|
+
return await readFile16(target, "utf8");
|
|
5944
|
+
} catch (error) {
|
|
5945
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return null;
|
|
5946
|
+
throw error;
|
|
5947
|
+
}
|
|
5948
|
+
})
|
|
5949
|
+
);
|
|
5950
|
+
if (targetContents.some((content) => content === null)) claudeRules.push(name);
|
|
5951
|
+
if (targetContents.some((content) => content !== null && content !== sourceContent)) {
|
|
5952
|
+
conflictingClaudeRules.push(name);
|
|
5953
|
+
} else if (targetContents.length > 0 && targetContents.every((content) => content === sourceContent)) {
|
|
5954
|
+
currentClaudeRules.push(name);
|
|
5955
|
+
}
|
|
5956
|
+
}
|
|
5957
|
+
const mcp = await readJsonObject(join17(projectRoot, ".mcp.json"));
|
|
5755
5958
|
const servers = mcp?.mcpServers;
|
|
5756
5959
|
const configured = servers !== null && typeof servers === "object" && !Array.isArray(servers) && Object.prototype.hasOwnProperty.call(servers, "codegraph");
|
|
5757
5960
|
return {
|
|
5758
5961
|
claudeRules,
|
|
5759
|
-
|
|
5962
|
+
currentClaudeRules,
|
|
5963
|
+
conflictingClaudeRules,
|
|
5964
|
+
hasCodeGraphIndex: await exists4(join17(projectRoot, ".codegraph")),
|
|
5760
5965
|
codeGraphConfigured: configured
|
|
5761
5966
|
};
|
|
5762
5967
|
}
|
|
5763
5968
|
function destinationTargets(root, surface, name) {
|
|
5764
|
-
const stem =
|
|
5969
|
+
const stem = basename3(name, extname2(name));
|
|
5765
5970
|
const targets = [];
|
|
5766
|
-
if (surface !== "cli") targets.push(
|
|
5767
|
-
if (surface !== "ide") targets.push(
|
|
5971
|
+
if (surface !== "cli") targets.push(join17(root, ".codebuddy", ".rules", `${stem}.mdc`));
|
|
5972
|
+
if (surface !== "ide") targets.push(join17(root, ".codebuddy", "rules", `${stem}.md`));
|
|
5768
5973
|
return targets;
|
|
5769
5974
|
}
|
|
5770
5975
|
async function applyCodeBuddySetup(options) {
|
|
@@ -5776,10 +5981,10 @@ async function applyCodeBuddySetup(options) {
|
|
|
5776
5981
|
warnings: []
|
|
5777
5982
|
};
|
|
5778
5983
|
if (options.syncClaudeRules) {
|
|
5779
|
-
const plan = await inspectCodeBuddySetup(options.projectRoot);
|
|
5984
|
+
const plan = await inspectCodeBuddySetup(options.projectRoot, options.surface);
|
|
5780
5985
|
for (const name of plan.claudeRules) {
|
|
5781
|
-
const source =
|
|
5782
|
-
const content = await
|
|
5986
|
+
const source = join17(options.projectRoot, ".claude", "rules", name);
|
|
5987
|
+
const content = await readFile16(source, "utf8");
|
|
5783
5988
|
if (SENSITIVE_ASSIGNMENT.test(content) || PRIVATE_KEY_BLOCK.test(content)) {
|
|
5784
5989
|
result.skippedSensitive.push(name);
|
|
5785
5990
|
continue;
|
|
@@ -5789,14 +5994,14 @@ async function applyCodeBuddySetup(options) {
|
|
|
5789
5994
|
result.preserved.push(target);
|
|
5790
5995
|
continue;
|
|
5791
5996
|
}
|
|
5792
|
-
await
|
|
5793
|
-
await
|
|
5997
|
+
await mkdir5(dirname5(target), { recursive: true });
|
|
5998
|
+
await writeFile7(target, content, { encoding: "utf8", flag: "wx" });
|
|
5794
5999
|
result.copied.push(target);
|
|
5795
6000
|
}
|
|
5796
6001
|
}
|
|
5797
6002
|
}
|
|
5798
6003
|
if (options.configureCodeGraph) {
|
|
5799
|
-
const path =
|
|
6004
|
+
const path = join17(options.projectRoot, ".mcp.json");
|
|
5800
6005
|
const current = await readJsonObject(path);
|
|
5801
6006
|
if (current === null) {
|
|
5802
6007
|
result.warnings.push(".mcp.json \u4E0D\u662F\u6709\u6548 JSON\uFF0C\u5DF2\u4FDD\u7559\u539F\u6587\u4EF6\u5E76\u8DF3\u8FC7 CodeGraph MCP \u914D\u7F6E");
|
|
@@ -5811,7 +6016,7 @@ async function applyCodeBuddySetup(options) {
|
|
|
5811
6016
|
codegraph: { command: "codegraph", args: ["serve", "--mcp"] }
|
|
5812
6017
|
}
|
|
5813
6018
|
};
|
|
5814
|
-
await
|
|
6019
|
+
await writeFile7(path, JSON.stringify(next, null, 2) + "\n", "utf8");
|
|
5815
6020
|
result.mcpUpdated = true;
|
|
5816
6021
|
}
|
|
5817
6022
|
}
|
|
@@ -5820,13 +6025,13 @@ async function applyCodeBuddySetup(options) {
|
|
|
5820
6025
|
}
|
|
5821
6026
|
|
|
5822
6027
|
// src/commands/refresh.ts
|
|
5823
|
-
import { readFile as
|
|
5824
|
-
import { join as
|
|
6028
|
+
import { readFile as readFile17 } from "node:fs/promises";
|
|
6029
|
+
import { join as join18 } from "node:path";
|
|
5825
6030
|
import { parse as parseYaml9 } from "yaml";
|
|
5826
6031
|
async function detectProject(root) {
|
|
5827
6032
|
let content;
|
|
5828
6033
|
try {
|
|
5829
|
-
content = await
|
|
6034
|
+
content = await readFile17(join18(root, ".harness", "project.yaml"), "utf8");
|
|
5830
6035
|
} catch (error) {
|
|
5831
6036
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
5832
6037
|
return { status: "absent" };
|
|
@@ -6055,7 +6260,13 @@ function agentMenuLines(installedProfiles) {
|
|
|
6055
6260
|
}
|
|
6056
6261
|
async function configureCodeBuddyExtras(agents, surface, options, dependencies) {
|
|
6057
6262
|
if (!agents.includes("codebuddy")) return;
|
|
6058
|
-
const plan = await inspectCodeBuddySetup(dependencies.cwd);
|
|
6263
|
+
const plan = await inspectCodeBuddySetup(dependencies.cwd, surface);
|
|
6264
|
+
if (plan.conflictingClaudeRules.length > 0) {
|
|
6265
|
+
dependencies.stderr(
|
|
6266
|
+
`\u4EE5\u4E0B CodeBuddy \u89C4\u5219\u4E0E Claude \u6E90\u89C4\u5219\u5185\u5BB9\u4E0D\u540C\uFF0C\u5DF2\u4FDD\u7559\u76EE\u6807\u6587\u4EF6\uFF1A${plan.conflictingClaudeRules.join(", ")}
|
|
6267
|
+
`
|
|
6268
|
+
);
|
|
6269
|
+
}
|
|
6059
6270
|
let syncClaudeRules = false;
|
|
6060
6271
|
let configureCodeGraph = false;
|
|
6061
6272
|
if (plan.claudeRules.length > 0) {
|
|
@@ -6654,7 +6865,7 @@ async function runUpdate(options, dependencies) {
|
|
|
6654
6865
|
|
|
6655
6866
|
// src/commands/recovery.ts
|
|
6656
6867
|
import { stat as stat5 } from "node:fs/promises";
|
|
6657
|
-
import { join as
|
|
6868
|
+
import { join as join19 } from "node:path";
|
|
6658
6869
|
async function exists5(path) {
|
|
6659
6870
|
try {
|
|
6660
6871
|
await stat5(path);
|
|
@@ -6714,7 +6925,7 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
|
|
|
6714
6925
|
return 5;
|
|
6715
6926
|
}
|
|
6716
6927
|
}
|
|
6717
|
-
const initialized = await exists5(
|
|
6928
|
+
const initialized = await exists5(join19(dependencies.cwd, ".harness", "project.yaml"));
|
|
6718
6929
|
if (!initialized || options.nonInteractive === true || explicitConfigure(options)) {
|
|
6719
6930
|
return null;
|
|
6720
6931
|
}
|
|
@@ -6757,8 +6968,8 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
|
|
|
6757
6968
|
}
|
|
6758
6969
|
|
|
6759
6970
|
// src/workflow-data/resolve.ts
|
|
6760
|
-
import { cp, mkdir as
|
|
6761
|
-
import { dirname as
|
|
6971
|
+
import { cp, mkdir as mkdir6, readdir as readdir9, readFile as readFile18, rm as rm8, stat as stat6 } from "node:fs/promises";
|
|
6972
|
+
import { dirname as dirname6, join as join20, relative as relative2 } from "node:path";
|
|
6762
6973
|
import { fileURLToPath } from "node:url";
|
|
6763
6974
|
var WorkflowDataResolutionError = class extends Error {
|
|
6764
6975
|
code;
|
|
@@ -6792,17 +7003,17 @@ function workflowPackageName(family, env) {
|
|
|
6792
7003
|
return `${scope}/workflow-${family}`;
|
|
6793
7004
|
}
|
|
6794
7005
|
async function listFilesRecursive(root, base = root) {
|
|
6795
|
-
const entries = await
|
|
7006
|
+
const entries = await readdir9(root, { withFileTypes: true });
|
|
6796
7007
|
const files = [];
|
|
6797
7008
|
for (const entry of entries) {
|
|
6798
|
-
const absolute =
|
|
7009
|
+
const absolute = join20(root, entry.name);
|
|
6799
7010
|
if (entry.isDirectory()) {
|
|
6800
7011
|
files.push(...await listFilesRecursive(absolute, base));
|
|
6801
7012
|
continue;
|
|
6802
7013
|
}
|
|
6803
7014
|
if (!entry.isFile()) continue;
|
|
6804
7015
|
const rel = relative2(base, absolute).replaceAll("\\", "/");
|
|
6805
|
-
files.push({ path: rel, content: await
|
|
7016
|
+
files.push({ path: rel, content: await readFile18(absolute, "utf8") });
|
|
6806
7017
|
}
|
|
6807
7018
|
return files;
|
|
6808
7019
|
}
|
|
@@ -6815,7 +7026,7 @@ async function verifyWorkflowPackageIntegrity(resourcesRoot) {
|
|
|
6815
7026
|
const manifest = await readWorkflowFamilyManifest(resourcesRoot);
|
|
6816
7027
|
const expected = manifest.content_sha256;
|
|
6817
7028
|
if (typeof expected !== "string" || expected.length === 0) return;
|
|
6818
|
-
const harnessRoot =
|
|
7029
|
+
const harnessRoot = join20(resourcesRoot, "harness");
|
|
6819
7030
|
if (!await pathExists3(harnessRoot)) {
|
|
6820
7031
|
throw new WorkflowDataResolutionError(
|
|
6821
7032
|
"\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u7F3A\u5C11 harness/\uFF0C\u65E0\u6CD5\u6821\u9A8C SHA-256",
|
|
@@ -6834,40 +7045,40 @@ async function verifyWorkflowPackageIntegrity(resourcesRoot) {
|
|
|
6834
7045
|
}
|
|
6835
7046
|
}
|
|
6836
7047
|
async function monorepoResourcesRoot() {
|
|
6837
|
-
const here =
|
|
7048
|
+
const here = dirname6(fileURLToPath(import.meta.url));
|
|
6838
7049
|
const candidates = [
|
|
6839
7050
|
// TypeScript source: packages/cli/src/workflow-data -> packages/workflow-data-harness.
|
|
6840
|
-
|
|
7051
|
+
join20(here, "../../../workflow-data-harness"),
|
|
6841
7052
|
// Bundled CLI: packages/cli/dist -> packages/workflow-data-harness.
|
|
6842
|
-
|
|
7053
|
+
join20(here, "../../workflow-data-harness"),
|
|
6843
7054
|
// Test/build layouts that preserve additional source directory levels.
|
|
6844
|
-
|
|
7055
|
+
join20(here, "../../../../packages/workflow-data-harness")
|
|
6845
7056
|
];
|
|
6846
7057
|
for (const candidate of candidates) {
|
|
6847
|
-
if (await pathExists3(
|
|
7058
|
+
if (await pathExists3(join20(candidate, "harness", "manifests"))) return candidate;
|
|
6848
7059
|
}
|
|
6849
7060
|
return null;
|
|
6850
7061
|
}
|
|
6851
7062
|
async function siblingWorkflowPackage(cwd) {
|
|
6852
|
-
const here =
|
|
7063
|
+
const here = dirname6(fileURLToPath(import.meta.url));
|
|
6853
7064
|
const candidates = [
|
|
6854
|
-
|
|
7065
|
+
join20(cwd, "node_modules", "@hunter-harness", "workflow-harness"),
|
|
6855
7066
|
// Published layout: node_modules/hunter-harness/dist/bin.js alongside the
|
|
6856
7067
|
// scoped workflow package in node_modules/@hunter-harness/workflow-harness.
|
|
6857
|
-
|
|
7068
|
+
join20(here, "..", "..", "@hunter-harness", "workflow-harness"),
|
|
6858
7069
|
// Monorepo bundled layout: packages/cli/dist/bin.js.
|
|
6859
|
-
|
|
7070
|
+
join20(here, "..", "..", "workflow-data-harness")
|
|
6860
7071
|
];
|
|
6861
7072
|
for (const candidate of candidates) {
|
|
6862
|
-
if (await pathExists3(
|
|
7073
|
+
if (await pathExists3(join20(candidate, "harness", "manifests"))) return candidate;
|
|
6863
7074
|
}
|
|
6864
7075
|
return null;
|
|
6865
7076
|
}
|
|
6866
7077
|
async function readCachedNpmPackageVersion(cacheRoot) {
|
|
6867
|
-
const manifestPath =
|
|
7078
|
+
const manifestPath = join20(cacheRoot, "package.json");
|
|
6868
7079
|
if (!await pathExists3(manifestPath)) return null;
|
|
6869
7080
|
try {
|
|
6870
|
-
const pkg = JSON.parse(await
|
|
7081
|
+
const pkg = JSON.parse(await readFile18(manifestPath, "utf8"));
|
|
6871
7082
|
return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : null;
|
|
6872
7083
|
} catch {
|
|
6873
7084
|
return null;
|
|
@@ -6889,13 +7100,13 @@ async function latestWorkflowCacheIsStale(cacheRoot, packageName, resolveManifes
|
|
|
6889
7100
|
}
|
|
6890
7101
|
}
|
|
6891
7102
|
async function materializeWorkflowPackageCache(cacheRoot, packageSpec, extract) {
|
|
6892
|
-
await
|
|
6893
|
-
const staging =
|
|
7103
|
+
await mkdir6(cacheRoot, { recursive: true });
|
|
7104
|
+
const staging = join20(cacheRoot, ".extract");
|
|
6894
7105
|
await rm8(staging, { recursive: true, force: true });
|
|
6895
|
-
await
|
|
7106
|
+
await mkdir6(staging, { recursive: true });
|
|
6896
7107
|
await extract(packageSpec, staging);
|
|
6897
|
-
const packageDir = await pathExists3(
|
|
6898
|
-
if (!await pathExists3(
|
|
7108
|
+
const packageDir = await pathExists3(join20(staging, "harness", "manifests")) ? staging : join20(staging, "package");
|
|
7109
|
+
if (!await pathExists3(join20(packageDir, "harness", "manifests"))) {
|
|
6899
7110
|
throw new WorkflowDataResolutionError(
|
|
6900
7111
|
"\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u5185\u5BB9\u65E0\u6548\uFF1A\u7F3A\u5C11 harness/manifests",
|
|
6901
7112
|
"WORKFLOW_DATA_INVALID",
|
|
@@ -6924,8 +7135,8 @@ async function resolveWorkflowResourcesRoot(options, argv = []) {
|
|
|
6924
7135
|
const packageName = workflowPackageName(family, options.env);
|
|
6925
7136
|
const packageSpec = version === "latest" ? packageName : `${packageName}@${version}`;
|
|
6926
7137
|
const cacheKey = packageSpec.replace("/", "+");
|
|
6927
|
-
const cacheRoot =
|
|
6928
|
-
if (await pathExists3(
|
|
7138
|
+
const cacheRoot = join20(options.cwd, ".harness", "cache", "workflow-packages", cacheKey);
|
|
7139
|
+
if (await pathExists3(join20(cacheRoot, "harness", "manifests"))) {
|
|
6929
7140
|
if (version === "latest") {
|
|
6930
7141
|
const stale = await latestWorkflowCacheIsStale(cacheRoot, packageName, options.pacoteManifest);
|
|
6931
7142
|
if (stale) {
|
|
@@ -6971,9 +7182,9 @@ function describeWorkflowDataFetchFailure(error, packageSpec) {
|
|
|
6971
7182
|
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}`;
|
|
6972
7183
|
}
|
|
6973
7184
|
async function readWorkflowFamilyManifest(resourcesRoot) {
|
|
6974
|
-
const manifestPath =
|
|
7185
|
+
const manifestPath = join20(resourcesRoot, "hunter-workflow-family.json");
|
|
6975
7186
|
if (!await pathExists3(manifestPath)) return {};
|
|
6976
|
-
return JSON.parse(await
|
|
7187
|
+
return JSON.parse(await readFile18(manifestPath, "utf8"));
|
|
6977
7188
|
}
|
|
6978
7189
|
|
|
6979
7190
|
// src/bin.ts
|