hunter-harness 0.2.27 → 0.2.29
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/README.md +28 -26
- package/dist/bin.js +790 -204
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -1216,8 +1216,8 @@ async function assertNoSymlinks(root, relativePath) {
|
|
|
1216
1216
|
for (const segment of normalized.split("/")) {
|
|
1217
1217
|
current = join(current, segment);
|
|
1218
1218
|
try {
|
|
1219
|
-
const
|
|
1220
|
-
if (
|
|
1219
|
+
const stat8 = await lstat(current);
|
|
1220
|
+
if (stat8.isSymbolicLink()) {
|
|
1221
1221
|
throw new UnsafePathError("symbolic links are not managed");
|
|
1222
1222
|
}
|
|
1223
1223
|
} catch (error) {
|
|
@@ -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((resolve11) => setTimeout(resolve11, 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,244 @@ 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
|
+
var AGENT_RULE_ROOTS = [
|
|
2267
|
+
".claude/rules",
|
|
2268
|
+
".cursor/rules",
|
|
2269
|
+
".codebuddy/.rules",
|
|
2270
|
+
".codebuddy/rules"
|
|
2271
|
+
];
|
|
2272
|
+
function sha256(content) {
|
|
2273
|
+
return createHash3("sha256").update(content).digest("hex");
|
|
2274
|
+
}
|
|
2275
|
+
async function optionalText(path) {
|
|
2276
|
+
try {
|
|
2277
|
+
return await readFile3(path, "utf8");
|
|
2278
|
+
} catch (error) {
|
|
2279
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
2280
|
+
return null;
|
|
2281
|
+
throw error;
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
async function readReceipt(root) {
|
|
2285
|
+
try {
|
|
2286
|
+
const parsed = JSON.parse(await readFile3(join4(root, RECEIPT_PATH), "utf8"));
|
|
2287
|
+
if (parsed.schema_version === 1 && parsed.targets && parsed.source_hashes)
|
|
2288
|
+
return parsed;
|
|
2289
|
+
} catch {
|
|
2290
|
+
}
|
|
2291
|
+
return { schema_version: 1, source_hashes: {}, targets: {} };
|
|
2292
|
+
}
|
|
2293
|
+
async function markdownFiles(root) {
|
|
2294
|
+
try {
|
|
2295
|
+
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();
|
|
2296
|
+
} catch (error) {
|
|
2297
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
2298
|
+
return [];
|
|
2299
|
+
throw error;
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
function portable(path) {
|
|
2303
|
+
return path.replaceAll("\\", "/");
|
|
2304
|
+
}
|
|
2305
|
+
function normalizeRuleContent(content) {
|
|
2306
|
+
return content.replace(/\r\n/g, "\n").trimEnd() + "\n";
|
|
2307
|
+
}
|
|
2308
|
+
function canonicalImportContent(content) {
|
|
2309
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
2310
|
+
if (!normalized.startsWith("---\n"))
|
|
2311
|
+
return normalizeRuleContent(normalized);
|
|
2312
|
+
const closing = normalized.indexOf("\n---\n", 4);
|
|
2313
|
+
if (closing < 0)
|
|
2314
|
+
return null;
|
|
2315
|
+
const frontmatter = normalized.slice(4, closing);
|
|
2316
|
+
if (/^\s*(?:globs?|paths?)\s*:/im.test(frontmatter) || /^\s*alwaysApply\s*:\s*false\s*$/im.test(frontmatter)) {
|
|
2317
|
+
return null;
|
|
2318
|
+
}
|
|
2319
|
+
return normalizeRuleContent(normalized.slice(closing + 5));
|
|
2320
|
+
}
|
|
2321
|
+
async function collectImportCandidates(root, previous, result) {
|
|
2322
|
+
const candidates = [];
|
|
2323
|
+
for (const ruleRoot of AGENT_RULE_ROOTS) {
|
|
2324
|
+
for (const name of await markdownFiles(join4(root, ...ruleRoot.split("/")))) {
|
|
2325
|
+
const source = portable(`${ruleRoot}/${name}`);
|
|
2326
|
+
const content = await readFile3(join4(root, ...source.split("/")), "utf8");
|
|
2327
|
+
if (previous.targets[source] === sha256(content))
|
|
2328
|
+
continue;
|
|
2329
|
+
const canonical = canonicalImportContent(content);
|
|
2330
|
+
if (canonical === null) {
|
|
2331
|
+
result.agent_specific.push(source);
|
|
2332
|
+
continue;
|
|
2333
|
+
}
|
|
2334
|
+
candidates.push({
|
|
2335
|
+
source,
|
|
2336
|
+
destination: `${RULES_ROOT}/${basename(name, extname(name))}.md`,
|
|
2337
|
+
content: canonical
|
|
2338
|
+
});
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
return candidates;
|
|
2342
|
+
}
|
|
2343
|
+
async function importAgentRules(root, canonicalRoot, previous, result) {
|
|
2344
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
2345
|
+
for (const candidate of await collectImportCandidates(root, previous, result)) {
|
|
2346
|
+
const values = grouped.get(candidate.destination) ?? [];
|
|
2347
|
+
values.push(candidate);
|
|
2348
|
+
grouped.set(candidate.destination, values);
|
|
2349
|
+
}
|
|
2350
|
+
for (const [destination, candidates] of [...grouped].sort(([a], [b]) => a.localeCompare(b))) {
|
|
2351
|
+
const destinationPath = join4(canonicalRoot, basename(destination));
|
|
2352
|
+
const current = await optionalText(destinationPath);
|
|
2353
|
+
const distinct = new Set(candidates.map((candidate) => candidate.content));
|
|
2354
|
+
const representative = candidates.at(0);
|
|
2355
|
+
if (representative === void 0)
|
|
2356
|
+
continue;
|
|
2357
|
+
if (current === null && distinct.size === 1) {
|
|
2358
|
+
await atomicWrite(destinationPath, representative.content);
|
|
2359
|
+
result.migrated.push(destination);
|
|
2360
|
+
continue;
|
|
2361
|
+
}
|
|
2362
|
+
if (current !== null && distinct.size === 1 && normalizeRuleContent(current) === representative.content) {
|
|
2363
|
+
continue;
|
|
2364
|
+
}
|
|
2365
|
+
result.conflicts.push(...candidates.map((candidate) => candidate.source));
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
function targetsFor(name, agents, surface) {
|
|
2369
|
+
const stem = basename(name, extname(name));
|
|
2370
|
+
const targets = [];
|
|
2371
|
+
if (agents.includes("claude-code"))
|
|
2372
|
+
targets.push(`.claude/rules/${stem}.md`);
|
|
2373
|
+
if (agents.includes("cursor"))
|
|
2374
|
+
targets.push(`.cursor/rules/${stem}.mdc`);
|
|
2375
|
+
if (agents.includes("codebuddy") && surface !== "cli") {
|
|
2376
|
+
targets.push(`.codebuddy/.rules/${stem}.mdc`);
|
|
2377
|
+
}
|
|
2378
|
+
if (agents.includes("codebuddy") && surface !== "ide") {
|
|
2379
|
+
targets.push(`.codebuddy/rules/${stem}.md`);
|
|
2380
|
+
}
|
|
2381
|
+
return targets;
|
|
2382
|
+
}
|
|
2383
|
+
async function atomicWrite(path, content) {
|
|
2384
|
+
await mkdir4(dirname3(path), { recursive: true });
|
|
2385
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
2386
|
+
await writeFile3(temporary, content, "utf8");
|
|
2387
|
+
await rename3(temporary, path);
|
|
2388
|
+
}
|
|
2389
|
+
async function synchronizeProjectRules(projectRoot, agents, surface = "both") {
|
|
2390
|
+
const root = resolve3(projectRoot);
|
|
2391
|
+
const canonicalRoot = join4(root, RULES_ROOT);
|
|
2392
|
+
const result = {
|
|
2393
|
+
migrated: [],
|
|
2394
|
+
agent_specific: [],
|
|
2395
|
+
written: [],
|
|
2396
|
+
removed: [],
|
|
2397
|
+
unchanged: [],
|
|
2398
|
+
conflicts: []
|
|
2399
|
+
};
|
|
2400
|
+
await mkdir4(canonicalRoot, { recursive: true });
|
|
2401
|
+
const previous = await readReceipt(root);
|
|
2402
|
+
await importAgentRules(root, canonicalRoot, previous, result);
|
|
2403
|
+
const next = { schema_version: 1, source_hashes: {}, targets: {} };
|
|
2404
|
+
const desired = /* @__PURE__ */ new Map();
|
|
2405
|
+
for (const name of await markdownFiles(canonicalRoot)) {
|
|
2406
|
+
const sourcePath = `${RULES_ROOT}/${name}`;
|
|
2407
|
+
const content = await readFile3(join4(canonicalRoot, name), "utf8");
|
|
2408
|
+
next.source_hashes[sourcePath] = sha256(content);
|
|
2409
|
+
for (const target of targetsFor(name, agents, surface))
|
|
2410
|
+
desired.set(target, content);
|
|
2411
|
+
}
|
|
2412
|
+
for (const [target, content] of desired) {
|
|
2413
|
+
const path = join4(root, target);
|
|
2414
|
+
const current = await optionalText(path);
|
|
2415
|
+
const incomingHash = sha256(content);
|
|
2416
|
+
const canonicalCurrent = current === null ? null : canonicalImportContent(current);
|
|
2417
|
+
if (current === content || canonicalCurrent === content) {
|
|
2418
|
+
result.unchanged.push(target);
|
|
2419
|
+
next.targets[target] = current === null ? incomingHash : sha256(current);
|
|
2420
|
+
} else if (current === null || previous.targets[target] === sha256(current)) {
|
|
2421
|
+
await atomicWrite(path, content);
|
|
2422
|
+
result.written.push(target);
|
|
2423
|
+
next.targets[target] = incomingHash;
|
|
2424
|
+
} else {
|
|
2425
|
+
result.conflicts.push(target);
|
|
2426
|
+
next.targets[target] = previous.targets[target] ?? sha256(current);
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
for (const [target, trustedHash] of Object.entries(previous.targets)) {
|
|
2430
|
+
if (target === "AGENTS.md")
|
|
2431
|
+
continue;
|
|
2432
|
+
if (desired.has(target))
|
|
2433
|
+
continue;
|
|
2434
|
+
const path = join4(root, target);
|
|
2435
|
+
const current = await optionalText(path);
|
|
2436
|
+
if (current === null)
|
|
2437
|
+
continue;
|
|
2438
|
+
if (sha256(current) === trustedHash) {
|
|
2439
|
+
await unlink(path);
|
|
2440
|
+
result.removed.push(target);
|
|
2441
|
+
} else {
|
|
2442
|
+
result.conflicts.push(target);
|
|
2443
|
+
next.targets[target] = trustedHash;
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
if (agents.includes("codex")) {
|
|
2447
|
+
const rules = Object.keys(next.source_hashes).sort();
|
|
2448
|
+
const body = [
|
|
2449
|
+
"Before project work, read and follow these shared project rules:",
|
|
2450
|
+
...rules.map((path) => `- \`${path}\``)
|
|
2451
|
+
].join("\n");
|
|
2452
|
+
const agentsPath = join4(root, "AGENTS.md");
|
|
2453
|
+
const current = await optionalText(agentsPath) ?? "";
|
|
2454
|
+
const updated = rules.length > 0 ? upsertManagedBlockById(current, CODEX_BLOCK_ID, body) : removeManagedBlockById(current, CODEX_BLOCK_ID);
|
|
2455
|
+
const target = "AGENTS.md";
|
|
2456
|
+
const semanticallyEqual = updated.replace(/\r\n/g, "\n").trimEnd() === current.replace(/\r\n/g, "\n").trimEnd();
|
|
2457
|
+
if (semanticallyEqual)
|
|
2458
|
+
result.unchanged.push(target);
|
|
2459
|
+
else {
|
|
2460
|
+
await atomicWrite(agentsPath, updated);
|
|
2461
|
+
result.written.push(target);
|
|
2462
|
+
}
|
|
2463
|
+
if (rules.length > 0) {
|
|
2464
|
+
next.targets[target] = sha256(semanticallyEqual ? current : updated);
|
|
2465
|
+
}
|
|
2466
|
+
} else if (Object.prototype.hasOwnProperty.call(previous.targets, "AGENTS.md")) {
|
|
2467
|
+
const agentsPath = join4(root, "AGENTS.md");
|
|
2468
|
+
const current = await optionalText(agentsPath);
|
|
2469
|
+
if (current !== null) {
|
|
2470
|
+
const updated = removeManagedBlockById(current, CODEX_BLOCK_ID);
|
|
2471
|
+
if (updated !== current) {
|
|
2472
|
+
await atomicWrite(agentsPath, updated);
|
|
2473
|
+
result.written.push("AGENTS.md");
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
await atomicWrite(join4(root, RECEIPT_PATH), JSON.stringify(next, null, 2) + "\n");
|
|
2478
|
+
result.migrated.sort();
|
|
2479
|
+
result.agent_specific.sort();
|
|
2480
|
+
result.written.sort();
|
|
2481
|
+
result.removed.sort();
|
|
2482
|
+
result.unchanged.sort();
|
|
2483
|
+
result.conflicts = [...new Set(result.conflicts)].sort();
|
|
2484
|
+
return result;
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
// ../core/dist/project/profile-bundle.js
|
|
2488
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
2489
|
+
import { readFile as readFile4, readdir as readdir3 } from "node:fs/promises";
|
|
2490
|
+
import { join as join5, resolve as resolve4 } from "node:path";
|
|
2254
2491
|
var AdapterBundleError = class extends Error {
|
|
2255
2492
|
code;
|
|
2256
2493
|
exitCode = 7;
|
|
@@ -2276,15 +2513,15 @@ function validateRelativeBundlePath2(path) {
|
|
|
2276
2513
|
}
|
|
2277
2514
|
var bundleCache = /* @__PURE__ */ new Map();
|
|
2278
2515
|
async function loadAgentBundle(resourcesRoot, profile, agent) {
|
|
2279
|
-
const cacheKey = `${
|
|
2516
|
+
const cacheKey = `${resolve4(resourcesRoot)}:${profile}:${agent}`;
|
|
2280
2517
|
const cached = bundleCache.get(cacheKey);
|
|
2281
2518
|
if (cached) {
|
|
2282
2519
|
return { manifest: cached.manifest, files: new Map(cached.files) };
|
|
2283
2520
|
}
|
|
2284
|
-
const manifestPath =
|
|
2521
|
+
const manifestPath = join5(resourcesRoot, "harness", "manifests", profile, `${agent}.json`);
|
|
2285
2522
|
let manifestText;
|
|
2286
2523
|
try {
|
|
2287
|
-
manifestText = await
|
|
2524
|
+
manifestText = await readFile4(manifestPath, "utf8");
|
|
2288
2525
|
} catch (error) {
|
|
2289
2526
|
if (isEnoent(error)) {
|
|
2290
2527
|
throw new AdapterBundleError("ADAPTER_BUNDLE_MISSING", `offline Harness Bundle manifest missing: ${profile}/${agent}`);
|
|
@@ -2312,14 +2549,14 @@ async function loadAgentBundle(resourcesRoot, profile, agent) {
|
|
|
2312
2549
|
}
|
|
2313
2550
|
let bytes;
|
|
2314
2551
|
try {
|
|
2315
|
-
bytes = await
|
|
2552
|
+
bytes = await readFile4(join5(resourcesRoot, "harness", "bundles", profile, agent, item2.path));
|
|
2316
2553
|
} catch (error) {
|
|
2317
2554
|
if (isEnoent(error)) {
|
|
2318
2555
|
throw new AdapterBundleError("ADAPTER_BUNDLE_MISSING", `offline Harness Bundle file missing: ${profile}/${agent}/${item2.path}`);
|
|
2319
2556
|
}
|
|
2320
2557
|
throw error;
|
|
2321
2558
|
}
|
|
2322
|
-
if (
|
|
2559
|
+
if (createHash4("sha256").update(bytes).digest("hex") !== item2.sha256) {
|
|
2323
2560
|
throw new AdapterBundleError("ADAPTER_BUNDLE_INVALID", `Harness Bundle hash mismatch: ${profile}/${agent}/${item2.path}`);
|
|
2324
2561
|
}
|
|
2325
2562
|
files.set(item2.path, bytes);
|
|
@@ -2384,10 +2621,10 @@ function parseMigrationManifest(raw) {
|
|
|
2384
2621
|
};
|
|
2385
2622
|
}
|
|
2386
2623
|
async function loadMigrationManifests(resourcesRoot) {
|
|
2387
|
-
const migrationsRoot =
|
|
2624
|
+
const migrationsRoot = join5(resourcesRoot, "harness", "migrations");
|
|
2388
2625
|
let versionDirs;
|
|
2389
2626
|
try {
|
|
2390
|
-
versionDirs = await
|
|
2627
|
+
versionDirs = await readdir3(migrationsRoot);
|
|
2391
2628
|
} catch (error) {
|
|
2392
2629
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
2393
2630
|
return [];
|
|
@@ -2398,7 +2635,7 @@ async function loadMigrationManifests(resourcesRoot) {
|
|
|
2398
2635
|
for (const versionDir of versionDirs) {
|
|
2399
2636
|
let files;
|
|
2400
2637
|
try {
|
|
2401
|
-
files = await
|
|
2638
|
+
files = await readdir3(join5(migrationsRoot, versionDir));
|
|
2402
2639
|
} catch (error) {
|
|
2403
2640
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
2404
2641
|
continue;
|
|
@@ -2408,7 +2645,7 @@ async function loadMigrationManifests(resourcesRoot) {
|
|
|
2408
2645
|
for (const file of files) {
|
|
2409
2646
|
if (!file.endsWith(".json"))
|
|
2410
2647
|
continue;
|
|
2411
|
-
manifests.push(parseMigrationManifest(JSON.parse(await
|
|
2648
|
+
manifests.push(parseMigrationManifest(JSON.parse(await readFile4(join5(migrationsRoot, versionDir, file), "utf8"))));
|
|
2412
2649
|
}
|
|
2413
2650
|
}
|
|
2414
2651
|
return manifests;
|
|
@@ -2440,14 +2677,14 @@ function uuidV7(now = Date.now()) {
|
|
|
2440
2677
|
var CONTEXT_INDEX_PATH = ".harness/context-index.json";
|
|
2441
2678
|
async function fileHex(path) {
|
|
2442
2679
|
try {
|
|
2443
|
-
const data = await
|
|
2444
|
-
return
|
|
2680
|
+
const data = await readFile5(path);
|
|
2681
|
+
return createHash5("sha256").update(data).digest("hex");
|
|
2445
2682
|
} catch {
|
|
2446
2683
|
return null;
|
|
2447
2684
|
}
|
|
2448
2685
|
}
|
|
2449
2686
|
async function readExistingSkillBundles(root) {
|
|
2450
|
-
const text = await readOptional(
|
|
2687
|
+
const text = await readOptional(join6(root, CONTEXT_INDEX_PATH));
|
|
2451
2688
|
if (text === "")
|
|
2452
2689
|
return /* @__PURE__ */ new Map();
|
|
2453
2690
|
try {
|
|
@@ -2467,7 +2704,7 @@ var TargetCollisionError = class extends Error {
|
|
|
2467
2704
|
}
|
|
2468
2705
|
};
|
|
2469
2706
|
function hex(bytes) {
|
|
2470
|
-
return
|
|
2707
|
+
return createHash5("sha256").update(bytes).digest("hex");
|
|
2471
2708
|
}
|
|
2472
2709
|
function bytesEqual(left, right) {
|
|
2473
2710
|
if (left.byteLength !== right.byteLength)
|
|
@@ -2480,7 +2717,7 @@ function bytesEqual(left, right) {
|
|
|
2480
2717
|
}
|
|
2481
2718
|
async function readOptional(path) {
|
|
2482
2719
|
try {
|
|
2483
|
-
return await
|
|
2720
|
+
return await readFile5(path, "utf8");
|
|
2484
2721
|
} catch (error) {
|
|
2485
2722
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
2486
2723
|
return "";
|
|
@@ -2500,12 +2737,12 @@ async function exists2(path) {
|
|
|
2500
2737
|
}
|
|
2501
2738
|
}
|
|
2502
2739
|
async function existingProjectConfig(root) {
|
|
2503
|
-
const path =
|
|
2740
|
+
const path = join6(root, ".harness", "project.yaml");
|
|
2504
2741
|
const content = await readOptional(path);
|
|
2505
2742
|
return content === "" ? null : projectConfigSchema.parse(parseYaml2(content));
|
|
2506
2743
|
}
|
|
2507
2744
|
async function operationFor(root, path, content) {
|
|
2508
|
-
return await exists2(
|
|
2745
|
+
return await exists2(join6(root, path)) ? { operation: "modify", path, content } : { operation: "add", path, content };
|
|
2509
2746
|
}
|
|
2510
2747
|
var INSTALLED_BUNDLE_PATH = ".harness/state/local/installed-harness-bundle.json";
|
|
2511
2748
|
function mergeOwnedTargets(owned) {
|
|
@@ -2541,7 +2778,7 @@ function mergeOwnedTargets(owned) {
|
|
|
2541
2778
|
});
|
|
2542
2779
|
}
|
|
2543
2780
|
async function initializeProject(options) {
|
|
2544
|
-
const root =
|
|
2781
|
+
const root = resolve5(options.projectRoot);
|
|
2545
2782
|
const config = initConfigSchema.parse(options.config);
|
|
2546
2783
|
const existing = await existingProjectConfig(root);
|
|
2547
2784
|
const profile = config.profile;
|
|
@@ -2584,7 +2821,7 @@ async function initializeProject(options) {
|
|
|
2584
2821
|
const projectConfig = projectConfigSchema.parse({
|
|
2585
2822
|
harness: { name: "hunter-harness", schema_version: 1 },
|
|
2586
2823
|
project: {
|
|
2587
|
-
name: existing?.project.name ??
|
|
2824
|
+
name: existing?.project.name ?? basename2(root),
|
|
2588
2825
|
root: ".",
|
|
2589
2826
|
local_project_key: existing?.project.local_project_key ?? uuidV7(),
|
|
2590
2827
|
project_id: config.project_id ?? existing?.project.project_id ?? null,
|
|
@@ -2606,7 +2843,7 @@ async function initializeProject(options) {
|
|
|
2606
2843
|
files: {}
|
|
2607
2844
|
});
|
|
2608
2845
|
const managedBlocks = [];
|
|
2609
|
-
let agentsContent = await readOptional(
|
|
2846
|
+
let agentsContent = await readOptional(join6(root, "AGENTS.md"));
|
|
2610
2847
|
agentsContent = upsertManagedBlockById(agentsContent, AGENTS_CORE_BLOCK_ID, AGENTS_MANAGED_BLOCK_CONTENT);
|
|
2611
2848
|
managedBlocks.push({
|
|
2612
2849
|
owner: "shared",
|
|
@@ -2643,7 +2880,7 @@ async function initializeProject(options) {
|
|
|
2643
2880
|
["AGENTS.md", agentsContent]
|
|
2644
2881
|
]);
|
|
2645
2882
|
if (enabledAgents.includes("claude-code")) {
|
|
2646
|
-
let claudeContent = await readOptional(
|
|
2883
|
+
let claudeContent = await readOptional(join6(root, "CLAUDE.md"));
|
|
2647
2884
|
claudeContent = upsertManagedBlockById(claudeContent, CLAUDE_BLOCK_ID, CLAUDE_MANAGED_BLOCK_CONTENT);
|
|
2648
2885
|
files.set("CLAUDE.md", claudeContent);
|
|
2649
2886
|
managedBlocks.push({
|
|
@@ -2654,7 +2891,7 @@ async function initializeProject(options) {
|
|
|
2654
2891
|
});
|
|
2655
2892
|
}
|
|
2656
2893
|
if (enabledAgents.includes("codebuddy")) {
|
|
2657
|
-
let codebuddyContent = await readOptional(
|
|
2894
|
+
let codebuddyContent = await readOptional(join6(root, "CODEBUDDY.md"));
|
|
2658
2895
|
codebuddyContent = upsertManagedBlockById(codebuddyContent, CODEBUDDY_BLOCK_ID, CODEBUDDY_MANAGED_BLOCK_CONTENT);
|
|
2659
2896
|
files.set("CODEBUDDY.md", codebuddyContent);
|
|
2660
2897
|
managedBlocks.push({
|
|
@@ -2691,6 +2928,7 @@ async function initializeProject(options) {
|
|
|
2691
2928
|
const writeOperations = await Promise.all([...files.entries()].map(async ([path, content]) => operationFor(root, path, content)));
|
|
2692
2929
|
await runTransaction(root, writeOperations, { kind: "init" });
|
|
2693
2930
|
await enrichContextIndexWithVerification(root, enabledAgents, profile, options.resourcesRoot, adapterContext);
|
|
2931
|
+
await synchronizeProjectRules(root, enabledAgents, surface);
|
|
2694
2932
|
}
|
|
2695
2933
|
return {
|
|
2696
2934
|
projectConfig,
|
|
@@ -2700,8 +2938,8 @@ async function initializeProject(options) {
|
|
|
2700
2938
|
};
|
|
2701
2939
|
}
|
|
2702
2940
|
async function enrichContextIndexWithVerification(root, agents, profile, resourcesRoot, adapterContext) {
|
|
2703
|
-
const contextPath =
|
|
2704
|
-
const existing = await readOptional(
|
|
2941
|
+
const contextPath = join6(root, CONTEXT_INDEX_PATH);
|
|
2942
|
+
const existing = await readOptional(join6(contextPath));
|
|
2705
2943
|
if (existing === "")
|
|
2706
2944
|
return;
|
|
2707
2945
|
let parsed;
|
|
@@ -2726,7 +2964,7 @@ async function enrichContextIndexWithVerification(root, agents, profile, resourc
|
|
|
2726
2964
|
const entries = [];
|
|
2727
2965
|
for (const target of targets) {
|
|
2728
2966
|
const rel = target.target_path.replace(/\\/g, "/");
|
|
2729
|
-
const actual = await fileHex(
|
|
2967
|
+
const actual = await fileHex(join6(root, target.target_path));
|
|
2730
2968
|
entries.push({ relpath: rel, sha256: actual ?? "" });
|
|
2731
2969
|
if (actual === null) {
|
|
2732
2970
|
mismatches.push({ relpath: rel, expected: target.sha256, actual: "<missing>" });
|
|
@@ -2743,19 +2981,240 @@ async function enrichContextIndexWithVerification(root, agents, profile, resourc
|
|
|
2743
2981
|
entry.verificationStatus = newStatus;
|
|
2744
2982
|
entry.mismatchDetails = mismatches;
|
|
2745
2983
|
}
|
|
2746
|
-
await
|
|
2984
|
+
await writeFile4(contextPath, JSON.stringify(parsed, null, 2) + "\n", "utf8");
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2987
|
+
// ../core/dist/project/rule-candidates.js
|
|
2988
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
2989
|
+
import { mkdir as mkdir5, readFile as readFile6, readdir as readdir4, rename as rename4, stat as stat3, writeFile as writeFile5 } from "node:fs/promises";
|
|
2990
|
+
import { basename as basename3, dirname as dirname4, join as join7, relative, resolve as resolve6 } from "node:path";
|
|
2991
|
+
var ARCHIVE_ROOT = ".harness/archive";
|
|
2992
|
+
var CANDIDATE_PATH = ".harness/knowledge/rule-candidates.json";
|
|
2993
|
+
var MAX_EVIDENCE_BYTES = 2 * 1024 * 1024;
|
|
2994
|
+
var EVIDENCE_NAMES = [
|
|
2995
|
+
/^review-findings.*\.json$/i,
|
|
2996
|
+
/^test-(?:report|results?|failures?).*\.json$/i,
|
|
2997
|
+
/^summary-data\.json$/i
|
|
2998
|
+
];
|
|
2999
|
+
function sha2562(content) {
|
|
3000
|
+
return createHash6("sha256").update(content).digest("hex");
|
|
3001
|
+
}
|
|
3002
|
+
function portable2(path) {
|
|
3003
|
+
return path.replaceAll("\\", "/");
|
|
3004
|
+
}
|
|
3005
|
+
function normalizeText(value) {
|
|
3006
|
+
return value.replace(/\s+/g, " ").trim();
|
|
3007
|
+
}
|
|
3008
|
+
function safeText(value, limit = 500) {
|
|
3009
|
+
if (typeof value !== "string")
|
|
3010
|
+
return null;
|
|
3011
|
+
const normalized = normalizeText(value).slice(0, limit);
|
|
3012
|
+
if (normalized.length < 8)
|
|
3013
|
+
return null;
|
|
3014
|
+
if (/(?:ignore|disregard)\s+(?:all\s+)?previous|system\s+prompt|developer\s+message/i.test(normalized)) {
|
|
3015
|
+
return null;
|
|
3016
|
+
}
|
|
3017
|
+
if (/-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----|gh[pousr]_[A-Za-z0-9]{20,}|(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?):\/\//i.test(normalized)) {
|
|
3018
|
+
return null;
|
|
3019
|
+
}
|
|
3020
|
+
return normalized;
|
|
3021
|
+
}
|
|
3022
|
+
function stringField(record, names) {
|
|
3023
|
+
for (const name of names) {
|
|
3024
|
+
const value = safeText(record[name]);
|
|
3025
|
+
if (value !== null)
|
|
3026
|
+
return value;
|
|
3027
|
+
}
|
|
3028
|
+
return null;
|
|
3029
|
+
}
|
|
3030
|
+
function severityOf(record) {
|
|
3031
|
+
const value = record.severity ?? record.level ?? record.priority ?? "unknown";
|
|
3032
|
+
return typeof value === "string" ? value.toLowerCase() : "unknown";
|
|
3033
|
+
}
|
|
3034
|
+
function highSeverity(severity) {
|
|
3035
|
+
return /^(?:red|critical|high|error|blocker|p0|p1)$/.test(severity);
|
|
3036
|
+
}
|
|
3037
|
+
function evidenceKind(path) {
|
|
3038
|
+
const name = basename3(path).toLowerCase();
|
|
3039
|
+
if (name.startsWith("review-"))
|
|
3040
|
+
return "review";
|
|
3041
|
+
if (name.startsWith("test-"))
|
|
3042
|
+
return "test";
|
|
3043
|
+
return "validation";
|
|
3044
|
+
}
|
|
3045
|
+
function observationFrom(record, path, archive) {
|
|
3046
|
+
const severity = severityOf(record);
|
|
3047
|
+
const suggestion = stringField(record, [
|
|
3048
|
+
"proposed_rule",
|
|
3049
|
+
"proposedRule",
|
|
3050
|
+
"suggestion",
|
|
3051
|
+
"recommendation",
|
|
3052
|
+
"remediation"
|
|
3053
|
+
]);
|
|
3054
|
+
const issue = stringField(record, ["issue", "message", "error", "failure"]);
|
|
3055
|
+
const title = stringField(record, ["title", "name", "id", "code"]) ?? issue;
|
|
3056
|
+
let proposedRule = suggestion;
|
|
3057
|
+
if (proposedRule === null && issue !== null && highSeverity(severity)) {
|
|
3058
|
+
proposedRule = `\u5FC5\u987B\u589E\u52A0\u53EF\u91CD\u590D\u9A8C\u8BC1\uFF0C\u9632\u6B62\u4EE5\u4E0B\u95EE\u9898\u518D\u6B21\u51FA\u73B0\uFF1A${issue}`;
|
|
3059
|
+
}
|
|
3060
|
+
if (proposedRule === null || title === null)
|
|
3061
|
+
return null;
|
|
3062
|
+
const recordId = record.id ?? record.code ?? record.name ?? null;
|
|
3063
|
+
return {
|
|
3064
|
+
title,
|
|
3065
|
+
proposedRule,
|
|
3066
|
+
severity,
|
|
3067
|
+
evidence: {
|
|
3068
|
+
archive,
|
|
3069
|
+
path,
|
|
3070
|
+
kind: evidenceKind(path),
|
|
3071
|
+
record_id: typeof recordId === "string" ? recordId.slice(0, 120) : null
|
|
3072
|
+
}
|
|
3073
|
+
};
|
|
3074
|
+
}
|
|
3075
|
+
function collectObservations(value, path, archive, output, rejected) {
|
|
3076
|
+
if (Array.isArray(value)) {
|
|
3077
|
+
for (const item2 of value)
|
|
3078
|
+
collectObservations(item2, path, archive, output, rejected);
|
|
3079
|
+
return;
|
|
3080
|
+
}
|
|
3081
|
+
if (value === null || typeof value !== "object")
|
|
3082
|
+
return;
|
|
3083
|
+
const record = value;
|
|
3084
|
+
const candidate = observationFrom(record, path, archive);
|
|
3085
|
+
if (candidate !== null)
|
|
3086
|
+
output.push(candidate);
|
|
3087
|
+
else if (Object.keys(record).some((key) => ["suggestion", "recommendation", "proposed_rule", "proposedRule"].includes(key)))
|
|
3088
|
+
rejected.count += 1;
|
|
3089
|
+
for (const nested of Object.values(record)) {
|
|
3090
|
+
if (nested !== null && typeof nested === "object") {
|
|
3091
|
+
collectObservations(nested, path, archive, output, rejected);
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
async function evidenceFiles(root) {
|
|
3096
|
+
const archiveRoot = join7(root, ...ARCHIVE_ROOT.split("/"));
|
|
3097
|
+
const output = [];
|
|
3098
|
+
async function walk(directory) {
|
|
3099
|
+
let entries;
|
|
3100
|
+
try {
|
|
3101
|
+
entries = await readdir4(directory, { withFileTypes: true });
|
|
3102
|
+
} catch (error) {
|
|
3103
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
3104
|
+
return;
|
|
3105
|
+
throw error;
|
|
3106
|
+
}
|
|
3107
|
+
for (const entry of entries) {
|
|
3108
|
+
const path = join7(directory, entry.name);
|
|
3109
|
+
if (entry.isDirectory())
|
|
3110
|
+
await walk(path);
|
|
3111
|
+
else if (entry.isFile() && EVIDENCE_NAMES.some((pattern) => pattern.test(entry.name))) {
|
|
3112
|
+
if ((await stat3(path)).size <= MAX_EVIDENCE_BYTES)
|
|
3113
|
+
output.push(path);
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
await walk(archiveRoot);
|
|
3118
|
+
return output.sort();
|
|
3119
|
+
}
|
|
3120
|
+
function candidateKey(rule) {
|
|
3121
|
+
return normalizeText(rule).toLocaleLowerCase();
|
|
3122
|
+
}
|
|
3123
|
+
function buildCandidates(observations) {
|
|
3124
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
3125
|
+
for (const observation of observations) {
|
|
3126
|
+
const key = candidateKey(observation.proposedRule);
|
|
3127
|
+
const values = grouped.get(key) ?? [];
|
|
3128
|
+
values.push(observation);
|
|
3129
|
+
grouped.set(key, values);
|
|
3130
|
+
}
|
|
3131
|
+
const candidates = [];
|
|
3132
|
+
for (const [key, values] of grouped) {
|
|
3133
|
+
const archives = new Set(values.map((value) => value.evidence.archive));
|
|
3134
|
+
const highest = values.find((value) => highSeverity(value.severity));
|
|
3135
|
+
if (archives.size < 2 && highest === void 0)
|
|
3136
|
+
continue;
|
|
3137
|
+
const representative = highest ?? values.at(0);
|
|
3138
|
+
if (representative === void 0)
|
|
3139
|
+
continue;
|
|
3140
|
+
const evidence = [...new Map(values.map((value) => [
|
|
3141
|
+
`${value.evidence.path}\0${value.evidence.record_id ?? ""}`,
|
|
3142
|
+
value.evidence
|
|
3143
|
+
])).values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
3144
|
+
candidates.push({
|
|
3145
|
+
id: `rule_${sha2562(key).slice(0, 16)}`,
|
|
3146
|
+
status: "candidate",
|
|
3147
|
+
title: representative.title,
|
|
3148
|
+
proposed_rule: representative.proposedRule,
|
|
3149
|
+
confidence: archives.size >= 2 && highest !== void 0 ? "high" : "medium",
|
|
3150
|
+
severity: representative.severity,
|
|
3151
|
+
occurrences: evidence.length,
|
|
3152
|
+
evidence
|
|
3153
|
+
});
|
|
3154
|
+
}
|
|
3155
|
+
return candidates.sort((a, b) => a.id.localeCompare(b.id));
|
|
3156
|
+
}
|
|
3157
|
+
async function atomicWrite2(path, content) {
|
|
3158
|
+
await mkdir5(dirname4(path), { recursive: true });
|
|
3159
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
3160
|
+
await writeFile5(temporary, content, "utf8");
|
|
3161
|
+
await rename4(temporary, path);
|
|
3162
|
+
}
|
|
3163
|
+
async function synchronizeRuleCandidates(projectRoot, options = {}) {
|
|
3164
|
+
const root = resolve6(projectRoot);
|
|
3165
|
+
const files = await evidenceFiles(root);
|
|
3166
|
+
const sourceHashes = {};
|
|
3167
|
+
const observations = [];
|
|
3168
|
+
const rejected = { count: 0 };
|
|
3169
|
+
for (const path of files) {
|
|
3170
|
+
const relativePath = portable2(relative(root, path));
|
|
3171
|
+
const content2 = await readFile6(path, "utf8");
|
|
3172
|
+
sourceHashes[relativePath] = sha2562(content2);
|
|
3173
|
+
let parsed;
|
|
3174
|
+
try {
|
|
3175
|
+
parsed = JSON.parse(content2);
|
|
3176
|
+
} catch {
|
|
3177
|
+
continue;
|
|
3178
|
+
}
|
|
3179
|
+
const archive = relativePath.split("/")[2] ?? "unknown";
|
|
3180
|
+
collectObservations(parsed, relativePath, archive, observations, rejected);
|
|
3181
|
+
}
|
|
3182
|
+
const manifest = {
|
|
3183
|
+
schema_version: 1,
|
|
3184
|
+
source_hashes: sourceHashes,
|
|
3185
|
+
candidates: buildCandidates(observations)
|
|
3186
|
+
};
|
|
3187
|
+
const content = JSON.stringify(manifest, null, 2) + "\n";
|
|
3188
|
+
const destination = join7(root, ...CANDIDATE_PATH.split("/"));
|
|
3189
|
+
let current = null;
|
|
3190
|
+
try {
|
|
3191
|
+
current = await readFile6(destination, "utf8");
|
|
3192
|
+
} catch (error) {
|
|
3193
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
|
|
3194
|
+
throw error;
|
|
3195
|
+
}
|
|
3196
|
+
const changed = current !== content;
|
|
3197
|
+
if (changed && options.dryRun !== true)
|
|
3198
|
+
await atomicWrite2(destination, content);
|
|
3199
|
+
return {
|
|
3200
|
+
path: CANDIDATE_PATH,
|
|
3201
|
+
scanned: files.length,
|
|
3202
|
+
candidates: manifest.candidates.length,
|
|
3203
|
+
changed,
|
|
3204
|
+
rejected_untrusted: rejected.count
|
|
3205
|
+
};
|
|
2747
3206
|
}
|
|
2748
3207
|
|
|
2749
3208
|
// ../core/dist/project/refresh.js
|
|
2750
|
-
import { createHash as
|
|
2751
|
-
import { readFile as
|
|
2752
|
-
import { dirname as
|
|
3209
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
3210
|
+
import { readFile as readFile7, readdir as readdir5, rmdir } from "node:fs/promises";
|
|
3211
|
+
import { dirname as dirname5, join as join8, resolve as resolve7 } from "node:path";
|
|
2753
3212
|
import { parse as parseYaml3, stringify as stringifyYaml2 } from "yaml";
|
|
2754
3213
|
var INSTALLED_STATE_PATH = ".harness/state/local/installed-harness-bundle.json";
|
|
2755
3214
|
var CONTEXT_INDEX_PATH2 = ".harness/context-index.json";
|
|
2756
3215
|
async function fileHex2(path) {
|
|
2757
3216
|
try {
|
|
2758
|
-
return
|
|
3217
|
+
return createHash7("sha256").update(await readFile7(path)).digest("hex");
|
|
2759
3218
|
} catch (error) {
|
|
2760
3219
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
2761
3220
|
return null;
|
|
@@ -2765,7 +3224,7 @@ async function fileHex2(path) {
|
|
|
2765
3224
|
}
|
|
2766
3225
|
async function readOptionalText(path) {
|
|
2767
3226
|
try {
|
|
2768
|
-
return await
|
|
3227
|
+
return await readFile7(path, "utf8");
|
|
2769
3228
|
} catch (error) {
|
|
2770
3229
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
2771
3230
|
return "";
|
|
@@ -2774,7 +3233,7 @@ async function readOptionalText(path) {
|
|
|
2774
3233
|
}
|
|
2775
3234
|
}
|
|
2776
3235
|
async function readInstalledState(root) {
|
|
2777
|
-
const content = await readOptionalText(
|
|
3236
|
+
const content = await readOptionalText(join8(root, INSTALLED_STATE_PATH));
|
|
2778
3237
|
if (content === "") {
|
|
2779
3238
|
return {
|
|
2780
3239
|
profile: null,
|
|
@@ -2844,7 +3303,7 @@ async function readInstalledState(root) {
|
|
|
2844
3303
|
};
|
|
2845
3304
|
}
|
|
2846
3305
|
async function readInstalledAgentConfiguration(projectRoot) {
|
|
2847
|
-
const installed = await readInstalledState(
|
|
3306
|
+
const installed = await readInstalledState(resolve7(projectRoot));
|
|
2848
3307
|
return {
|
|
2849
3308
|
agents: installed.adapters,
|
|
2850
3309
|
profiles: Object.fromEntries(installed.adapters.map((agent) => [
|
|
@@ -2854,7 +3313,7 @@ async function readInstalledAgentConfiguration(projectRoot) {
|
|
|
2854
3313
|
};
|
|
2855
3314
|
}
|
|
2856
3315
|
async function readContextIndexBundleHash(root) {
|
|
2857
|
-
const content = await readOptionalText(
|
|
3316
|
+
const content = await readOptionalText(join8(root, CONTEXT_INDEX_PATH2));
|
|
2858
3317
|
if (content === "")
|
|
2859
3318
|
return null;
|
|
2860
3319
|
try {
|
|
@@ -2866,13 +3325,13 @@ async function readContextIndexBundleHash(root) {
|
|
|
2866
3325
|
}
|
|
2867
3326
|
}
|
|
2868
3327
|
async function pruneEmptyParentDirs(root, deletedPaths, boundaryPaths) {
|
|
2869
|
-
const boundaries = new Set(boundaryPaths.map((path) =>
|
|
3328
|
+
const boundaries = new Set(boundaryPaths.map((path) => join8(root, path)));
|
|
2870
3329
|
for (const deleted of deletedPaths) {
|
|
2871
|
-
let dir =
|
|
3330
|
+
let dir = dirname5(join8(root, deleted));
|
|
2872
3331
|
while (dir.startsWith(root) && !boundaries.has(dir)) {
|
|
2873
3332
|
let entries;
|
|
2874
3333
|
try {
|
|
2875
|
-
entries = await
|
|
3334
|
+
entries = await readdir5(dir);
|
|
2876
3335
|
} catch {
|
|
2877
3336
|
break;
|
|
2878
3337
|
}
|
|
@@ -2883,7 +3342,7 @@ async function pruneEmptyParentDirs(root, deletedPaths, boundaryPaths) {
|
|
|
2883
3342
|
} catch {
|
|
2884
3343
|
break;
|
|
2885
3344
|
}
|
|
2886
|
-
dir =
|
|
3345
|
+
dir = dirname5(dir);
|
|
2887
3346
|
}
|
|
2888
3347
|
}
|
|
2889
3348
|
}
|
|
@@ -2910,7 +3369,7 @@ function sortByTarget(items) {
|
|
|
2910
3369
|
return [...items].sort((left, right) => left.target_path.localeCompare(right.target_path));
|
|
2911
3370
|
}
|
|
2912
3371
|
async function reconcileContextIndex(root, agents, profiles, manifests, codebuddySurface2, verifications) {
|
|
2913
|
-
const existing = await readOptionalText(
|
|
3372
|
+
const existing = await readOptionalText(join8(root, CONTEXT_INDEX_PATH2));
|
|
2914
3373
|
let codebase = {
|
|
2915
3374
|
map: ".harness/codebase/map",
|
|
2916
3375
|
status: "missing"
|
|
@@ -2963,7 +3422,7 @@ async function reconcileContextIndex(root, agents, profiles, manifests, codebudd
|
|
|
2963
3422
|
}
|
|
2964
3423
|
async function projectTransitionOperation(root, agents, profiles, codebuddySurface2) {
|
|
2965
3424
|
const path = ".harness/project.yaml";
|
|
2966
|
-
const content = await readOptionalText(
|
|
3425
|
+
const content = await readOptionalText(join8(root, path));
|
|
2967
3426
|
if (content === "")
|
|
2968
3427
|
return null;
|
|
2969
3428
|
const project = parseYaml3(content);
|
|
@@ -2998,11 +3457,11 @@ function mergeTargets(targets) {
|
|
|
2998
3457
|
}).sort((left, right) => left.target_path.localeCompare(right.target_path));
|
|
2999
3458
|
}
|
|
3000
3459
|
async function reconcileMarkdownBlock(root, fileName, blockId, content, remove, ops, conflicts, preserved) {
|
|
3001
|
-
const original = await readOptionalText(
|
|
3460
|
+
const original = await readOptionalText(join8(root, fileName));
|
|
3002
3461
|
const synthetic = {
|
|
3003
3462
|
source_path: fileName,
|
|
3004
3463
|
target_path: fileName,
|
|
3005
|
-
sha256:
|
|
3464
|
+
sha256: createHash7("sha256").update(content).digest("hex"),
|
|
3006
3465
|
bytes: new TextEncoder().encode(content)
|
|
3007
3466
|
};
|
|
3008
3467
|
let next;
|
|
@@ -3017,7 +3476,7 @@ async function reconcileMarkdownBlock(root, fileName, blockId, content, remove,
|
|
|
3017
3476
|
next = refreshed.content;
|
|
3018
3477
|
}
|
|
3019
3478
|
} catch {
|
|
3020
|
-
const current = original === "" ? null :
|
|
3479
|
+
const current = original === "" ? null : createHash7("sha256").update(original).digest("hex");
|
|
3021
3480
|
preserved.push(item(synthetic, "preserve", "MALFORMED_MANAGED_BLOCK", current, synthetic.sha256));
|
|
3022
3481
|
conflicts.push(conflict(synthetic, "MALFORMED_MANAGED_BLOCK", current, synthetic.sha256));
|
|
3023
3482
|
return;
|
|
@@ -3037,7 +3496,7 @@ function stateWithoutInstalledAt(value) {
|
|
|
3037
3496
|
return copy;
|
|
3038
3497
|
}
|
|
3039
3498
|
async function refreshProject(options) {
|
|
3040
|
-
const root =
|
|
3499
|
+
const root = resolve7(options.projectRoot);
|
|
3041
3500
|
const installed = await readInstalledState(root);
|
|
3042
3501
|
const oldAgents = installed.adapters.length > 0 ? installed.adapters : ["claude-code"];
|
|
3043
3502
|
const selectedAgents = sortHarnessAgents(options.agents);
|
|
@@ -3121,7 +3580,7 @@ async function refreshProject(options) {
|
|
|
3121
3580
|
const newStateFiles = installed.files.filter((entry) => entry.owner === "shared" || !selectedSet.has(entry.owner));
|
|
3122
3581
|
for (const target of newManaged) {
|
|
3123
3582
|
const incoming = target.sha256;
|
|
3124
|
-
const current = await fileHex2(
|
|
3583
|
+
const current = await fileHex2(join8(root, target.target_path));
|
|
3125
3584
|
if (current === null) {
|
|
3126
3585
|
applied.push(item(target, "add", "MISSING_TARGET", null, incoming));
|
|
3127
3586
|
ops.push({ operation: "add", path: target.target_path, content: target.bytes });
|
|
@@ -3149,7 +3608,7 @@ async function refreshProject(options) {
|
|
|
3149
3608
|
}
|
|
3150
3609
|
}
|
|
3151
3610
|
for (const target of oldOnly) {
|
|
3152
|
-
const current = await fileHex2(
|
|
3611
|
+
const current = await fileHex2(join8(root, target.target_path));
|
|
3153
3612
|
if (current === null) {
|
|
3154
3613
|
continue;
|
|
3155
3614
|
}
|
|
@@ -3192,7 +3651,7 @@ async function refreshProject(options) {
|
|
|
3192
3651
|
const verifyEntries = [];
|
|
3193
3652
|
for (const target of verifyTargets) {
|
|
3194
3653
|
const rel = target.target_path.replace(/\\/g, "/");
|
|
3195
|
-
const actual = await fileHex2(
|
|
3654
|
+
const actual = await fileHex2(join8(root, target.target_path));
|
|
3196
3655
|
verifyEntries.push({ relpath: rel, sha256: actual ?? "" });
|
|
3197
3656
|
if (actual === null) {
|
|
3198
3657
|
verifyMismatches.push({ relpath: rel, expected: target.sha256, actual: "<missing>" });
|
|
@@ -3225,19 +3684,19 @@ async function refreshProject(options) {
|
|
|
3225
3684
|
owner: "shared",
|
|
3226
3685
|
target_path: "AGENTS.md",
|
|
3227
3686
|
block_id: AGENTS_CORE_BLOCK_ID,
|
|
3228
|
-
content_sha256:
|
|
3687
|
+
content_sha256: createHash7("sha256").update(AGENTS_MANAGED_BLOCK_CONTENT).digest("hex")
|
|
3229
3688
|
},
|
|
3230
3689
|
...selectedSet.has("claude-code") ? [{
|
|
3231
3690
|
owner: "claude-code",
|
|
3232
3691
|
target_path: "CLAUDE.md",
|
|
3233
3692
|
block_id: CLAUDE_BLOCK_ID,
|
|
3234
|
-
content_sha256:
|
|
3693
|
+
content_sha256: createHash7("sha256").update(CLAUDE_MANAGED_BLOCK_CONTENT).digest("hex")
|
|
3235
3694
|
}] : [],
|
|
3236
3695
|
...selectedSet.has("codebuddy") ? [{
|
|
3237
3696
|
owner: "codebuddy",
|
|
3238
3697
|
target_path: "CODEBUDDY.md",
|
|
3239
3698
|
block_id: CODEBUDDY_BLOCK_ID,
|
|
3240
|
-
content_sha256:
|
|
3699
|
+
content_sha256: createHash7("sha256").update(CODEBUDDY_MANAGED_BLOCK_CONTENT).digest("hex")
|
|
3241
3700
|
}] : []
|
|
3242
3701
|
].sort((left, right) => left.target_path.localeCompare(right.target_path) || left.block_id.localeCompare(right.block_id));
|
|
3243
3702
|
const filesByTarget = new Map(newStateFiles.map((entry) => [entry.target_path, entry]));
|
|
@@ -3253,7 +3712,7 @@ async function refreshProject(options) {
|
|
|
3253
3712
|
files: [...filesByTarget.values()].sort((left, right) => left.target_path.localeCompare(right.target_path) || left.source_path.localeCompare(right.source_path)),
|
|
3254
3713
|
managed_blocks: managedBlocks
|
|
3255
3714
|
};
|
|
3256
|
-
const existingState = await readOptionalText(
|
|
3715
|
+
const existingState = await readOptionalText(join8(root, INSTALLED_STATE_PATH));
|
|
3257
3716
|
let existingParsed = null;
|
|
3258
3717
|
try {
|
|
3259
3718
|
existingParsed = existingState === "" ? null : JSON.parse(existingState);
|
|
@@ -3268,6 +3727,7 @@ async function refreshProject(options) {
|
|
|
3268
3727
|
}
|
|
3269
3728
|
if (!options.dryRun) {
|
|
3270
3729
|
await runTransaction(root, ops, { kind: "refresh" });
|
|
3730
|
+
await synchronizeProjectRules(root, agents, codebuddySurface2);
|
|
3271
3731
|
await pruneEmptyParentDirs(root, removed.map((item2) => item2.target_path), getAdapters(selectedAgents).flatMap((adapter) => adapter.pruneBoundaries({
|
|
3272
3732
|
profile: profiles.get(adapter.name) ?? profile,
|
|
3273
3733
|
codebuddySurface: codebuddySurface2
|
|
@@ -3307,7 +3767,7 @@ function buildMarkerCoreHash(text) {
|
|
|
3307
3767
|
}
|
|
3308
3768
|
}
|
|
3309
3769
|
async function collectFreshness(options) {
|
|
3310
|
-
const root =
|
|
3770
|
+
const root = resolve7(options.projectRoot);
|
|
3311
3771
|
const installed = await readInstalledState(root);
|
|
3312
3772
|
const codebuddySurface2 = options.codebuddySurface ?? "both";
|
|
3313
3773
|
const agents = [];
|
|
@@ -3352,18 +3812,18 @@ async function collectFreshness(options) {
|
|
|
3352
3812
|
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
3813
|
const installedProjection = await Promise.all(targets.map(async (target) => ({
|
|
3354
3814
|
path: target.target_path.replace(/\\/g, "/"),
|
|
3355
|
-
sha256: await fileHex2(
|
|
3815
|
+
sha256: await fileHex2(join8(root, target.target_path))
|
|
3356
3816
|
})));
|
|
3357
3817
|
identity.installedAdapterHash = sha256Bytes(canonicalJson(installedProjection.sort((a, b) => a.path.localeCompare(b.path))));
|
|
3358
3818
|
const markerTarget = targets.find((target) => target.target_path.replace(/\\/g, "/").endsWith(`/${BUILD_MARKER_BUNDLE_PATH}`) || target.target_path === BUILD_MARKER_BUNDLE_PATH);
|
|
3359
3819
|
if (markerTarget !== void 0) {
|
|
3360
|
-
identity.installedCoreHash = buildMarkerCoreHash(await readOptionalText(
|
|
3820
|
+
identity.installedCoreHash = buildMarkerCoreHash(await readOptionalText(join8(root, markerTarget.target_path)));
|
|
3361
3821
|
}
|
|
3362
3822
|
const mismatchDetails = [];
|
|
3363
3823
|
const contentEntries = [];
|
|
3364
3824
|
for (const target of targets) {
|
|
3365
3825
|
const rel = target.target_path.replace(/\\/g, "/");
|
|
3366
|
-
const actual = await fileHex2(
|
|
3826
|
+
const actual = await fileHex2(join8(root, target.target_path));
|
|
3367
3827
|
contentEntries.push({ relpath: rel, sha256: actual ?? "" });
|
|
3368
3828
|
if (actual === null) {
|
|
3369
3829
|
mismatchDetails.push({ relpath: rel, expected: target.sha256, actual: "<missing>" });
|
|
@@ -3386,7 +3846,7 @@ async function collectFreshness(options) {
|
|
|
3386
3846
|
const drifted = [];
|
|
3387
3847
|
const missing = [];
|
|
3388
3848
|
for (const target of targets) {
|
|
3389
|
-
const current = await fileHex2(
|
|
3849
|
+
const current = await fileHex2(join8(root, target.target_path));
|
|
3390
3850
|
if (current === null) {
|
|
3391
3851
|
missing.push(target.target_path);
|
|
3392
3852
|
} else if (current !== target.sha256) {
|
|
@@ -3634,11 +4094,11 @@ function rawFindings(content) {
|
|
|
3634
4094
|
if (rule.id === "HH_PASSWORD_VALUE" && isObviousPlaceholder(value)) {
|
|
3635
4095
|
continue;
|
|
3636
4096
|
}
|
|
3637
|
-
const
|
|
4097
|
+
const relative4 = match[0].indexOf(value);
|
|
3638
4098
|
findings.push({
|
|
3639
4099
|
ruleId: rule.id,
|
|
3640
4100
|
severity: rule.severity,
|
|
3641
|
-
offset: match.index + Math.max(0,
|
|
4101
|
+
offset: match.index + Math.max(0, relative4),
|
|
3642
4102
|
value
|
|
3643
4103
|
});
|
|
3644
4104
|
}
|
|
@@ -3750,8 +4210,8 @@ function generateProposalPreview(input, scanOptions = {}) {
|
|
|
3750
4210
|
}
|
|
3751
4211
|
|
|
3752
4212
|
// ../core/dist/push/credentials.js
|
|
3753
|
-
import { readFile as
|
|
3754
|
-
import { join as
|
|
4213
|
+
import { readFile as readFile8, writeFile as writeFile6 } from "node:fs/promises";
|
|
4214
|
+
import { join as join9 } from "node:path";
|
|
3755
4215
|
import { parse as parseYaml4, stringify as stringifyYaml3 } from "yaml";
|
|
3756
4216
|
var CREDENTIALS_LOCAL_RELATIVE = ".harness/credentials.local.yaml";
|
|
3757
4217
|
var CREDENTIALS_GITIGNORE_LINES = [
|
|
@@ -3821,7 +4281,7 @@ function mergeLocalCredentials(existing, patch) {
|
|
|
3821
4281
|
}
|
|
3822
4282
|
async function readLocalCredentials(projectRoot) {
|
|
3823
4283
|
try {
|
|
3824
|
-
const raw = await
|
|
4284
|
+
const raw = await readFile8(join9(projectRoot, CREDENTIALS_LOCAL_RELATIVE), "utf8");
|
|
3825
4285
|
return parseLocalCredentials(parseYaml4(raw));
|
|
3826
4286
|
} catch (error) {
|
|
3827
4287
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
@@ -3832,13 +4292,13 @@ async function readLocalCredentials(projectRoot) {
|
|
|
3832
4292
|
}
|
|
3833
4293
|
async function writeLocalCredentials(projectRoot, credentials) {
|
|
3834
4294
|
const normalized = validateLocalCredentials(credentials);
|
|
3835
|
-
await
|
|
4295
|
+
await writeFile6(join9(projectRoot, CREDENTIALS_LOCAL_RELATIVE), stringifyYaml3(normalized, { sortMapEntries: true }) + "\n", "utf8");
|
|
3836
4296
|
}
|
|
3837
4297
|
async function ensureCredentialsGitignore(projectRoot) {
|
|
3838
|
-
const gitignorePath =
|
|
4298
|
+
const gitignorePath = join9(projectRoot, ".gitignore");
|
|
3839
4299
|
let content = "";
|
|
3840
4300
|
try {
|
|
3841
|
-
content = await
|
|
4301
|
+
content = await readFile8(gitignorePath, "utf8");
|
|
3842
4302
|
} catch (error) {
|
|
3843
4303
|
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) {
|
|
3844
4304
|
throw error;
|
|
@@ -3861,7 +4321,7 @@ async function ensureCredentialsGitignore(projectRoot) {
|
|
|
3861
4321
|
return;
|
|
3862
4322
|
}
|
|
3863
4323
|
const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
|
|
3864
|
-
await
|
|
4324
|
+
await writeFile6(gitignorePath, content + suffix + missing.join("\n") + "\n", "utf8");
|
|
3865
4325
|
}
|
|
3866
4326
|
function resolvePushAuth(input) {
|
|
3867
4327
|
const tokenEnv = input.tokenEnv ?? input.projectTokenEnv;
|
|
@@ -3889,28 +4349,28 @@ function resolvePushAuth(input) {
|
|
|
3889
4349
|
}
|
|
3890
4350
|
|
|
3891
4351
|
// ../core/dist/push/push.js
|
|
3892
|
-
import { lstat as lstat3, readFile as
|
|
3893
|
-
import { join as
|
|
4352
|
+
import { lstat as lstat3, readFile as readFile12, readdir as readdir6, rm as rm5 } from "node:fs/promises";
|
|
4353
|
+
import { join as join13, relative as relative2, resolve as resolve9 } from "node:path";
|
|
3894
4354
|
import { parse as parseYaml5, stringify as stringifyYaml4 } from "yaml";
|
|
3895
4355
|
|
|
3896
4356
|
// ../core/dist/state/baseline.js
|
|
3897
|
-
import { readFile as
|
|
3898
|
-
import { join as
|
|
4357
|
+
import { readFile as readFile9 } from "node:fs/promises";
|
|
4358
|
+
import { join as join10 } from "node:path";
|
|
3899
4359
|
async function readBaseline(projectRoot) {
|
|
3900
|
-
const content = await
|
|
4360
|
+
const content = await readFile9(join10(stateLayout(projectRoot).baseline, "manifest.json"), "utf8");
|
|
3901
4361
|
return baselineManifestSchema.parse(JSON.parse(content));
|
|
3902
4362
|
}
|
|
3903
4363
|
|
|
3904
4364
|
// ../core/dist/state/locks.js
|
|
3905
4365
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
3906
|
-
import { readFile as
|
|
3907
|
-
import { join as
|
|
4366
|
+
import { readFile as readFile10, rename as rename5, rm as rm3, writeFile as writeFile7 } from "node:fs/promises";
|
|
4367
|
+
import { join as join11 } from "node:path";
|
|
3908
4368
|
async function readLock(path) {
|
|
3909
|
-
return JSON.parse(await
|
|
4369
|
+
return JSON.parse(await readFile10(path, "utf8"));
|
|
3910
4370
|
}
|
|
3911
4371
|
async function acquireProtocolLock(projectRoot, operation, options = {}) {
|
|
3912
4372
|
const layout = await ensureStateLayout(projectRoot);
|
|
3913
|
-
const lockPath =
|
|
4373
|
+
const lockPath = join11(layout.locks, "protocol.lock");
|
|
3914
4374
|
const now = options.now ?? Date.now();
|
|
3915
4375
|
const staleAfterMs = options.staleAfterMs ?? 15 * 60 * 1e3;
|
|
3916
4376
|
const record = {
|
|
@@ -3923,7 +4383,7 @@ async function acquireProtocolLock(projectRoot, operation, options = {}) {
|
|
|
3923
4383
|
};
|
|
3924
4384
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
3925
4385
|
try {
|
|
3926
|
-
await
|
|
4386
|
+
await writeFile7(lockPath, JSON.stringify(record, null, 2), { flag: "wx" });
|
|
3927
4387
|
return {
|
|
3928
4388
|
path: lockPath,
|
|
3929
4389
|
operation,
|
|
@@ -3945,15 +4405,15 @@ async function acquireProtocolLock(projectRoot, operation, options = {}) {
|
|
|
3945
4405
|
if (now - current.heartbeat_at_ms <= staleAfterMs) {
|
|
3946
4406
|
throw new Error("protocol lock is active for operation " + current.operation, { cause: error });
|
|
3947
4407
|
}
|
|
3948
|
-
await
|
|
4408
|
+
await rename5(lockPath, lockPath + ".stale-" + randomUUID3());
|
|
3949
4409
|
}
|
|
3950
4410
|
}
|
|
3951
4411
|
throw new Error("unable to acquire protocol lock");
|
|
3952
4412
|
}
|
|
3953
4413
|
|
|
3954
4414
|
// ../core/dist/sync/synchronize.js
|
|
3955
|
-
import { lstat as lstat2, readFile as
|
|
3956
|
-
import { join as
|
|
4415
|
+
import { lstat as lstat2, readFile as readFile11, rm as rm4 } from "node:fs/promises";
|
|
4416
|
+
import { join as join12, resolve as resolve8 } from "node:path";
|
|
3957
4417
|
|
|
3958
4418
|
// ../core/dist/update/conflicts.js
|
|
3959
4419
|
function operationTargetPath(operation) {
|
|
@@ -4235,8 +4695,8 @@ async function pathExists(path) {
|
|
|
4235
4695
|
}
|
|
4236
4696
|
}
|
|
4237
4697
|
async function optionalContent(root, path) {
|
|
4238
|
-
const full =
|
|
4239
|
-
return await pathExists(full) ?
|
|
4698
|
+
const full = join12(root, path);
|
|
4699
|
+
return await pathExists(full) ? readFile11(full, "utf8") : null;
|
|
4240
4700
|
}
|
|
4241
4701
|
function manifestPayloadHash(manifest) {
|
|
4242
4702
|
const payload = { ...manifest };
|
|
@@ -4248,10 +4708,10 @@ async function loadBlob(root, client, artifactId, operation, requestId, dryRun)
|
|
|
4248
4708
|
return null;
|
|
4249
4709
|
}
|
|
4250
4710
|
const hash = operation.content_sha256;
|
|
4251
|
-
const cacheRoot =
|
|
4252
|
-
const cachePath =
|
|
4711
|
+
const cacheRoot = join12(root, ".harness", "cache", "server-artifacts", artifactId);
|
|
4712
|
+
const cachePath = join12(cacheRoot, "blobs", hash.replace(":", "_"));
|
|
4253
4713
|
if (await pathExists(cachePath) && await sha256File(cachePath) === hash) {
|
|
4254
|
-
return
|
|
4714
|
+
return readFile11(cachePath, "utf8");
|
|
4255
4715
|
}
|
|
4256
4716
|
const bytes = await client.downloadArtifactBlob(artifactId, hash, requestId);
|
|
4257
4717
|
if (bytes.byteLength !== operation.size_bytes || sha256Bytes(bytes) !== hash) {
|
|
@@ -4356,7 +4816,7 @@ async function planSingleArtifact(root, baseline, manifest, client, requestId, d
|
|
|
4356
4816
|
});
|
|
4357
4817
|
}
|
|
4358
4818
|
async function saveConflictReport(root, requestId, manifest, plan) {
|
|
4359
|
-
const reportPath =
|
|
4819
|
+
const reportPath = join12(root, ".harness", "reports", "conflicts-" + requestId + ".json");
|
|
4360
4820
|
await atomicWriteJson(reportPath, {
|
|
4361
4821
|
schema_version: 1,
|
|
4362
4822
|
request_id: requestId,
|
|
@@ -4367,7 +4827,7 @@ async function saveConflictReport(root, requestId, manifest, plan) {
|
|
|
4367
4827
|
});
|
|
4368
4828
|
}
|
|
4369
4829
|
async function synchronizeArtifacts(options, initialBaseline) {
|
|
4370
|
-
const root =
|
|
4830
|
+
const root = resolve8(options.projectRoot);
|
|
4371
4831
|
const projectId = options.project.project.project_id;
|
|
4372
4832
|
if (projectId === null) {
|
|
4373
4833
|
throw new Error("PROJECT_NOT_BOUND");
|
|
@@ -4474,7 +4934,7 @@ async function synchronizeArtifacts(options, initialBaseline) {
|
|
|
4474
4934
|
}
|
|
4475
4935
|
continue;
|
|
4476
4936
|
}
|
|
4477
|
-
await atomicWriteJson(
|
|
4937
|
+
await atomicWriteJson(join12(root, ".harness", "cache", "server-artifacts", manifest.artifact_id, "manifest.json"), manifest);
|
|
4478
4938
|
const lock = await acquireProtocolLock(root, "update", { requestId: options.requestId });
|
|
4479
4939
|
try {
|
|
4480
4940
|
const nextBaseline = applyBaselineUpdates(baseline, plan);
|
|
@@ -4544,7 +5004,7 @@ function baselineEntryFromOperation(manifest, operation, localContent) {
|
|
|
4544
5004
|
};
|
|
4545
5005
|
}
|
|
4546
5006
|
async function advanceBaselineFromArtifact(options, baseline) {
|
|
4547
|
-
const root =
|
|
5007
|
+
const root = resolve8(options.projectRoot);
|
|
4548
5008
|
if (options.manifest.project_version === null) {
|
|
4549
5009
|
throw new Error("artifact manifest missing project_version");
|
|
4550
5010
|
}
|
|
@@ -4621,7 +5081,8 @@ var PushWorkflowError = class extends Error {
|
|
|
4621
5081
|
};
|
|
4622
5082
|
var SHARED_MANAGED_ROOTS = [
|
|
4623
5083
|
".harness/knowledge",
|
|
4624
|
-
".harness/codebase"
|
|
5084
|
+
".harness/codebase",
|
|
5085
|
+
".harness/rules"
|
|
4625
5086
|
];
|
|
4626
5087
|
var SHARED_MANAGED_FILES = [
|
|
4627
5088
|
"AGENTS.md",
|
|
@@ -4644,29 +5105,29 @@ async function walkFiles(root, current, output) {
|
|
|
4644
5105
|
if (!await exists3(current)) {
|
|
4645
5106
|
return;
|
|
4646
5107
|
}
|
|
4647
|
-
for (const item2 of await
|
|
4648
|
-
const path =
|
|
5108
|
+
for (const item2 of await readdir6(current, { withFileTypes: true })) {
|
|
5109
|
+
const path = join13(current, item2.name);
|
|
4649
5110
|
if (item2.isSymbolicLink()) {
|
|
4650
5111
|
throw new PushWorkflowError("symlink is not pushable", 6, "UNSAFE_SYMLINK");
|
|
4651
5112
|
}
|
|
4652
5113
|
if (item2.isDirectory()) {
|
|
4653
5114
|
await walkFiles(root, path, output);
|
|
4654
5115
|
} else if (item2.isFile()) {
|
|
4655
|
-
output.push(normalizeManagedPath(
|
|
5116
|
+
output.push(normalizeManagedPath(relative2(root, path).replaceAll("\\", "/")));
|
|
4656
5117
|
}
|
|
4657
5118
|
}
|
|
4658
5119
|
}
|
|
4659
5120
|
async function walkArchiveSummaries(root, output) {
|
|
4660
|
-
const archiveRoot =
|
|
5121
|
+
const archiveRoot = join13(root, ".harness", "archive");
|
|
4661
5122
|
if (!await exists3(archiveRoot))
|
|
4662
5123
|
return;
|
|
4663
|
-
for (const item2 of await
|
|
5124
|
+
for (const item2 of await readdir6(archiveRoot, { withFileTypes: true })) {
|
|
4664
5125
|
if (item2.isSymbolicLink()) {
|
|
4665
5126
|
throw new PushWorkflowError("symlink is not pushable", 6, "UNSAFE_SYMLINK");
|
|
4666
5127
|
}
|
|
4667
5128
|
if (!item2.isDirectory())
|
|
4668
5129
|
continue;
|
|
4669
|
-
const summaryPath =
|
|
5130
|
+
const summaryPath = join13(archiveRoot, item2.name, "reports", "final", "summary-data.json");
|
|
4670
5131
|
try {
|
|
4671
5132
|
const stats = await lstat3(summaryPath);
|
|
4672
5133
|
if (stats.isSymbolicLink()) {
|
|
@@ -4679,7 +5140,7 @@ async function walkArchiveSummaries(root, output) {
|
|
|
4679
5140
|
continue;
|
|
4680
5141
|
throw error;
|
|
4681
5142
|
}
|
|
4682
|
-
const relativePath = normalizeManagedPath(
|
|
5143
|
+
const relativePath = normalizeManagedPath(relative2(root, summaryPath).replaceAll("\\", "/"));
|
|
4683
5144
|
if (ARCHIVE_SUMMARY_PATH.test(relativePath)) {
|
|
4684
5145
|
output.push(relativePath);
|
|
4685
5146
|
}
|
|
@@ -4694,19 +5155,19 @@ function enabledHarnessAgents(project) {
|
|
|
4694
5155
|
async function walkHarnessEntries(root, directory, output) {
|
|
4695
5156
|
if (!await exists3(directory))
|
|
4696
5157
|
return;
|
|
4697
|
-
for (const item2 of await
|
|
5158
|
+
for (const item2 of await readdir6(directory, { withFileTypes: true })) {
|
|
4698
5159
|
if (item2.name.startsWith("harness-")) {
|
|
4699
|
-
const path =
|
|
5160
|
+
const path = join13(directory, item2.name);
|
|
4700
5161
|
if (item2.isDirectory()) {
|
|
4701
5162
|
await walkFiles(root, path, output);
|
|
4702
5163
|
} else if (item2.isFile()) {
|
|
4703
|
-
output.push(normalizeManagedPath(
|
|
5164
|
+
output.push(normalizeManagedPath(relative2(root, path).replaceAll("\\", "/")));
|
|
4704
5165
|
}
|
|
4705
5166
|
}
|
|
4706
5167
|
}
|
|
4707
5168
|
}
|
|
4708
5169
|
async function managedFiles(projectRoot, project) {
|
|
4709
|
-
const root =
|
|
5170
|
+
const root = resolve9(projectRoot);
|
|
4710
5171
|
const paths = [];
|
|
4711
5172
|
const adapters = getAdapters(enabledHarnessAgents(project));
|
|
4712
5173
|
const managedFiles2 = [
|
|
@@ -4715,26 +5176,26 @@ async function managedFiles(projectRoot, project) {
|
|
|
4715
5176
|
...adapters.some((adapter) => adapter.name === "codebuddy") ? ["CODEBUDDY.md"] : []
|
|
4716
5177
|
];
|
|
4717
5178
|
for (const path of managedFiles2) {
|
|
4718
|
-
if (await exists3(
|
|
5179
|
+
if (await exists3(join13(root, path))) {
|
|
4719
5180
|
paths.push(path);
|
|
4720
5181
|
}
|
|
4721
5182
|
}
|
|
4722
5183
|
for (const path of SHARED_MANAGED_ROOTS) {
|
|
4723
|
-
await walkFiles(root,
|
|
5184
|
+
await walkFiles(root, join13(root, path), paths);
|
|
4724
5185
|
}
|
|
4725
5186
|
await walkArchiveSummaries(root, paths);
|
|
4726
5187
|
for (const adapter of adapters) {
|
|
4727
5188
|
if (adapter.rulesRoot !== null) {
|
|
4728
|
-
await
|
|
5189
|
+
await walkHarnessEntries(root, join13(root, adapter.rulesRoot), paths);
|
|
4729
5190
|
}
|
|
4730
|
-
await walkHarnessEntries(root,
|
|
5191
|
+
await walkHarnessEntries(root, join13(root, adapter.skillsRoot), paths);
|
|
4731
5192
|
if (adapter.agentsRoot !== null) {
|
|
4732
|
-
await walkHarnessEntries(root,
|
|
5193
|
+
await walkHarnessEntries(root, join13(root, adapter.agentsRoot), paths);
|
|
4733
5194
|
}
|
|
4734
5195
|
}
|
|
4735
5196
|
const result = {};
|
|
4736
5197
|
for (const path of [...new Set(paths)].sort()) {
|
|
4737
|
-
result[path] = await
|
|
5198
|
+
result[path] = await readFile12(join13(root, path), "utf8");
|
|
4738
5199
|
}
|
|
4739
5200
|
return result;
|
|
4740
5201
|
}
|
|
@@ -4743,14 +5204,14 @@ function proposalBaseline(baseline) {
|
|
|
4743
5204
|
}
|
|
4744
5205
|
async function readProject(root) {
|
|
4745
5206
|
try {
|
|
4746
|
-
return projectConfigSchema.parse(parseYaml5(await
|
|
5207
|
+
return projectConfigSchema.parse(parseYaml5(await readFile12(join13(root, ".harness", "project.yaml"), "utf8")));
|
|
4747
5208
|
} catch {
|
|
4748
5209
|
throw new PushWorkflowError("project configuration is missing or invalid", 3, "PROJECT_CONFIG_INVALID");
|
|
4749
5210
|
}
|
|
4750
5211
|
}
|
|
4751
5212
|
async function readOptionalJson(path) {
|
|
4752
5213
|
try {
|
|
4753
|
-
return JSON.parse(await
|
|
5214
|
+
return JSON.parse(await readFile12(path, "utf8"));
|
|
4754
5215
|
} catch (error) {
|
|
4755
5216
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
4756
5217
|
return null;
|
|
@@ -4762,7 +5223,7 @@ async function clientIdFor(root, explicit) {
|
|
|
4762
5223
|
if (explicit !== void 0) {
|
|
4763
5224
|
return explicit;
|
|
4764
5225
|
}
|
|
4765
|
-
const path =
|
|
5226
|
+
const path = join13(root, ".harness", "state", "local", "client.json");
|
|
4766
5227
|
const existing = await readOptionalJson(path);
|
|
4767
5228
|
if (typeof existing?.client_id === "string" && /^cli_[A-Za-z0-9_-]+$/.test(existing.client_id)) {
|
|
4768
5229
|
return existing.client_id;
|
|
@@ -4958,7 +5419,7 @@ async function bindProject(root, project, baseline, projectId) {
|
|
|
4958
5419
|
return { project: nextProject, baseline: nextBaseline };
|
|
4959
5420
|
}
|
|
4960
5421
|
async function pushProject(options) {
|
|
4961
|
-
const root =
|
|
5422
|
+
const root = resolve9(options.projectRoot);
|
|
4962
5423
|
let project = await readProject(root);
|
|
4963
5424
|
let baseline = await readBaseline(root);
|
|
4964
5425
|
const profile = parseHarnessProfile(project.project.profiles[0]);
|
|
@@ -5024,7 +5485,7 @@ async function pushProject(options) {
|
|
|
5024
5485
|
if (options.confirmProposal !== void 0 && !await options.confirmProposal(preview)) {
|
|
5025
5486
|
return { preview, proposalId: null, projectId: project.project.project_id, cancelled: true };
|
|
5026
5487
|
}
|
|
5027
|
-
const workflowPath =
|
|
5488
|
+
const workflowPath = join13(root, ".harness", "state", "local", "push-workflow.json");
|
|
5028
5489
|
const priorWorkflow = await readOptionalJson(workflowPath);
|
|
5029
5490
|
const provisionalRequestId = priorWorkflow?.local_project_key === project.project.local_project_key ? priorWorkflow.request_id : uuidV7();
|
|
5030
5491
|
const lock = await acquireProtocolLock(root, "push", { requestId: provisionalRequestId });
|
|
@@ -5179,7 +5640,7 @@ async function pushProject(options) {
|
|
|
5179
5640
|
pushWarning = "BASELINE_ADVANCE_DEFERRED";
|
|
5180
5641
|
}
|
|
5181
5642
|
}
|
|
5182
|
-
await atomicWriteJson(
|
|
5643
|
+
await atomicWriteJson(join13(root, ".harness", "state", "local", "push-results", finalized.proposal_id + ".json"), {
|
|
5183
5644
|
schema_version: 1,
|
|
5184
5645
|
request_id: requestId,
|
|
5185
5646
|
project_id: projectId,
|
|
@@ -5224,14 +5685,14 @@ async function pushProject(options) {
|
|
|
5224
5685
|
}
|
|
5225
5686
|
|
|
5226
5687
|
// ../core/dist/state/cleanup.js
|
|
5227
|
-
import { readFile as
|
|
5228
|
-
import { join as
|
|
5688
|
+
import { readFile as readFile13, readdir as readdir7, rm as rm6 } from "node:fs/promises";
|
|
5689
|
+
import { join as join14 } from "node:path";
|
|
5229
5690
|
function isSafeEntryName(name) {
|
|
5230
5691
|
return name.length > 0 && !name.includes("/") && !name.includes("\\") && name !== "." && name !== ".." && !name.includes("\0");
|
|
5231
5692
|
}
|
|
5232
5693
|
async function listDir(path) {
|
|
5233
5694
|
try {
|
|
5234
|
-
return await
|
|
5695
|
+
return await readdir7(path);
|
|
5235
5696
|
} catch (error) {
|
|
5236
5697
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
5237
5698
|
return [];
|
|
@@ -5249,7 +5710,7 @@ async function cleanupProject(options) {
|
|
|
5249
5710
|
continue;
|
|
5250
5711
|
let journal;
|
|
5251
5712
|
try {
|
|
5252
|
-
journal = JSON.parse(await
|
|
5713
|
+
journal = JSON.parse(await readFile13(join14(layout.transactions, name, "journal.json"), "utf8"));
|
|
5253
5714
|
} catch {
|
|
5254
5715
|
continue;
|
|
5255
5716
|
}
|
|
@@ -5267,7 +5728,7 @@ async function cleanupProject(options) {
|
|
|
5267
5728
|
continue;
|
|
5268
5729
|
pruned.push(entry.id);
|
|
5269
5730
|
if (!options.dryRun) {
|
|
5270
|
-
await rm6(
|
|
5731
|
+
await rm6(join14(layout.transactions, entry.id), { recursive: true, force: true });
|
|
5271
5732
|
}
|
|
5272
5733
|
}
|
|
5273
5734
|
}
|
|
@@ -5276,7 +5737,7 @@ async function cleanupProject(options) {
|
|
|
5276
5737
|
continue;
|
|
5277
5738
|
removedCache.push(name);
|
|
5278
5739
|
if (!options.dryRun) {
|
|
5279
|
-
await rm6(
|
|
5740
|
+
await rm6(join14(layout.serverArtifacts, name), { recursive: true, force: true });
|
|
5280
5741
|
}
|
|
5281
5742
|
}
|
|
5282
5743
|
return {
|
|
@@ -5336,10 +5797,10 @@ var SKILL_TARGET_AGENTS = Object.freeze([
|
|
|
5336
5797
|
]);
|
|
5337
5798
|
|
|
5338
5799
|
// ../core/dist/transaction/recovery.js
|
|
5339
|
-
import { readFile as
|
|
5340
|
-
import { join as
|
|
5800
|
+
import { readFile as readFile14, readdir as readdir8, rm as rm7, stat as stat4 } from "node:fs/promises";
|
|
5801
|
+
import { join as join15 } from "node:path";
|
|
5341
5802
|
async function recoverTransaction(projectRoot, transactionId) {
|
|
5342
|
-
const journal = JSON.parse(await
|
|
5803
|
+
const journal = JSON.parse(await readFile14(join15(stateLayout(projectRoot).transactions, transactionId, "journal.json"), "utf8"));
|
|
5343
5804
|
if (journal.state === "committed") {
|
|
5344
5805
|
return { transactionId, status: "committed" };
|
|
5345
5806
|
}
|
|
@@ -5356,7 +5817,7 @@ async function listTransactions(projectRoot) {
|
|
|
5356
5817
|
const root = stateLayout(projectRoot).transactions;
|
|
5357
5818
|
let names;
|
|
5358
5819
|
try {
|
|
5359
|
-
names = await
|
|
5820
|
+
names = await readdir8(root);
|
|
5360
5821
|
} catch (error) {
|
|
5361
5822
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
5362
5823
|
return [];
|
|
@@ -5366,7 +5827,7 @@ async function listTransactions(projectRoot) {
|
|
|
5366
5827
|
const transactions = [];
|
|
5367
5828
|
for (const transactionId of names) {
|
|
5368
5829
|
try {
|
|
5369
|
-
const journal = JSON.parse(await
|
|
5830
|
+
const journal = JSON.parse(await readFile14(join15(root, transactionId, "journal.json"), "utf8"));
|
|
5370
5831
|
transactions.push({
|
|
5371
5832
|
transactionId,
|
|
5372
5833
|
kind: journal.kind,
|
|
@@ -5383,7 +5844,7 @@ async function pendingTransactions(projectRoot) {
|
|
|
5383
5844
|
}
|
|
5384
5845
|
async function pathExists2(path) {
|
|
5385
5846
|
try {
|
|
5386
|
-
await
|
|
5847
|
+
await stat4(path);
|
|
5387
5848
|
return true;
|
|
5388
5849
|
} catch (error) {
|
|
5389
5850
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
@@ -5397,11 +5858,11 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
|
|
|
5397
5858
|
if (latest === void 0) {
|
|
5398
5859
|
throw new Error("no committed update transaction is available for rollback");
|
|
5399
5860
|
}
|
|
5400
|
-
const transactionRoot =
|
|
5401
|
-
const journal = JSON.parse(await
|
|
5402
|
-
const after = JSON.parse(await
|
|
5861
|
+
const transactionRoot = join15(stateLayout(projectRoot).transactions, latest.transactionId);
|
|
5862
|
+
const journal = JSON.parse(await readFile14(join15(transactionRoot, "journal.json"), "utf8"));
|
|
5863
|
+
const after = JSON.parse(await readFile14(join15(transactionRoot, "after", "manifest.json"), "utf8"));
|
|
5403
5864
|
for (const entry of after) {
|
|
5404
|
-
const target =
|
|
5865
|
+
const target = join15(projectRoot, entry.path);
|
|
5405
5866
|
const exists6 = await pathExists2(target);
|
|
5406
5867
|
if (exists6 !== entry.exists || exists6 && await sha256File(target) !== entry.hash) {
|
|
5407
5868
|
throw new Error("cannot rollback dirty path: " + entry.path);
|
|
@@ -5414,10 +5875,10 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
|
|
|
5414
5875
|
continue;
|
|
5415
5876
|
}
|
|
5416
5877
|
seen.add(snapshot.path);
|
|
5417
|
-
const target =
|
|
5878
|
+
const target = join15(projectRoot, snapshot.path);
|
|
5418
5879
|
const exists6 = await pathExists2(target);
|
|
5419
5880
|
if (snapshot.existed && snapshot.snapshot_name !== null) {
|
|
5420
|
-
const content = await
|
|
5881
|
+
const content = await readFile14(join15(transactionRoot, "before", snapshot.snapshot_name));
|
|
5421
5882
|
operations.push({
|
|
5422
5883
|
operation: exists6 ? "modify" : "add",
|
|
5423
5884
|
path: snapshot.path,
|
|
@@ -5446,7 +5907,7 @@ async function cleanupOldTransactions(projectRoot, now = /* @__PURE__ */ new Dat
|
|
|
5446
5907
|
if (!removable) {
|
|
5447
5908
|
continue;
|
|
5448
5909
|
}
|
|
5449
|
-
await rm7(
|
|
5910
|
+
await rm7(join15(stateLayout(projectRoot).transactions, item2.transactionId), {
|
|
5450
5911
|
recursive: true,
|
|
5451
5912
|
force: true
|
|
5452
5913
|
});
|
|
@@ -5456,8 +5917,8 @@ async function cleanupOldTransactions(projectRoot, now = /* @__PURE__ */ new Dat
|
|
|
5456
5917
|
}
|
|
5457
5918
|
|
|
5458
5919
|
// ../core/dist/update/update.js
|
|
5459
|
-
import { readFile as
|
|
5460
|
-
import { join as
|
|
5920
|
+
import { readFile as readFile15 } from "node:fs/promises";
|
|
5921
|
+
import { join as join16, resolve as resolve10 } from "node:path";
|
|
5461
5922
|
import { parse as parseYaml8 } from "yaml";
|
|
5462
5923
|
var UpdateWorkflowError = class extends Error {
|
|
5463
5924
|
exitCode;
|
|
@@ -5470,10 +5931,10 @@ var UpdateWorkflowError = class extends Error {
|
|
|
5470
5931
|
}
|
|
5471
5932
|
};
|
|
5472
5933
|
async function updateProject(options) {
|
|
5473
|
-
const root =
|
|
5934
|
+
const root = resolve10(options.projectRoot);
|
|
5474
5935
|
let project;
|
|
5475
5936
|
try {
|
|
5476
|
-
project = projectConfigSchema.parse(parseYaml8(await
|
|
5937
|
+
project = projectConfigSchema.parse(parseYaml8(await readFile15(join16(root, ".harness", "project.yaml"), "utf8")));
|
|
5477
5938
|
} catch {
|
|
5478
5939
|
throw new UpdateWorkflowError("project configuration is invalid", 3, "PROJECT_CONFIG_INVALID");
|
|
5479
5940
|
}
|
|
@@ -5523,6 +5984,13 @@ async function updateProject(options) {
|
|
|
5523
5984
|
...options.confirmConflictStrategy === void 0 ? {} : { confirmConflictStrategy: options.confirmConflictStrategy },
|
|
5524
5985
|
...options.transactionOptions === void 0 ? {} : { transactionOptions: options.transactionOptions }
|
|
5525
5986
|
}, baseline);
|
|
5987
|
+
if (!options.dryRun) {
|
|
5988
|
+
const agents = project.adapters.enabled.flatMap((agent) => {
|
|
5989
|
+
const parsed = harnessAgentSchema.safeParse(agent);
|
|
5990
|
+
return parsed.success ? [parsed.data] : [];
|
|
5991
|
+
});
|
|
5992
|
+
await synchronizeProjectRules(root, agents, project.adapter_options?.codebuddy?.surface ?? "both");
|
|
5993
|
+
}
|
|
5526
5994
|
return result;
|
|
5527
5995
|
} catch (error) {
|
|
5528
5996
|
if (error instanceof UpdateWorkflowError) {
|
|
@@ -5548,8 +6016,8 @@ async function updateProject(options) {
|
|
|
5548
6016
|
}
|
|
5549
6017
|
|
|
5550
6018
|
// src/config/init-config.ts
|
|
5551
|
-
import { isAbsolute as isAbsolute2, join as
|
|
5552
|
-
import { readFile as
|
|
6019
|
+
import { isAbsolute as isAbsolute2, join as join17 } from "node:path";
|
|
6020
|
+
import { readFile as readFile16 } from "node:fs/promises";
|
|
5553
6021
|
var InitConfigurationError = class extends Error {
|
|
5554
6022
|
exitCode;
|
|
5555
6023
|
code;
|
|
@@ -5632,9 +6100,9 @@ function hasOwn(record, key) {
|
|
|
5632
6100
|
async function resolveInitConfig(cwd, flags, prompts = {}, warnings = []) {
|
|
5633
6101
|
let fileConfig = {};
|
|
5634
6102
|
if (flags.config !== void 0) {
|
|
5635
|
-
const path = isAbsolute2(flags.config) ? flags.config :
|
|
6103
|
+
const path = isAbsolute2(flags.config) ? flags.config : join17(cwd, flags.config);
|
|
5636
6104
|
try {
|
|
5637
|
-
fileConfig = JSON.parse(await
|
|
6105
|
+
fileConfig = JSON.parse(await readFile16(path, "utf8"));
|
|
5638
6106
|
} catch (error) {
|
|
5639
6107
|
throw new InitConfigurationError(
|
|
5640
6108
|
"unable to read init config: " + (error instanceof Error ? error.message : String(error)),
|
|
@@ -5716,8 +6184,8 @@ function serializeCliResult(result) {
|
|
|
5716
6184
|
}
|
|
5717
6185
|
|
|
5718
6186
|
// src/config/codebuddy-setup.ts
|
|
5719
|
-
import { mkdir as
|
|
5720
|
-
import { basename as
|
|
6187
|
+
import { mkdir as mkdir6, readFile as readFile17, readdir as readdir9, stat as stat5, writeFile as writeFile8 } from "node:fs/promises";
|
|
6188
|
+
import { basename as basename4, dirname as dirname6, extname as extname2, join as join18 } from "node:path";
|
|
5721
6189
|
var MANAGED_RULE_NAMES = /* @__PURE__ */ new Set([
|
|
5722
6190
|
"harness-general.md",
|
|
5723
6191
|
"harness-general.mdc",
|
|
@@ -5728,7 +6196,7 @@ var SENSITIVE_ASSIGNMENT = /(?:password|passwd|token|secret|access[_-]?key|priva
|
|
|
5728
6196
|
var PRIVATE_KEY_BLOCK = /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/i;
|
|
5729
6197
|
async function exists4(path) {
|
|
5730
6198
|
try {
|
|
5731
|
-
await
|
|
6199
|
+
await stat5(path);
|
|
5732
6200
|
return true;
|
|
5733
6201
|
} catch {
|
|
5734
6202
|
return false;
|
|
@@ -5736,35 +6204,59 @@ async function exists4(path) {
|
|
|
5736
6204
|
}
|
|
5737
6205
|
async function readJsonObject(path) {
|
|
5738
6206
|
try {
|
|
5739
|
-
const parsed = JSON.parse(await
|
|
6207
|
+
const parsed = JSON.parse(await readFile17(path, "utf8"));
|
|
5740
6208
|
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
5741
6209
|
} catch (error) {
|
|
5742
6210
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") return {};
|
|
5743
6211
|
return null;
|
|
5744
6212
|
}
|
|
5745
6213
|
}
|
|
5746
|
-
async function inspectCodeBuddySetup(projectRoot) {
|
|
5747
|
-
const rulesRoot =
|
|
5748
|
-
let
|
|
6214
|
+
async function inspectCodeBuddySetup(projectRoot, surface = "both") {
|
|
6215
|
+
const rulesRoot = join18(projectRoot, ".claude", "rules");
|
|
6216
|
+
let ruleNames = [];
|
|
5749
6217
|
try {
|
|
5750
|
-
|
|
6218
|
+
ruleNames = (await readdir9(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
6219
|
} catch (error) {
|
|
5752
6220
|
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
|
|
5753
6221
|
}
|
|
5754
|
-
const
|
|
6222
|
+
const claudeRules = [];
|
|
6223
|
+
const currentClaudeRules = [];
|
|
6224
|
+
const conflictingClaudeRules = [];
|
|
6225
|
+
for (const name of ruleNames) {
|
|
6226
|
+
const sourceContent = await readFile17(join18(rulesRoot, name), "utf8");
|
|
6227
|
+
const targetContents = await Promise.all(
|
|
6228
|
+
destinationTargets(projectRoot, surface, name).map(async (target) => {
|
|
6229
|
+
try {
|
|
6230
|
+
return await readFile17(target, "utf8");
|
|
6231
|
+
} catch (error) {
|
|
6232
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return null;
|
|
6233
|
+
throw error;
|
|
6234
|
+
}
|
|
6235
|
+
})
|
|
6236
|
+
);
|
|
6237
|
+
if (targetContents.some((content) => content === null)) claudeRules.push(name);
|
|
6238
|
+
if (targetContents.some((content) => content !== null && content !== sourceContent)) {
|
|
6239
|
+
conflictingClaudeRules.push(name);
|
|
6240
|
+
} else if (targetContents.length > 0 && targetContents.every((content) => content === sourceContent)) {
|
|
6241
|
+
currentClaudeRules.push(name);
|
|
6242
|
+
}
|
|
6243
|
+
}
|
|
6244
|
+
const mcp = await readJsonObject(join18(projectRoot, ".mcp.json"));
|
|
5755
6245
|
const servers = mcp?.mcpServers;
|
|
5756
6246
|
const configured = servers !== null && typeof servers === "object" && !Array.isArray(servers) && Object.prototype.hasOwnProperty.call(servers, "codegraph");
|
|
5757
6247
|
return {
|
|
5758
6248
|
claudeRules,
|
|
5759
|
-
|
|
6249
|
+
currentClaudeRules,
|
|
6250
|
+
conflictingClaudeRules,
|
|
6251
|
+
hasCodeGraphIndex: await exists4(join18(projectRoot, ".codegraph")),
|
|
5760
6252
|
codeGraphConfigured: configured
|
|
5761
6253
|
};
|
|
5762
6254
|
}
|
|
5763
6255
|
function destinationTargets(root, surface, name) {
|
|
5764
|
-
const stem =
|
|
6256
|
+
const stem = basename4(name, extname2(name));
|
|
5765
6257
|
const targets = [];
|
|
5766
|
-
if (surface !== "cli") targets.push(
|
|
5767
|
-
if (surface !== "ide") targets.push(
|
|
6258
|
+
if (surface !== "cli") targets.push(join18(root, ".codebuddy", ".rules", `${stem}.mdc`));
|
|
6259
|
+
if (surface !== "ide") targets.push(join18(root, ".codebuddy", "rules", `${stem}.md`));
|
|
5768
6260
|
return targets;
|
|
5769
6261
|
}
|
|
5770
6262
|
async function applyCodeBuddySetup(options) {
|
|
@@ -5776,10 +6268,10 @@ async function applyCodeBuddySetup(options) {
|
|
|
5776
6268
|
warnings: []
|
|
5777
6269
|
};
|
|
5778
6270
|
if (options.syncClaudeRules) {
|
|
5779
|
-
const plan = await inspectCodeBuddySetup(options.projectRoot);
|
|
6271
|
+
const plan = await inspectCodeBuddySetup(options.projectRoot, options.surface);
|
|
5780
6272
|
for (const name of plan.claudeRules) {
|
|
5781
|
-
const source =
|
|
5782
|
-
const content = await
|
|
6273
|
+
const source = join18(options.projectRoot, ".claude", "rules", name);
|
|
6274
|
+
const content = await readFile17(source, "utf8");
|
|
5783
6275
|
if (SENSITIVE_ASSIGNMENT.test(content) || PRIVATE_KEY_BLOCK.test(content)) {
|
|
5784
6276
|
result.skippedSensitive.push(name);
|
|
5785
6277
|
continue;
|
|
@@ -5789,14 +6281,14 @@ async function applyCodeBuddySetup(options) {
|
|
|
5789
6281
|
result.preserved.push(target);
|
|
5790
6282
|
continue;
|
|
5791
6283
|
}
|
|
5792
|
-
await
|
|
5793
|
-
await
|
|
6284
|
+
await mkdir6(dirname6(target), { recursive: true });
|
|
6285
|
+
await writeFile8(target, content, { encoding: "utf8", flag: "wx" });
|
|
5794
6286
|
result.copied.push(target);
|
|
5795
6287
|
}
|
|
5796
6288
|
}
|
|
5797
6289
|
}
|
|
5798
6290
|
if (options.configureCodeGraph) {
|
|
5799
|
-
const path =
|
|
6291
|
+
const path = join18(options.projectRoot, ".mcp.json");
|
|
5800
6292
|
const current = await readJsonObject(path);
|
|
5801
6293
|
if (current === null) {
|
|
5802
6294
|
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 +6303,7 @@ async function applyCodeBuddySetup(options) {
|
|
|
5811
6303
|
codegraph: { command: "codegraph", args: ["serve", "--mcp"] }
|
|
5812
6304
|
}
|
|
5813
6305
|
};
|
|
5814
|
-
await
|
|
6306
|
+
await writeFile8(path, JSON.stringify(next, null, 2) + "\n", "utf8");
|
|
5815
6307
|
result.mcpUpdated = true;
|
|
5816
6308
|
}
|
|
5817
6309
|
}
|
|
@@ -5820,13 +6312,13 @@ async function applyCodeBuddySetup(options) {
|
|
|
5820
6312
|
}
|
|
5821
6313
|
|
|
5822
6314
|
// src/commands/refresh.ts
|
|
5823
|
-
import { readFile as
|
|
5824
|
-
import { join as
|
|
6315
|
+
import { readFile as readFile18 } from "node:fs/promises";
|
|
6316
|
+
import { join as join19 } from "node:path";
|
|
5825
6317
|
import { parse as parseYaml9 } from "yaml";
|
|
5826
6318
|
async function detectProject(root) {
|
|
5827
6319
|
let content;
|
|
5828
6320
|
try {
|
|
5829
|
-
content = await
|
|
6321
|
+
content = await readFile18(join19(root, ".harness", "project.yaml"), "utf8");
|
|
5830
6322
|
} catch (error) {
|
|
5831
6323
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
5832
6324
|
return { status: "absent" };
|
|
@@ -6055,7 +6547,13 @@ function agentMenuLines(installedProfiles) {
|
|
|
6055
6547
|
}
|
|
6056
6548
|
async function configureCodeBuddyExtras(agents, surface, options, dependencies) {
|
|
6057
6549
|
if (!agents.includes("codebuddy")) return;
|
|
6058
|
-
const plan = await inspectCodeBuddySetup(dependencies.cwd);
|
|
6550
|
+
const plan = await inspectCodeBuddySetup(dependencies.cwd, surface);
|
|
6551
|
+
if (plan.conflictingClaudeRules.length > 0) {
|
|
6552
|
+
dependencies.stderr(
|
|
6553
|
+
`\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(", ")}
|
|
6554
|
+
`
|
|
6555
|
+
);
|
|
6556
|
+
}
|
|
6059
6557
|
let syncClaudeRules = false;
|
|
6060
6558
|
let configureCodeGraph = false;
|
|
6061
6559
|
if (plan.claudeRules.length > 0) {
|
|
@@ -6652,12 +7150,94 @@ async function runUpdate(options, dependencies) {
|
|
|
6652
7150
|
}
|
|
6653
7151
|
}
|
|
6654
7152
|
|
|
7153
|
+
// src/commands/rules-sync.ts
|
|
7154
|
+
function configuredAgents(config) {
|
|
7155
|
+
const agents = config.adapters.enabled.flatMap((value) => {
|
|
7156
|
+
const parsed = harnessAgentSchema.safeParse(value);
|
|
7157
|
+
return parsed.success ? [parsed.data] : [];
|
|
7158
|
+
});
|
|
7159
|
+
return sortHarnessAgents(agents.length > 0 ? agents : ["claude-code"]);
|
|
7160
|
+
}
|
|
7161
|
+
function configuredSurface(config, override) {
|
|
7162
|
+
const value = override ?? config.adapter_options?.codebuddy?.surface ?? "both";
|
|
7163
|
+
if (value === "both" || value === "ide" || value === "cli") return value;
|
|
7164
|
+
throw new Error("codebuddy surface \u5FC5\u987B\u4E3A both\u3001ide \u6216 cli");
|
|
7165
|
+
}
|
|
7166
|
+
async function runRulesSync(options, dependencies) {
|
|
7167
|
+
const detection = await detectProject(dependencies.cwd);
|
|
7168
|
+
if (detection.status === "absent") {
|
|
7169
|
+
dependencies.stderr("\u5C1A\u672A\u521D\u59CB\u5316 Hunter Harness\uFF1B\u8BF7\u5148\u8FD0\u884C `hunter-harness`\u3002\n");
|
|
7170
|
+
return 3;
|
|
7171
|
+
}
|
|
7172
|
+
if (detection.status === "invalid") {
|
|
7173
|
+
dependencies.stderr("PROJECT_CONFIG_INVALID\uFF1A.harness/project.yaml \u65E0\u6548\n");
|
|
7174
|
+
return 3;
|
|
7175
|
+
}
|
|
7176
|
+
try {
|
|
7177
|
+
const agents = options.agents === void 0 ? configuredAgents(detection.config) : parseAgentsInput(options.agents);
|
|
7178
|
+
const projections = await synchronizeProjectRules(
|
|
7179
|
+
dependencies.cwd,
|
|
7180
|
+
agents,
|
|
7181
|
+
configuredSurface(detection.config, options.codebuddySurface)
|
|
7182
|
+
);
|
|
7183
|
+
const learning = options.learn === false ? null : await synchronizeRuleCandidates(dependencies.cwd);
|
|
7184
|
+
const exitCode = projections.conflicts.length > 0 ? 5 : 0;
|
|
7185
|
+
const payload = {
|
|
7186
|
+
schema_version: 1,
|
|
7187
|
+
command: "rules-sync",
|
|
7188
|
+
request_id: uuidV7(),
|
|
7189
|
+
dry_run: false,
|
|
7190
|
+
ok: exitCode === 0,
|
|
7191
|
+
exit_code: exitCode,
|
|
7192
|
+
project_id: detection.config.project.project_id,
|
|
7193
|
+
summary: {
|
|
7194
|
+
migrated: projections.migrated.length,
|
|
7195
|
+
projected: projections.written.length,
|
|
7196
|
+
removed: projections.removed.length,
|
|
7197
|
+
unchanged: projections.unchanged.length,
|
|
7198
|
+
conflicts: projections.conflicts.length,
|
|
7199
|
+
agent_specific: projections.agent_specific.length,
|
|
7200
|
+
rule_candidates: learning?.candidates ?? 0
|
|
7201
|
+
},
|
|
7202
|
+
items: [
|
|
7203
|
+
...projections.migrated.map((path) => ({ path, status: "migrated" })),
|
|
7204
|
+
...projections.written.map((path) => ({ path, status: "projected" })),
|
|
7205
|
+
...projections.agent_specific.map((path) => ({ path, status: "agent-specific" })),
|
|
7206
|
+
...learning === null ? [] : [{
|
|
7207
|
+
path: learning.path,
|
|
7208
|
+
status: learning.changed ? "updated" : "unchanged",
|
|
7209
|
+
candidates: learning.candidates,
|
|
7210
|
+
scanned: learning.scanned
|
|
7211
|
+
}]
|
|
7212
|
+
],
|
|
7213
|
+
warnings: [
|
|
7214
|
+
...projections.conflicts.map((path) => `\u89C4\u5219\u5206\u6B67\u672A\u8986\u76D6\uFF1A${path}`),
|
|
7215
|
+
...projections.agent_specific.map((path) => `\u4FDD\u7559 Agent \u4E13\u5C5E\u89C4\u5219\uFF1A${path}`)
|
|
7216
|
+
],
|
|
7217
|
+
errors: []
|
|
7218
|
+
};
|
|
7219
|
+
if (options.json === true) {
|
|
7220
|
+
dependencies.stdout(serializeCliResult(payload));
|
|
7221
|
+
} else {
|
|
7222
|
+
dependencies.stdout(
|
|
7223
|
+
`\u89C4\u5219\u540C\u6B65\uFF1A\u8FC1\u79FB ${payload.summary.migrated}\uFF0C\u6295\u5F71 ${payload.summary.projected}\uFF0C\u51B2\u7A81 ${payload.summary.conflicts}\uFF0C\u5019\u9009 ${payload.summary.rule_candidates}\u3002
|
|
7224
|
+
`
|
|
7225
|
+
);
|
|
7226
|
+
for (const warning of payload.warnings) dependencies.stderr(warning + "\n");
|
|
7227
|
+
}
|
|
7228
|
+
return exitCode;
|
|
7229
|
+
} catch (error) {
|
|
7230
|
+
dependencies.stderr((error instanceof Error ? error.message : String(error)) + "\n");
|
|
7231
|
+
return 1;
|
|
7232
|
+
}
|
|
7233
|
+
}
|
|
7234
|
+
|
|
6655
7235
|
// src/commands/recovery.ts
|
|
6656
|
-
import { stat as
|
|
6657
|
-
import { join as
|
|
7236
|
+
import { stat as stat6 } from "node:fs/promises";
|
|
7237
|
+
import { join as join20 } from "node:path";
|
|
6658
7238
|
async function exists5(path) {
|
|
6659
7239
|
try {
|
|
6660
|
-
await
|
|
7240
|
+
await stat6(path);
|
|
6661
7241
|
return true;
|
|
6662
7242
|
} catch {
|
|
6663
7243
|
return false;
|
|
@@ -6714,7 +7294,7 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
|
|
|
6714
7294
|
return 5;
|
|
6715
7295
|
}
|
|
6716
7296
|
}
|
|
6717
|
-
const initialized = await exists5(
|
|
7297
|
+
const initialized = await exists5(join20(dependencies.cwd, ".harness", "project.yaml"));
|
|
6718
7298
|
if (!initialized || options.nonInteractive === true || explicitConfigure(options)) {
|
|
6719
7299
|
return null;
|
|
6720
7300
|
}
|
|
@@ -6757,8 +7337,8 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
|
|
|
6757
7337
|
}
|
|
6758
7338
|
|
|
6759
7339
|
// src/workflow-data/resolve.ts
|
|
6760
|
-
import { cp, mkdir as
|
|
6761
|
-
import { dirname as
|
|
7340
|
+
import { cp, mkdir as mkdir7, readdir as readdir10, readFile as readFile19, rm as rm8, stat as stat7 } from "node:fs/promises";
|
|
7341
|
+
import { dirname as dirname7, join as join21, relative as relative3 } from "node:path";
|
|
6762
7342
|
import { fileURLToPath } from "node:url";
|
|
6763
7343
|
var WorkflowDataResolutionError = class extends Error {
|
|
6764
7344
|
code;
|
|
@@ -6772,7 +7352,7 @@ var WorkflowDataResolutionError = class extends Error {
|
|
|
6772
7352
|
};
|
|
6773
7353
|
async function pathExists3(path) {
|
|
6774
7354
|
try {
|
|
6775
|
-
await
|
|
7355
|
+
await stat7(path);
|
|
6776
7356
|
return true;
|
|
6777
7357
|
} catch {
|
|
6778
7358
|
return false;
|
|
@@ -6792,17 +7372,17 @@ function workflowPackageName(family, env) {
|
|
|
6792
7372
|
return `${scope}/workflow-${family}`;
|
|
6793
7373
|
}
|
|
6794
7374
|
async function listFilesRecursive(root, base = root) {
|
|
6795
|
-
const entries = await
|
|
7375
|
+
const entries = await readdir10(root, { withFileTypes: true });
|
|
6796
7376
|
const files = [];
|
|
6797
7377
|
for (const entry of entries) {
|
|
6798
|
-
const absolute =
|
|
7378
|
+
const absolute = join21(root, entry.name);
|
|
6799
7379
|
if (entry.isDirectory()) {
|
|
6800
7380
|
files.push(...await listFilesRecursive(absolute, base));
|
|
6801
7381
|
continue;
|
|
6802
7382
|
}
|
|
6803
7383
|
if (!entry.isFile()) continue;
|
|
6804
|
-
const rel =
|
|
6805
|
-
files.push({ path: rel, content: await
|
|
7384
|
+
const rel = relative3(base, absolute).replaceAll("\\", "/");
|
|
7385
|
+
files.push({ path: rel, content: await readFile19(absolute, "utf8") });
|
|
6806
7386
|
}
|
|
6807
7387
|
return files;
|
|
6808
7388
|
}
|
|
@@ -6815,7 +7395,7 @@ async function verifyWorkflowPackageIntegrity(resourcesRoot) {
|
|
|
6815
7395
|
const manifest = await readWorkflowFamilyManifest(resourcesRoot);
|
|
6816
7396
|
const expected = manifest.content_sha256;
|
|
6817
7397
|
if (typeof expected !== "string" || expected.length === 0) return;
|
|
6818
|
-
const harnessRoot =
|
|
7398
|
+
const harnessRoot = join21(resourcesRoot, "harness");
|
|
6819
7399
|
if (!await pathExists3(harnessRoot)) {
|
|
6820
7400
|
throw new WorkflowDataResolutionError(
|
|
6821
7401
|
"\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u7F3A\u5C11 harness/\uFF0C\u65E0\u6CD5\u6821\u9A8C SHA-256",
|
|
@@ -6834,40 +7414,40 @@ async function verifyWorkflowPackageIntegrity(resourcesRoot) {
|
|
|
6834
7414
|
}
|
|
6835
7415
|
}
|
|
6836
7416
|
async function monorepoResourcesRoot() {
|
|
6837
|
-
const here =
|
|
7417
|
+
const here = dirname7(fileURLToPath(import.meta.url));
|
|
6838
7418
|
const candidates = [
|
|
6839
7419
|
// TypeScript source: packages/cli/src/workflow-data -> packages/workflow-data-harness.
|
|
6840
|
-
|
|
7420
|
+
join21(here, "../../../workflow-data-harness"),
|
|
6841
7421
|
// Bundled CLI: packages/cli/dist -> packages/workflow-data-harness.
|
|
6842
|
-
|
|
7422
|
+
join21(here, "../../workflow-data-harness"),
|
|
6843
7423
|
// Test/build layouts that preserve additional source directory levels.
|
|
6844
|
-
|
|
7424
|
+
join21(here, "../../../../packages/workflow-data-harness")
|
|
6845
7425
|
];
|
|
6846
7426
|
for (const candidate of candidates) {
|
|
6847
|
-
if (await pathExists3(
|
|
7427
|
+
if (await pathExists3(join21(candidate, "harness", "manifests"))) return candidate;
|
|
6848
7428
|
}
|
|
6849
7429
|
return null;
|
|
6850
7430
|
}
|
|
6851
7431
|
async function siblingWorkflowPackage(cwd) {
|
|
6852
|
-
const here =
|
|
7432
|
+
const here = dirname7(fileURLToPath(import.meta.url));
|
|
6853
7433
|
const candidates = [
|
|
6854
|
-
|
|
7434
|
+
join21(cwd, "node_modules", "@hunter-harness", "workflow-harness"),
|
|
6855
7435
|
// Published layout: node_modules/hunter-harness/dist/bin.js alongside the
|
|
6856
7436
|
// scoped workflow package in node_modules/@hunter-harness/workflow-harness.
|
|
6857
|
-
|
|
7437
|
+
join21(here, "..", "..", "@hunter-harness", "workflow-harness"),
|
|
6858
7438
|
// Monorepo bundled layout: packages/cli/dist/bin.js.
|
|
6859
|
-
|
|
7439
|
+
join21(here, "..", "..", "workflow-data-harness")
|
|
6860
7440
|
];
|
|
6861
7441
|
for (const candidate of candidates) {
|
|
6862
|
-
if (await pathExists3(
|
|
7442
|
+
if (await pathExists3(join21(candidate, "harness", "manifests"))) return candidate;
|
|
6863
7443
|
}
|
|
6864
7444
|
return null;
|
|
6865
7445
|
}
|
|
6866
7446
|
async function readCachedNpmPackageVersion(cacheRoot) {
|
|
6867
|
-
const manifestPath =
|
|
7447
|
+
const manifestPath = join21(cacheRoot, "package.json");
|
|
6868
7448
|
if (!await pathExists3(manifestPath)) return null;
|
|
6869
7449
|
try {
|
|
6870
|
-
const pkg = JSON.parse(await
|
|
7450
|
+
const pkg = JSON.parse(await readFile19(manifestPath, "utf8"));
|
|
6871
7451
|
return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : null;
|
|
6872
7452
|
} catch {
|
|
6873
7453
|
return null;
|
|
@@ -6889,13 +7469,13 @@ async function latestWorkflowCacheIsStale(cacheRoot, packageName, resolveManifes
|
|
|
6889
7469
|
}
|
|
6890
7470
|
}
|
|
6891
7471
|
async function materializeWorkflowPackageCache(cacheRoot, packageSpec, extract) {
|
|
6892
|
-
await
|
|
6893
|
-
const staging =
|
|
7472
|
+
await mkdir7(cacheRoot, { recursive: true });
|
|
7473
|
+
const staging = join21(cacheRoot, ".extract");
|
|
6894
7474
|
await rm8(staging, { recursive: true, force: true });
|
|
6895
|
-
await
|
|
7475
|
+
await mkdir7(staging, { recursive: true });
|
|
6896
7476
|
await extract(packageSpec, staging);
|
|
6897
|
-
const packageDir = await pathExists3(
|
|
6898
|
-
if (!await pathExists3(
|
|
7477
|
+
const packageDir = await pathExists3(join21(staging, "harness", "manifests")) ? staging : join21(staging, "package");
|
|
7478
|
+
if (!await pathExists3(join21(packageDir, "harness", "manifests"))) {
|
|
6899
7479
|
throw new WorkflowDataResolutionError(
|
|
6900
7480
|
"\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u5185\u5BB9\u65E0\u6548\uFF1A\u7F3A\u5C11 harness/manifests",
|
|
6901
7481
|
"WORKFLOW_DATA_INVALID",
|
|
@@ -6924,8 +7504,8 @@ async function resolveWorkflowResourcesRoot(options, argv = []) {
|
|
|
6924
7504
|
const packageName = workflowPackageName(family, options.env);
|
|
6925
7505
|
const packageSpec = version === "latest" ? packageName : `${packageName}@${version}`;
|
|
6926
7506
|
const cacheKey = packageSpec.replace("/", "+");
|
|
6927
|
-
const cacheRoot =
|
|
6928
|
-
if (await pathExists3(
|
|
7507
|
+
const cacheRoot = join21(options.cwd, ".harness", "cache", "workflow-packages", cacheKey);
|
|
7508
|
+
if (await pathExists3(join21(cacheRoot, "harness", "manifests"))) {
|
|
6929
7509
|
if (version === "latest") {
|
|
6930
7510
|
const stale = await latestWorkflowCacheIsStale(cacheRoot, packageName, options.pacoteManifest);
|
|
6931
7511
|
if (stale) {
|
|
@@ -6971,9 +7551,9 @@ function describeWorkflowDataFetchFailure(error, packageSpec) {
|
|
|
6971
7551
|
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
7552
|
}
|
|
6973
7553
|
async function readWorkflowFamilyManifest(resourcesRoot) {
|
|
6974
|
-
const manifestPath =
|
|
7554
|
+
const manifestPath = join21(resourcesRoot, "hunter-workflow-family.json");
|
|
6975
7555
|
if (!await pathExists3(manifestPath)) return {};
|
|
6976
|
-
return JSON.parse(await
|
|
7556
|
+
return JSON.parse(await readFile19(manifestPath, "utf8"));
|
|
6977
7557
|
}
|
|
6978
7558
|
|
|
6979
7559
|
// src/bin.ts
|
|
@@ -7087,6 +7667,12 @@ async function runCli(argv, overrides = {}) {
|
|
|
7087
7667
|
dependencies
|
|
7088
7668
|
);
|
|
7089
7669
|
});
|
|
7670
|
+
program.command("rules-sync").description("\u6536\u655B\u516C\u5171\u89C4\u5219\u3001\u5237\u65B0 Agent \u6295\u5F71\u5E76\u63D0\u70BC\u5386\u53F2\u89C4\u5219\u5019\u9009").option("--agents <csv>").option("--codebuddy-surface <surface>").option("--no-learn", "\u53EA\u540C\u6B65\u89C4\u5219\uFF0C\u4E0D\u8BFB\u53D6\u5386\u53F2 review/test \u8BC1\u636E").option("--json").action(async (options) => {
|
|
7671
|
+
exitCode = await runRulesSync(
|
|
7672
|
+
{ ...program.opts(), ...options },
|
|
7673
|
+
dependencies
|
|
7674
|
+
);
|
|
7675
|
+
});
|
|
7090
7676
|
try {
|
|
7091
7677
|
await program.parseAsync(["node", "hunter-harness", ...argv]);
|
|
7092
7678
|
return exitCode;
|