hunter-harness 0.2.28 → 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.
Files changed (3) hide show
  1. package/README.md +28 -26
  2. package/dist/bin.js +556 -181
  3. 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 stat7 = await lstat(current);
1220
- if (stat7.isSymbolicLink()) {
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((resolve10) => setTimeout(resolve10, milliseconds));
1236
+ await new Promise((resolve11) => setTimeout(resolve11, milliseconds));
1237
1237
  });
1238
1238
  let lastError;
1239
1239
  for (let attempt = 1; attempt <= attempts; attempt += 1) {
@@ -2263,6 +2263,12 @@ var MANAGED_NAMES = /* @__PURE__ */ new Set([
2263
2263
  "harness-profile-java.md",
2264
2264
  "harness-profile-java.mdc"
2265
2265
  ]);
2266
+ var AGENT_RULE_ROOTS = [
2267
+ ".claude/rules",
2268
+ ".cursor/rules",
2269
+ ".codebuddy/.rules",
2270
+ ".codebuddy/rules"
2271
+ ];
2266
2272
  function sha256(content) {
2267
2273
  return createHash3("sha256").update(content).digest("hex");
2268
2274
  }
@@ -2293,6 +2299,72 @@ async function markdownFiles(root) {
2293
2299
  throw error;
2294
2300
  }
2295
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
+ }
2296
2368
  function targetsFor(name, agents, surface) {
2297
2369
  const stem = basename(name, extname(name));
2298
2370
  const targets = [];
@@ -2319,23 +2391,15 @@ async function synchronizeProjectRules(projectRoot, agents, surface = "both") {
2319
2391
  const canonicalRoot = join4(root, RULES_ROOT);
2320
2392
  const result = {
2321
2393
  migrated: [],
2394
+ agent_specific: [],
2322
2395
  written: [],
2323
2396
  removed: [],
2324
2397
  unchanged: [],
2325
2398
  conflicts: []
2326
2399
  };
2327
2400
  await mkdir4(canonicalRoot, { recursive: true });
2328
- if ((await markdownFiles(canonicalRoot)).length === 0) {
2329
- for (const name of await markdownFiles(join4(root, ".claude", "rules"))) {
2330
- const destination = join4(canonicalRoot, `${basename(name, extname(name))}.md`);
2331
- if (await optionalText(destination) !== null)
2332
- continue;
2333
- const content = await readFile3(join4(root, ".claude", "rules", name), "utf8");
2334
- await atomicWrite(destination, content);
2335
- result.migrated.push(`${RULES_ROOT}/${basename(destination)}`);
2336
- }
2337
- }
2338
2401
  const previous = await readReceipt(root);
2402
+ await importAgentRules(root, canonicalRoot, previous, result);
2339
2403
  const next = { schema_version: 1, source_hashes: {}, targets: {} };
2340
2404
  const desired = /* @__PURE__ */ new Map();
2341
2405
  for (const name of await markdownFiles(canonicalRoot)) {
@@ -2349,9 +2413,10 @@ async function synchronizeProjectRules(projectRoot, agents, surface = "both") {
2349
2413
  const path = join4(root, target);
2350
2414
  const current = await optionalText(path);
2351
2415
  const incomingHash = sha256(content);
2352
- if (current === content) {
2416
+ const canonicalCurrent = current === null ? null : canonicalImportContent(current);
2417
+ if (current === content || canonicalCurrent === content) {
2353
2418
  result.unchanged.push(target);
2354
- next.targets[target] = incomingHash;
2419
+ next.targets[target] = current === null ? incomingHash : sha256(current);
2355
2420
  } else if (current === null || previous.targets[target] === sha256(current)) {
2356
2421
  await atomicWrite(path, content);
2357
2422
  result.written.push(target);
@@ -2411,6 +2476,7 @@ async function synchronizeProjectRules(projectRoot, agents, surface = "both") {
2411
2476
  }
2412
2477
  await atomicWrite(join4(root, RECEIPT_PATH), JSON.stringify(next, null, 2) + "\n");
2413
2478
  result.migrated.sort();
2479
+ result.agent_specific.sort();
2414
2480
  result.written.sort();
2415
2481
  result.removed.sort();
2416
2482
  result.unchanged.sort();
@@ -2918,16 +2984,237 @@ async function enrichContextIndexWithVerification(root, agents, profile, resourc
2918
2984
  await writeFile4(contextPath, JSON.stringify(parsed, null, 2) + "\n", "utf8");
2919
2985
  }
2920
2986
 
2921
- // ../core/dist/project/refresh.js
2987
+ // ../core/dist/project/rule-candidates.js
2922
2988
  import { createHash as createHash6 } from "node:crypto";
2923
- import { readFile as readFile6, readdir as readdir4, rmdir } from "node:fs/promises";
2924
- import { dirname as dirname4, join as join7, resolve as resolve6 } from "node:path";
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
+ };
3206
+ }
3207
+
3208
+ // ../core/dist/project/refresh.js
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";
2925
3212
  import { parse as parseYaml3, stringify as stringifyYaml2 } from "yaml";
2926
3213
  var INSTALLED_STATE_PATH = ".harness/state/local/installed-harness-bundle.json";
2927
3214
  var CONTEXT_INDEX_PATH2 = ".harness/context-index.json";
2928
3215
  async function fileHex2(path) {
2929
3216
  try {
2930
- return createHash6("sha256").update(await readFile6(path)).digest("hex");
3217
+ return createHash7("sha256").update(await readFile7(path)).digest("hex");
2931
3218
  } catch (error) {
2932
3219
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
2933
3220
  return null;
@@ -2937,7 +3224,7 @@ async function fileHex2(path) {
2937
3224
  }
2938
3225
  async function readOptionalText(path) {
2939
3226
  try {
2940
- return await readFile6(path, "utf8");
3227
+ return await readFile7(path, "utf8");
2941
3228
  } catch (error) {
2942
3229
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
2943
3230
  return "";
@@ -2946,7 +3233,7 @@ async function readOptionalText(path) {
2946
3233
  }
2947
3234
  }
2948
3235
  async function readInstalledState(root) {
2949
- const content = await readOptionalText(join7(root, INSTALLED_STATE_PATH));
3236
+ const content = await readOptionalText(join8(root, INSTALLED_STATE_PATH));
2950
3237
  if (content === "") {
2951
3238
  return {
2952
3239
  profile: null,
@@ -3016,7 +3303,7 @@ async function readInstalledState(root) {
3016
3303
  };
3017
3304
  }
3018
3305
  async function readInstalledAgentConfiguration(projectRoot) {
3019
- const installed = await readInstalledState(resolve6(projectRoot));
3306
+ const installed = await readInstalledState(resolve7(projectRoot));
3020
3307
  return {
3021
3308
  agents: installed.adapters,
3022
3309
  profiles: Object.fromEntries(installed.adapters.map((agent) => [
@@ -3026,7 +3313,7 @@ async function readInstalledAgentConfiguration(projectRoot) {
3026
3313
  };
3027
3314
  }
3028
3315
  async function readContextIndexBundleHash(root) {
3029
- const content = await readOptionalText(join7(root, CONTEXT_INDEX_PATH2));
3316
+ const content = await readOptionalText(join8(root, CONTEXT_INDEX_PATH2));
3030
3317
  if (content === "")
3031
3318
  return null;
3032
3319
  try {
@@ -3038,13 +3325,13 @@ async function readContextIndexBundleHash(root) {
3038
3325
  }
3039
3326
  }
3040
3327
  async function pruneEmptyParentDirs(root, deletedPaths, boundaryPaths) {
3041
- const boundaries = new Set(boundaryPaths.map((path) => join7(root, path)));
3328
+ const boundaries = new Set(boundaryPaths.map((path) => join8(root, path)));
3042
3329
  for (const deleted of deletedPaths) {
3043
- let dir = dirname4(join7(root, deleted));
3330
+ let dir = dirname5(join8(root, deleted));
3044
3331
  while (dir.startsWith(root) && !boundaries.has(dir)) {
3045
3332
  let entries;
3046
3333
  try {
3047
- entries = await readdir4(dir);
3334
+ entries = await readdir5(dir);
3048
3335
  } catch {
3049
3336
  break;
3050
3337
  }
@@ -3055,7 +3342,7 @@ async function pruneEmptyParentDirs(root, deletedPaths, boundaryPaths) {
3055
3342
  } catch {
3056
3343
  break;
3057
3344
  }
3058
- dir = dirname4(dir);
3345
+ dir = dirname5(dir);
3059
3346
  }
3060
3347
  }
3061
3348
  }
@@ -3082,7 +3369,7 @@ function sortByTarget(items) {
3082
3369
  return [...items].sort((left, right) => left.target_path.localeCompare(right.target_path));
3083
3370
  }
3084
3371
  async function reconcileContextIndex(root, agents, profiles, manifests, codebuddySurface2, verifications) {
3085
- const existing = await readOptionalText(join7(root, CONTEXT_INDEX_PATH2));
3372
+ const existing = await readOptionalText(join8(root, CONTEXT_INDEX_PATH2));
3086
3373
  let codebase = {
3087
3374
  map: ".harness/codebase/map",
3088
3375
  status: "missing"
@@ -3135,7 +3422,7 @@ async function reconcileContextIndex(root, agents, profiles, manifests, codebudd
3135
3422
  }
3136
3423
  async function projectTransitionOperation(root, agents, profiles, codebuddySurface2) {
3137
3424
  const path = ".harness/project.yaml";
3138
- const content = await readOptionalText(join7(root, path));
3425
+ const content = await readOptionalText(join8(root, path));
3139
3426
  if (content === "")
3140
3427
  return null;
3141
3428
  const project = parseYaml3(content);
@@ -3170,11 +3457,11 @@ function mergeTargets(targets) {
3170
3457
  }).sort((left, right) => left.target_path.localeCompare(right.target_path));
3171
3458
  }
3172
3459
  async function reconcileMarkdownBlock(root, fileName, blockId, content, remove, ops, conflicts, preserved) {
3173
- const original = await readOptionalText(join7(root, fileName));
3460
+ const original = await readOptionalText(join8(root, fileName));
3174
3461
  const synthetic = {
3175
3462
  source_path: fileName,
3176
3463
  target_path: fileName,
3177
- sha256: createHash6("sha256").update(content).digest("hex"),
3464
+ sha256: createHash7("sha256").update(content).digest("hex"),
3178
3465
  bytes: new TextEncoder().encode(content)
3179
3466
  };
3180
3467
  let next;
@@ -3189,7 +3476,7 @@ async function reconcileMarkdownBlock(root, fileName, blockId, content, remove,
3189
3476
  next = refreshed.content;
3190
3477
  }
3191
3478
  } catch {
3192
- const current = original === "" ? null : createHash6("sha256").update(original).digest("hex");
3479
+ const current = original === "" ? null : createHash7("sha256").update(original).digest("hex");
3193
3480
  preserved.push(item(synthetic, "preserve", "MALFORMED_MANAGED_BLOCK", current, synthetic.sha256));
3194
3481
  conflicts.push(conflict(synthetic, "MALFORMED_MANAGED_BLOCK", current, synthetic.sha256));
3195
3482
  return;
@@ -3209,7 +3496,7 @@ function stateWithoutInstalledAt(value) {
3209
3496
  return copy;
3210
3497
  }
3211
3498
  async function refreshProject(options) {
3212
- const root = resolve6(options.projectRoot);
3499
+ const root = resolve7(options.projectRoot);
3213
3500
  const installed = await readInstalledState(root);
3214
3501
  const oldAgents = installed.adapters.length > 0 ? installed.adapters : ["claude-code"];
3215
3502
  const selectedAgents = sortHarnessAgents(options.agents);
@@ -3293,7 +3580,7 @@ async function refreshProject(options) {
3293
3580
  const newStateFiles = installed.files.filter((entry) => entry.owner === "shared" || !selectedSet.has(entry.owner));
3294
3581
  for (const target of newManaged) {
3295
3582
  const incoming = target.sha256;
3296
- const current = await fileHex2(join7(root, target.target_path));
3583
+ const current = await fileHex2(join8(root, target.target_path));
3297
3584
  if (current === null) {
3298
3585
  applied.push(item(target, "add", "MISSING_TARGET", null, incoming));
3299
3586
  ops.push({ operation: "add", path: target.target_path, content: target.bytes });
@@ -3321,7 +3608,7 @@ async function refreshProject(options) {
3321
3608
  }
3322
3609
  }
3323
3610
  for (const target of oldOnly) {
3324
- const current = await fileHex2(join7(root, target.target_path));
3611
+ const current = await fileHex2(join8(root, target.target_path));
3325
3612
  if (current === null) {
3326
3613
  continue;
3327
3614
  }
@@ -3364,7 +3651,7 @@ async function refreshProject(options) {
3364
3651
  const verifyEntries = [];
3365
3652
  for (const target of verifyTargets) {
3366
3653
  const rel = target.target_path.replace(/\\/g, "/");
3367
- const actual = await fileHex2(join7(root, target.target_path));
3654
+ const actual = await fileHex2(join8(root, target.target_path));
3368
3655
  verifyEntries.push({ relpath: rel, sha256: actual ?? "" });
3369
3656
  if (actual === null) {
3370
3657
  verifyMismatches.push({ relpath: rel, expected: target.sha256, actual: "<missing>" });
@@ -3397,19 +3684,19 @@ async function refreshProject(options) {
3397
3684
  owner: "shared",
3398
3685
  target_path: "AGENTS.md",
3399
3686
  block_id: AGENTS_CORE_BLOCK_ID,
3400
- content_sha256: createHash6("sha256").update(AGENTS_MANAGED_BLOCK_CONTENT).digest("hex")
3687
+ content_sha256: createHash7("sha256").update(AGENTS_MANAGED_BLOCK_CONTENT).digest("hex")
3401
3688
  },
3402
3689
  ...selectedSet.has("claude-code") ? [{
3403
3690
  owner: "claude-code",
3404
3691
  target_path: "CLAUDE.md",
3405
3692
  block_id: CLAUDE_BLOCK_ID,
3406
- content_sha256: createHash6("sha256").update(CLAUDE_MANAGED_BLOCK_CONTENT).digest("hex")
3693
+ content_sha256: createHash7("sha256").update(CLAUDE_MANAGED_BLOCK_CONTENT).digest("hex")
3407
3694
  }] : [],
3408
3695
  ...selectedSet.has("codebuddy") ? [{
3409
3696
  owner: "codebuddy",
3410
3697
  target_path: "CODEBUDDY.md",
3411
3698
  block_id: CODEBUDDY_BLOCK_ID,
3412
- content_sha256: createHash6("sha256").update(CODEBUDDY_MANAGED_BLOCK_CONTENT).digest("hex")
3699
+ content_sha256: createHash7("sha256").update(CODEBUDDY_MANAGED_BLOCK_CONTENT).digest("hex")
3413
3700
  }] : []
3414
3701
  ].sort((left, right) => left.target_path.localeCompare(right.target_path) || left.block_id.localeCompare(right.block_id));
3415
3702
  const filesByTarget = new Map(newStateFiles.map((entry) => [entry.target_path, entry]));
@@ -3425,7 +3712,7 @@ async function refreshProject(options) {
3425
3712
  files: [...filesByTarget.values()].sort((left, right) => left.target_path.localeCompare(right.target_path) || left.source_path.localeCompare(right.source_path)),
3426
3713
  managed_blocks: managedBlocks
3427
3714
  };
3428
- const existingState = await readOptionalText(join7(root, INSTALLED_STATE_PATH));
3715
+ const existingState = await readOptionalText(join8(root, INSTALLED_STATE_PATH));
3429
3716
  let existingParsed = null;
3430
3717
  try {
3431
3718
  existingParsed = existingState === "" ? null : JSON.parse(existingState);
@@ -3480,7 +3767,7 @@ function buildMarkerCoreHash(text) {
3480
3767
  }
3481
3768
  }
3482
3769
  async function collectFreshness(options) {
3483
- const root = resolve6(options.projectRoot);
3770
+ const root = resolve7(options.projectRoot);
3484
3771
  const installed = await readInstalledState(root);
3485
3772
  const codebuddySurface2 = options.codebuddySurface ?? "both";
3486
3773
  const agents = [];
@@ -3525,18 +3812,18 @@ async function collectFreshness(options) {
3525
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))));
3526
3813
  const installedProjection = await Promise.all(targets.map(async (target) => ({
3527
3814
  path: target.target_path.replace(/\\/g, "/"),
3528
- sha256: await fileHex2(join7(root, target.target_path))
3815
+ sha256: await fileHex2(join8(root, target.target_path))
3529
3816
  })));
3530
3817
  identity.installedAdapterHash = sha256Bytes(canonicalJson(installedProjection.sort((a, b) => a.path.localeCompare(b.path))));
3531
3818
  const markerTarget = targets.find((target) => target.target_path.replace(/\\/g, "/").endsWith(`/${BUILD_MARKER_BUNDLE_PATH}`) || target.target_path === BUILD_MARKER_BUNDLE_PATH);
3532
3819
  if (markerTarget !== void 0) {
3533
- identity.installedCoreHash = buildMarkerCoreHash(await readOptionalText(join7(root, markerTarget.target_path)));
3820
+ identity.installedCoreHash = buildMarkerCoreHash(await readOptionalText(join8(root, markerTarget.target_path)));
3534
3821
  }
3535
3822
  const mismatchDetails = [];
3536
3823
  const contentEntries = [];
3537
3824
  for (const target of targets) {
3538
3825
  const rel = target.target_path.replace(/\\/g, "/");
3539
- const actual = await fileHex2(join7(root, target.target_path));
3826
+ const actual = await fileHex2(join8(root, target.target_path));
3540
3827
  contentEntries.push({ relpath: rel, sha256: actual ?? "" });
3541
3828
  if (actual === null) {
3542
3829
  mismatchDetails.push({ relpath: rel, expected: target.sha256, actual: "<missing>" });
@@ -3559,7 +3846,7 @@ async function collectFreshness(options) {
3559
3846
  const drifted = [];
3560
3847
  const missing = [];
3561
3848
  for (const target of targets) {
3562
- const current = await fileHex2(join7(root, target.target_path));
3849
+ const current = await fileHex2(join8(root, target.target_path));
3563
3850
  if (current === null) {
3564
3851
  missing.push(target.target_path);
3565
3852
  } else if (current !== target.sha256) {
@@ -3807,11 +4094,11 @@ function rawFindings(content) {
3807
4094
  if (rule.id === "HH_PASSWORD_VALUE" && isObviousPlaceholder(value)) {
3808
4095
  continue;
3809
4096
  }
3810
- const relative3 = match[0].indexOf(value);
4097
+ const relative4 = match[0].indexOf(value);
3811
4098
  findings.push({
3812
4099
  ruleId: rule.id,
3813
4100
  severity: rule.severity,
3814
- offset: match.index + Math.max(0, relative3),
4101
+ offset: match.index + Math.max(0, relative4),
3815
4102
  value
3816
4103
  });
3817
4104
  }
@@ -3923,8 +4210,8 @@ function generateProposalPreview(input, scanOptions = {}) {
3923
4210
  }
3924
4211
 
3925
4212
  // ../core/dist/push/credentials.js
3926
- import { readFile as readFile7, writeFile as writeFile5 } from "node:fs/promises";
3927
- import { join as join8 } from "node:path";
4213
+ import { readFile as readFile8, writeFile as writeFile6 } from "node:fs/promises";
4214
+ import { join as join9 } from "node:path";
3928
4215
  import { parse as parseYaml4, stringify as stringifyYaml3 } from "yaml";
3929
4216
  var CREDENTIALS_LOCAL_RELATIVE = ".harness/credentials.local.yaml";
3930
4217
  var CREDENTIALS_GITIGNORE_LINES = [
@@ -3994,7 +4281,7 @@ function mergeLocalCredentials(existing, patch) {
3994
4281
  }
3995
4282
  async function readLocalCredentials(projectRoot) {
3996
4283
  try {
3997
- const raw = await readFile7(join8(projectRoot, CREDENTIALS_LOCAL_RELATIVE), "utf8");
4284
+ const raw = await readFile8(join9(projectRoot, CREDENTIALS_LOCAL_RELATIVE), "utf8");
3998
4285
  return parseLocalCredentials(parseYaml4(raw));
3999
4286
  } catch (error) {
4000
4287
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
@@ -4005,13 +4292,13 @@ async function readLocalCredentials(projectRoot) {
4005
4292
  }
4006
4293
  async function writeLocalCredentials(projectRoot, credentials) {
4007
4294
  const normalized = validateLocalCredentials(credentials);
4008
- await writeFile5(join8(projectRoot, CREDENTIALS_LOCAL_RELATIVE), stringifyYaml3(normalized, { sortMapEntries: true }) + "\n", "utf8");
4295
+ await writeFile6(join9(projectRoot, CREDENTIALS_LOCAL_RELATIVE), stringifyYaml3(normalized, { sortMapEntries: true }) + "\n", "utf8");
4009
4296
  }
4010
4297
  async function ensureCredentialsGitignore(projectRoot) {
4011
- const gitignorePath = join8(projectRoot, ".gitignore");
4298
+ const gitignorePath = join9(projectRoot, ".gitignore");
4012
4299
  let content = "";
4013
4300
  try {
4014
- content = await readFile7(gitignorePath, "utf8");
4301
+ content = await readFile8(gitignorePath, "utf8");
4015
4302
  } catch (error) {
4016
4303
  if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) {
4017
4304
  throw error;
@@ -4034,7 +4321,7 @@ async function ensureCredentialsGitignore(projectRoot) {
4034
4321
  return;
4035
4322
  }
4036
4323
  const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
4037
- await writeFile5(gitignorePath, content + suffix + missing.join("\n") + "\n", "utf8");
4324
+ await writeFile6(gitignorePath, content + suffix + missing.join("\n") + "\n", "utf8");
4038
4325
  }
4039
4326
  function resolvePushAuth(input) {
4040
4327
  const tokenEnv = input.tokenEnv ?? input.projectTokenEnv;
@@ -4062,28 +4349,28 @@ function resolvePushAuth(input) {
4062
4349
  }
4063
4350
 
4064
4351
  // ../core/dist/push/push.js
4065
- import { lstat as lstat3, readFile as readFile11, readdir as readdir5, rm as rm5 } from "node:fs/promises";
4066
- import { join as join12, relative, resolve as resolve8 } from "node:path";
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";
4067
4354
  import { parse as parseYaml5, stringify as stringifyYaml4 } from "yaml";
4068
4355
 
4069
4356
  // ../core/dist/state/baseline.js
4070
- import { readFile as readFile8 } from "node:fs/promises";
4071
- import { join as join9 } from "node:path";
4357
+ import { readFile as readFile9 } from "node:fs/promises";
4358
+ import { join as join10 } from "node:path";
4072
4359
  async function readBaseline(projectRoot) {
4073
- const content = await readFile8(join9(stateLayout(projectRoot).baseline, "manifest.json"), "utf8");
4360
+ const content = await readFile9(join10(stateLayout(projectRoot).baseline, "manifest.json"), "utf8");
4074
4361
  return baselineManifestSchema.parse(JSON.parse(content));
4075
4362
  }
4076
4363
 
4077
4364
  // ../core/dist/state/locks.js
4078
4365
  import { randomUUID as randomUUID3 } from "node:crypto";
4079
- import { readFile as readFile9, rename as rename4, rm as rm3, writeFile as writeFile6 } from "node:fs/promises";
4080
- import { join as join10 } from "node:path";
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";
4081
4368
  async function readLock(path) {
4082
- return JSON.parse(await readFile9(path, "utf8"));
4369
+ return JSON.parse(await readFile10(path, "utf8"));
4083
4370
  }
4084
4371
  async function acquireProtocolLock(projectRoot, operation, options = {}) {
4085
4372
  const layout = await ensureStateLayout(projectRoot);
4086
- const lockPath = join10(layout.locks, "protocol.lock");
4373
+ const lockPath = join11(layout.locks, "protocol.lock");
4087
4374
  const now = options.now ?? Date.now();
4088
4375
  const staleAfterMs = options.staleAfterMs ?? 15 * 60 * 1e3;
4089
4376
  const record = {
@@ -4096,7 +4383,7 @@ async function acquireProtocolLock(projectRoot, operation, options = {}) {
4096
4383
  };
4097
4384
  for (let attempt = 0; attempt < 2; attempt += 1) {
4098
4385
  try {
4099
- await writeFile6(lockPath, JSON.stringify(record, null, 2), { flag: "wx" });
4386
+ await writeFile7(lockPath, JSON.stringify(record, null, 2), { flag: "wx" });
4100
4387
  return {
4101
4388
  path: lockPath,
4102
4389
  operation,
@@ -4118,15 +4405,15 @@ async function acquireProtocolLock(projectRoot, operation, options = {}) {
4118
4405
  if (now - current.heartbeat_at_ms <= staleAfterMs) {
4119
4406
  throw new Error("protocol lock is active for operation " + current.operation, { cause: error });
4120
4407
  }
4121
- await rename4(lockPath, lockPath + ".stale-" + randomUUID3());
4408
+ await rename5(lockPath, lockPath + ".stale-" + randomUUID3());
4122
4409
  }
4123
4410
  }
4124
4411
  throw new Error("unable to acquire protocol lock");
4125
4412
  }
4126
4413
 
4127
4414
  // ../core/dist/sync/synchronize.js
4128
- import { lstat as lstat2, readFile as readFile10, rm as rm4 } from "node:fs/promises";
4129
- import { join as join11, resolve as resolve7 } from "node:path";
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";
4130
4417
 
4131
4418
  // ../core/dist/update/conflicts.js
4132
4419
  function operationTargetPath(operation) {
@@ -4408,8 +4695,8 @@ async function pathExists(path) {
4408
4695
  }
4409
4696
  }
4410
4697
  async function optionalContent(root, path) {
4411
- const full = join11(root, path);
4412
- return await pathExists(full) ? readFile10(full, "utf8") : null;
4698
+ const full = join12(root, path);
4699
+ return await pathExists(full) ? readFile11(full, "utf8") : null;
4413
4700
  }
4414
4701
  function manifestPayloadHash(manifest) {
4415
4702
  const payload = { ...manifest };
@@ -4421,10 +4708,10 @@ async function loadBlob(root, client, artifactId, operation, requestId, dryRun)
4421
4708
  return null;
4422
4709
  }
4423
4710
  const hash = operation.content_sha256;
4424
- const cacheRoot = join11(root, ".harness", "cache", "server-artifacts", artifactId);
4425
- const cachePath = join11(cacheRoot, "blobs", hash.replace(":", "_"));
4711
+ const cacheRoot = join12(root, ".harness", "cache", "server-artifacts", artifactId);
4712
+ const cachePath = join12(cacheRoot, "blobs", hash.replace(":", "_"));
4426
4713
  if (await pathExists(cachePath) && await sha256File(cachePath) === hash) {
4427
- return readFile10(cachePath, "utf8");
4714
+ return readFile11(cachePath, "utf8");
4428
4715
  }
4429
4716
  const bytes = await client.downloadArtifactBlob(artifactId, hash, requestId);
4430
4717
  if (bytes.byteLength !== operation.size_bytes || sha256Bytes(bytes) !== hash) {
@@ -4529,7 +4816,7 @@ async function planSingleArtifact(root, baseline, manifest, client, requestId, d
4529
4816
  });
4530
4817
  }
4531
4818
  async function saveConflictReport(root, requestId, manifest, plan) {
4532
- const reportPath = join11(root, ".harness", "reports", "conflicts-" + requestId + ".json");
4819
+ const reportPath = join12(root, ".harness", "reports", "conflicts-" + requestId + ".json");
4533
4820
  await atomicWriteJson(reportPath, {
4534
4821
  schema_version: 1,
4535
4822
  request_id: requestId,
@@ -4540,7 +4827,7 @@ async function saveConflictReport(root, requestId, manifest, plan) {
4540
4827
  });
4541
4828
  }
4542
4829
  async function synchronizeArtifacts(options, initialBaseline) {
4543
- const root = resolve7(options.projectRoot);
4830
+ const root = resolve8(options.projectRoot);
4544
4831
  const projectId = options.project.project.project_id;
4545
4832
  if (projectId === null) {
4546
4833
  throw new Error("PROJECT_NOT_BOUND");
@@ -4647,7 +4934,7 @@ async function synchronizeArtifacts(options, initialBaseline) {
4647
4934
  }
4648
4935
  continue;
4649
4936
  }
4650
- await atomicWriteJson(join11(root, ".harness", "cache", "server-artifacts", manifest.artifact_id, "manifest.json"), manifest);
4937
+ await atomicWriteJson(join12(root, ".harness", "cache", "server-artifacts", manifest.artifact_id, "manifest.json"), manifest);
4651
4938
  const lock = await acquireProtocolLock(root, "update", { requestId: options.requestId });
4652
4939
  try {
4653
4940
  const nextBaseline = applyBaselineUpdates(baseline, plan);
@@ -4717,7 +5004,7 @@ function baselineEntryFromOperation(manifest, operation, localContent) {
4717
5004
  };
4718
5005
  }
4719
5006
  async function advanceBaselineFromArtifact(options, baseline) {
4720
- const root = resolve7(options.projectRoot);
5007
+ const root = resolve8(options.projectRoot);
4721
5008
  if (options.manifest.project_version === null) {
4722
5009
  throw new Error("artifact manifest missing project_version");
4723
5010
  }
@@ -4818,29 +5105,29 @@ async function walkFiles(root, current, output) {
4818
5105
  if (!await exists3(current)) {
4819
5106
  return;
4820
5107
  }
4821
- for (const item2 of await readdir5(current, { withFileTypes: true })) {
4822
- const path = join12(current, item2.name);
5108
+ for (const item2 of await readdir6(current, { withFileTypes: true })) {
5109
+ const path = join13(current, item2.name);
4823
5110
  if (item2.isSymbolicLink()) {
4824
5111
  throw new PushWorkflowError("symlink is not pushable", 6, "UNSAFE_SYMLINK");
4825
5112
  }
4826
5113
  if (item2.isDirectory()) {
4827
5114
  await walkFiles(root, path, output);
4828
5115
  } else if (item2.isFile()) {
4829
- output.push(normalizeManagedPath(relative(root, path).replaceAll("\\", "/")));
5116
+ output.push(normalizeManagedPath(relative2(root, path).replaceAll("\\", "/")));
4830
5117
  }
4831
5118
  }
4832
5119
  }
4833
5120
  async function walkArchiveSummaries(root, output) {
4834
- const archiveRoot = join12(root, ".harness", "archive");
5121
+ const archiveRoot = join13(root, ".harness", "archive");
4835
5122
  if (!await exists3(archiveRoot))
4836
5123
  return;
4837
- for (const item2 of await readdir5(archiveRoot, { withFileTypes: true })) {
5124
+ for (const item2 of await readdir6(archiveRoot, { withFileTypes: true })) {
4838
5125
  if (item2.isSymbolicLink()) {
4839
5126
  throw new PushWorkflowError("symlink is not pushable", 6, "UNSAFE_SYMLINK");
4840
5127
  }
4841
5128
  if (!item2.isDirectory())
4842
5129
  continue;
4843
- const summaryPath = join12(archiveRoot, item2.name, "reports", "final", "summary-data.json");
5130
+ const summaryPath = join13(archiveRoot, item2.name, "reports", "final", "summary-data.json");
4844
5131
  try {
4845
5132
  const stats = await lstat3(summaryPath);
4846
5133
  if (stats.isSymbolicLink()) {
@@ -4853,7 +5140,7 @@ async function walkArchiveSummaries(root, output) {
4853
5140
  continue;
4854
5141
  throw error;
4855
5142
  }
4856
- const relativePath = normalizeManagedPath(relative(root, summaryPath).replaceAll("\\", "/"));
5143
+ const relativePath = normalizeManagedPath(relative2(root, summaryPath).replaceAll("\\", "/"));
4857
5144
  if (ARCHIVE_SUMMARY_PATH.test(relativePath)) {
4858
5145
  output.push(relativePath);
4859
5146
  }
@@ -4868,19 +5155,19 @@ function enabledHarnessAgents(project) {
4868
5155
  async function walkHarnessEntries(root, directory, output) {
4869
5156
  if (!await exists3(directory))
4870
5157
  return;
4871
- for (const item2 of await readdir5(directory, { withFileTypes: true })) {
5158
+ for (const item2 of await readdir6(directory, { withFileTypes: true })) {
4872
5159
  if (item2.name.startsWith("harness-")) {
4873
- const path = join12(directory, item2.name);
5160
+ const path = join13(directory, item2.name);
4874
5161
  if (item2.isDirectory()) {
4875
5162
  await walkFiles(root, path, output);
4876
5163
  } else if (item2.isFile()) {
4877
- output.push(normalizeManagedPath(relative(root, path).replaceAll("\\", "/")));
5164
+ output.push(normalizeManagedPath(relative2(root, path).replaceAll("\\", "/")));
4878
5165
  }
4879
5166
  }
4880
5167
  }
4881
5168
  }
4882
5169
  async function managedFiles(projectRoot, project) {
4883
- const root = resolve8(projectRoot);
5170
+ const root = resolve9(projectRoot);
4884
5171
  const paths = [];
4885
5172
  const adapters = getAdapters(enabledHarnessAgents(project));
4886
5173
  const managedFiles2 = [
@@ -4889,26 +5176,26 @@ async function managedFiles(projectRoot, project) {
4889
5176
  ...adapters.some((adapter) => adapter.name === "codebuddy") ? ["CODEBUDDY.md"] : []
4890
5177
  ];
4891
5178
  for (const path of managedFiles2) {
4892
- if (await exists3(join12(root, path))) {
5179
+ if (await exists3(join13(root, path))) {
4893
5180
  paths.push(path);
4894
5181
  }
4895
5182
  }
4896
5183
  for (const path of SHARED_MANAGED_ROOTS) {
4897
- await walkFiles(root, join12(root, path), paths);
5184
+ await walkFiles(root, join13(root, path), paths);
4898
5185
  }
4899
5186
  await walkArchiveSummaries(root, paths);
4900
5187
  for (const adapter of adapters) {
4901
5188
  if (adapter.rulesRoot !== null) {
4902
- await walkHarnessEntries(root, join12(root, adapter.rulesRoot), paths);
5189
+ await walkHarnessEntries(root, join13(root, adapter.rulesRoot), paths);
4903
5190
  }
4904
- await walkHarnessEntries(root, join12(root, adapter.skillsRoot), paths);
5191
+ await walkHarnessEntries(root, join13(root, adapter.skillsRoot), paths);
4905
5192
  if (adapter.agentsRoot !== null) {
4906
- await walkHarnessEntries(root, join12(root, adapter.agentsRoot), paths);
5193
+ await walkHarnessEntries(root, join13(root, adapter.agentsRoot), paths);
4907
5194
  }
4908
5195
  }
4909
5196
  const result = {};
4910
5197
  for (const path of [...new Set(paths)].sort()) {
4911
- result[path] = await readFile11(join12(root, path), "utf8");
5198
+ result[path] = await readFile12(join13(root, path), "utf8");
4912
5199
  }
4913
5200
  return result;
4914
5201
  }
@@ -4917,14 +5204,14 @@ function proposalBaseline(baseline) {
4917
5204
  }
4918
5205
  async function readProject(root) {
4919
5206
  try {
4920
- return projectConfigSchema.parse(parseYaml5(await readFile11(join12(root, ".harness", "project.yaml"), "utf8")));
5207
+ return projectConfigSchema.parse(parseYaml5(await readFile12(join13(root, ".harness", "project.yaml"), "utf8")));
4921
5208
  } catch {
4922
5209
  throw new PushWorkflowError("project configuration is missing or invalid", 3, "PROJECT_CONFIG_INVALID");
4923
5210
  }
4924
5211
  }
4925
5212
  async function readOptionalJson(path) {
4926
5213
  try {
4927
- return JSON.parse(await readFile11(path, "utf8"));
5214
+ return JSON.parse(await readFile12(path, "utf8"));
4928
5215
  } catch (error) {
4929
5216
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
4930
5217
  return null;
@@ -4936,7 +5223,7 @@ async function clientIdFor(root, explicit) {
4936
5223
  if (explicit !== void 0) {
4937
5224
  return explicit;
4938
5225
  }
4939
- const path = join12(root, ".harness", "state", "local", "client.json");
5226
+ const path = join13(root, ".harness", "state", "local", "client.json");
4940
5227
  const existing = await readOptionalJson(path);
4941
5228
  if (typeof existing?.client_id === "string" && /^cli_[A-Za-z0-9_-]+$/.test(existing.client_id)) {
4942
5229
  return existing.client_id;
@@ -5132,7 +5419,7 @@ async function bindProject(root, project, baseline, projectId) {
5132
5419
  return { project: nextProject, baseline: nextBaseline };
5133
5420
  }
5134
5421
  async function pushProject(options) {
5135
- const root = resolve8(options.projectRoot);
5422
+ const root = resolve9(options.projectRoot);
5136
5423
  let project = await readProject(root);
5137
5424
  let baseline = await readBaseline(root);
5138
5425
  const profile = parseHarnessProfile(project.project.profiles[0]);
@@ -5198,7 +5485,7 @@ async function pushProject(options) {
5198
5485
  if (options.confirmProposal !== void 0 && !await options.confirmProposal(preview)) {
5199
5486
  return { preview, proposalId: null, projectId: project.project.project_id, cancelled: true };
5200
5487
  }
5201
- const workflowPath = join12(root, ".harness", "state", "local", "push-workflow.json");
5488
+ const workflowPath = join13(root, ".harness", "state", "local", "push-workflow.json");
5202
5489
  const priorWorkflow = await readOptionalJson(workflowPath);
5203
5490
  const provisionalRequestId = priorWorkflow?.local_project_key === project.project.local_project_key ? priorWorkflow.request_id : uuidV7();
5204
5491
  const lock = await acquireProtocolLock(root, "push", { requestId: provisionalRequestId });
@@ -5353,7 +5640,7 @@ async function pushProject(options) {
5353
5640
  pushWarning = "BASELINE_ADVANCE_DEFERRED";
5354
5641
  }
5355
5642
  }
5356
- await atomicWriteJson(join12(root, ".harness", "state", "local", "push-results", finalized.proposal_id + ".json"), {
5643
+ await atomicWriteJson(join13(root, ".harness", "state", "local", "push-results", finalized.proposal_id + ".json"), {
5357
5644
  schema_version: 1,
5358
5645
  request_id: requestId,
5359
5646
  project_id: projectId,
@@ -5398,14 +5685,14 @@ async function pushProject(options) {
5398
5685
  }
5399
5686
 
5400
5687
  // ../core/dist/state/cleanup.js
5401
- import { readFile as readFile12, readdir as readdir6, rm as rm6 } from "node:fs/promises";
5402
- import { join as join13 } from "node:path";
5688
+ import { readFile as readFile13, readdir as readdir7, rm as rm6 } from "node:fs/promises";
5689
+ import { join as join14 } from "node:path";
5403
5690
  function isSafeEntryName(name) {
5404
5691
  return name.length > 0 && !name.includes("/") && !name.includes("\\") && name !== "." && name !== ".." && !name.includes("\0");
5405
5692
  }
5406
5693
  async function listDir(path) {
5407
5694
  try {
5408
- return await readdir6(path);
5695
+ return await readdir7(path);
5409
5696
  } catch (error) {
5410
5697
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
5411
5698
  return [];
@@ -5423,7 +5710,7 @@ async function cleanupProject(options) {
5423
5710
  continue;
5424
5711
  let journal;
5425
5712
  try {
5426
- journal = JSON.parse(await readFile12(join13(layout.transactions, name, "journal.json"), "utf8"));
5713
+ journal = JSON.parse(await readFile13(join14(layout.transactions, name, "journal.json"), "utf8"));
5427
5714
  } catch {
5428
5715
  continue;
5429
5716
  }
@@ -5441,7 +5728,7 @@ async function cleanupProject(options) {
5441
5728
  continue;
5442
5729
  pruned.push(entry.id);
5443
5730
  if (!options.dryRun) {
5444
- await rm6(join13(layout.transactions, entry.id), { recursive: true, force: true });
5731
+ await rm6(join14(layout.transactions, entry.id), { recursive: true, force: true });
5445
5732
  }
5446
5733
  }
5447
5734
  }
@@ -5450,7 +5737,7 @@ async function cleanupProject(options) {
5450
5737
  continue;
5451
5738
  removedCache.push(name);
5452
5739
  if (!options.dryRun) {
5453
- await rm6(join13(layout.serverArtifacts, name), { recursive: true, force: true });
5740
+ await rm6(join14(layout.serverArtifacts, name), { recursive: true, force: true });
5454
5741
  }
5455
5742
  }
5456
5743
  return {
@@ -5510,10 +5797,10 @@ var SKILL_TARGET_AGENTS = Object.freeze([
5510
5797
  ]);
5511
5798
 
5512
5799
  // ../core/dist/transaction/recovery.js
5513
- import { readFile as readFile13, readdir as readdir7, rm as rm7, stat as stat3 } from "node:fs/promises";
5514
- import { join as join14 } from "node:path";
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";
5515
5802
  async function recoverTransaction(projectRoot, transactionId) {
5516
- const journal = JSON.parse(await readFile13(join14(stateLayout(projectRoot).transactions, transactionId, "journal.json"), "utf8"));
5803
+ const journal = JSON.parse(await readFile14(join15(stateLayout(projectRoot).transactions, transactionId, "journal.json"), "utf8"));
5517
5804
  if (journal.state === "committed") {
5518
5805
  return { transactionId, status: "committed" };
5519
5806
  }
@@ -5530,7 +5817,7 @@ async function listTransactions(projectRoot) {
5530
5817
  const root = stateLayout(projectRoot).transactions;
5531
5818
  let names;
5532
5819
  try {
5533
- names = await readdir7(root);
5820
+ names = await readdir8(root);
5534
5821
  } catch (error) {
5535
5822
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
5536
5823
  return [];
@@ -5540,7 +5827,7 @@ async function listTransactions(projectRoot) {
5540
5827
  const transactions = [];
5541
5828
  for (const transactionId of names) {
5542
5829
  try {
5543
- const journal = JSON.parse(await readFile13(join14(root, transactionId, "journal.json"), "utf8"));
5830
+ const journal = JSON.parse(await readFile14(join15(root, transactionId, "journal.json"), "utf8"));
5544
5831
  transactions.push({
5545
5832
  transactionId,
5546
5833
  kind: journal.kind,
@@ -5557,7 +5844,7 @@ async function pendingTransactions(projectRoot) {
5557
5844
  }
5558
5845
  async function pathExists2(path) {
5559
5846
  try {
5560
- await stat3(path);
5847
+ await stat4(path);
5561
5848
  return true;
5562
5849
  } catch (error) {
5563
5850
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
@@ -5571,11 +5858,11 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
5571
5858
  if (latest === void 0) {
5572
5859
  throw new Error("no committed update transaction is available for rollback");
5573
5860
  }
5574
- const transactionRoot = join14(stateLayout(projectRoot).transactions, latest.transactionId);
5575
- const journal = JSON.parse(await readFile13(join14(transactionRoot, "journal.json"), "utf8"));
5576
- const after = JSON.parse(await readFile13(join14(transactionRoot, "after", "manifest.json"), "utf8"));
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"));
5577
5864
  for (const entry of after) {
5578
- const target = join14(projectRoot, entry.path);
5865
+ const target = join15(projectRoot, entry.path);
5579
5866
  const exists6 = await pathExists2(target);
5580
5867
  if (exists6 !== entry.exists || exists6 && await sha256File(target) !== entry.hash) {
5581
5868
  throw new Error("cannot rollback dirty path: " + entry.path);
@@ -5588,10 +5875,10 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
5588
5875
  continue;
5589
5876
  }
5590
5877
  seen.add(snapshot.path);
5591
- const target = join14(projectRoot, snapshot.path);
5878
+ const target = join15(projectRoot, snapshot.path);
5592
5879
  const exists6 = await pathExists2(target);
5593
5880
  if (snapshot.existed && snapshot.snapshot_name !== null) {
5594
- const content = await readFile13(join14(transactionRoot, "before", snapshot.snapshot_name));
5881
+ const content = await readFile14(join15(transactionRoot, "before", snapshot.snapshot_name));
5595
5882
  operations.push({
5596
5883
  operation: exists6 ? "modify" : "add",
5597
5884
  path: snapshot.path,
@@ -5620,7 +5907,7 @@ async function cleanupOldTransactions(projectRoot, now = /* @__PURE__ */ new Dat
5620
5907
  if (!removable) {
5621
5908
  continue;
5622
5909
  }
5623
- await rm7(join14(stateLayout(projectRoot).transactions, item2.transactionId), {
5910
+ await rm7(join15(stateLayout(projectRoot).transactions, item2.transactionId), {
5624
5911
  recursive: true,
5625
5912
  force: true
5626
5913
  });
@@ -5630,8 +5917,8 @@ async function cleanupOldTransactions(projectRoot, now = /* @__PURE__ */ new Dat
5630
5917
  }
5631
5918
 
5632
5919
  // ../core/dist/update/update.js
5633
- import { readFile as readFile14 } from "node:fs/promises";
5634
- import { join as join15, resolve as resolve9 } from "node:path";
5920
+ import { readFile as readFile15 } from "node:fs/promises";
5921
+ import { join as join16, resolve as resolve10 } from "node:path";
5635
5922
  import { parse as parseYaml8 } from "yaml";
5636
5923
  var UpdateWorkflowError = class extends Error {
5637
5924
  exitCode;
@@ -5644,10 +5931,10 @@ var UpdateWorkflowError = class extends Error {
5644
5931
  }
5645
5932
  };
5646
5933
  async function updateProject(options) {
5647
- const root = resolve9(options.projectRoot);
5934
+ const root = resolve10(options.projectRoot);
5648
5935
  let project;
5649
5936
  try {
5650
- project = projectConfigSchema.parse(parseYaml8(await readFile14(join15(root, ".harness", "project.yaml"), "utf8")));
5937
+ project = projectConfigSchema.parse(parseYaml8(await readFile15(join16(root, ".harness", "project.yaml"), "utf8")));
5651
5938
  } catch {
5652
5939
  throw new UpdateWorkflowError("project configuration is invalid", 3, "PROJECT_CONFIG_INVALID");
5653
5940
  }
@@ -5729,8 +6016,8 @@ async function updateProject(options) {
5729
6016
  }
5730
6017
 
5731
6018
  // src/config/init-config.ts
5732
- import { isAbsolute as isAbsolute2, join as join16 } from "node:path";
5733
- import { readFile as readFile15 } from "node:fs/promises";
6019
+ import { isAbsolute as isAbsolute2, join as join17 } from "node:path";
6020
+ import { readFile as readFile16 } from "node:fs/promises";
5734
6021
  var InitConfigurationError = class extends Error {
5735
6022
  exitCode;
5736
6023
  code;
@@ -5813,9 +6100,9 @@ function hasOwn(record, key) {
5813
6100
  async function resolveInitConfig(cwd, flags, prompts = {}, warnings = []) {
5814
6101
  let fileConfig = {};
5815
6102
  if (flags.config !== void 0) {
5816
- const path = isAbsolute2(flags.config) ? flags.config : join16(cwd, flags.config);
6103
+ const path = isAbsolute2(flags.config) ? flags.config : join17(cwd, flags.config);
5817
6104
  try {
5818
- fileConfig = JSON.parse(await readFile15(path, "utf8"));
6105
+ fileConfig = JSON.parse(await readFile16(path, "utf8"));
5819
6106
  } catch (error) {
5820
6107
  throw new InitConfigurationError(
5821
6108
  "unable to read init config: " + (error instanceof Error ? error.message : String(error)),
@@ -5897,8 +6184,8 @@ function serializeCliResult(result) {
5897
6184
  }
5898
6185
 
5899
6186
  // src/config/codebuddy-setup.ts
5900
- import { mkdir as mkdir5, readFile as readFile16, readdir as readdir8, stat as stat4, writeFile as writeFile7 } from "node:fs/promises";
5901
- import { basename as basename3, dirname as dirname5, extname as extname2, join as join17 } from "node:path";
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";
5902
6189
  var MANAGED_RULE_NAMES = /* @__PURE__ */ new Set([
5903
6190
  "harness-general.md",
5904
6191
  "harness-general.mdc",
@@ -5909,7 +6196,7 @@ var SENSITIVE_ASSIGNMENT = /(?:password|passwd|token|secret|access[_-]?key|priva
5909
6196
  var PRIVATE_KEY_BLOCK = /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/i;
5910
6197
  async function exists4(path) {
5911
6198
  try {
5912
- await stat4(path);
6199
+ await stat5(path);
5913
6200
  return true;
5914
6201
  } catch {
5915
6202
  return false;
@@ -5917,7 +6204,7 @@ async function exists4(path) {
5917
6204
  }
5918
6205
  async function readJsonObject(path) {
5919
6206
  try {
5920
- const parsed = JSON.parse(await readFile16(path, "utf8"));
6207
+ const parsed = JSON.parse(await readFile17(path, "utf8"));
5921
6208
  return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
5922
6209
  } catch (error) {
5923
6210
  if (error instanceof Error && "code" in error && error.code === "ENOENT") return {};
@@ -5925,10 +6212,10 @@ async function readJsonObject(path) {
5925
6212
  }
5926
6213
  }
5927
6214
  async function inspectCodeBuddySetup(projectRoot, surface = "both") {
5928
- const rulesRoot = join17(projectRoot, ".claude", "rules");
6215
+ const rulesRoot = join18(projectRoot, ".claude", "rules");
5929
6216
  let ruleNames = [];
5930
6217
  try {
5931
- ruleNames = (await readdir8(rulesRoot, { withFileTypes: true })).filter((entry) => entry.isFile() && [".md", ".mdc"].includes(extname2(entry.name).toLowerCase())).map((entry) => entry.name).filter((name) => !MANAGED_RULE_NAMES.has(name)).sort();
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();
5932
6219
  } catch (error) {
5933
6220
  if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
5934
6221
  }
@@ -5936,11 +6223,11 @@ async function inspectCodeBuddySetup(projectRoot, surface = "both") {
5936
6223
  const currentClaudeRules = [];
5937
6224
  const conflictingClaudeRules = [];
5938
6225
  for (const name of ruleNames) {
5939
- const sourceContent = await readFile16(join17(rulesRoot, name), "utf8");
6226
+ const sourceContent = await readFile17(join18(rulesRoot, name), "utf8");
5940
6227
  const targetContents = await Promise.all(
5941
6228
  destinationTargets(projectRoot, surface, name).map(async (target) => {
5942
6229
  try {
5943
- return await readFile16(target, "utf8");
6230
+ return await readFile17(target, "utf8");
5944
6231
  } catch (error) {
5945
6232
  if (error instanceof Error && "code" in error && error.code === "ENOENT") return null;
5946
6233
  throw error;
@@ -5954,22 +6241,22 @@ async function inspectCodeBuddySetup(projectRoot, surface = "both") {
5954
6241
  currentClaudeRules.push(name);
5955
6242
  }
5956
6243
  }
5957
- const mcp = await readJsonObject(join17(projectRoot, ".mcp.json"));
6244
+ const mcp = await readJsonObject(join18(projectRoot, ".mcp.json"));
5958
6245
  const servers = mcp?.mcpServers;
5959
6246
  const configured = servers !== null && typeof servers === "object" && !Array.isArray(servers) && Object.prototype.hasOwnProperty.call(servers, "codegraph");
5960
6247
  return {
5961
6248
  claudeRules,
5962
6249
  currentClaudeRules,
5963
6250
  conflictingClaudeRules,
5964
- hasCodeGraphIndex: await exists4(join17(projectRoot, ".codegraph")),
6251
+ hasCodeGraphIndex: await exists4(join18(projectRoot, ".codegraph")),
5965
6252
  codeGraphConfigured: configured
5966
6253
  };
5967
6254
  }
5968
6255
  function destinationTargets(root, surface, name) {
5969
- const stem = basename3(name, extname2(name));
6256
+ const stem = basename4(name, extname2(name));
5970
6257
  const targets = [];
5971
- if (surface !== "cli") targets.push(join17(root, ".codebuddy", ".rules", `${stem}.mdc`));
5972
- if (surface !== "ide") targets.push(join17(root, ".codebuddy", "rules", `${stem}.md`));
6258
+ if (surface !== "cli") targets.push(join18(root, ".codebuddy", ".rules", `${stem}.mdc`));
6259
+ if (surface !== "ide") targets.push(join18(root, ".codebuddy", "rules", `${stem}.md`));
5973
6260
  return targets;
5974
6261
  }
5975
6262
  async function applyCodeBuddySetup(options) {
@@ -5983,8 +6270,8 @@ async function applyCodeBuddySetup(options) {
5983
6270
  if (options.syncClaudeRules) {
5984
6271
  const plan = await inspectCodeBuddySetup(options.projectRoot, options.surface);
5985
6272
  for (const name of plan.claudeRules) {
5986
- const source = join17(options.projectRoot, ".claude", "rules", name);
5987
- const content = await readFile16(source, "utf8");
6273
+ const source = join18(options.projectRoot, ".claude", "rules", name);
6274
+ const content = await readFile17(source, "utf8");
5988
6275
  if (SENSITIVE_ASSIGNMENT.test(content) || PRIVATE_KEY_BLOCK.test(content)) {
5989
6276
  result.skippedSensitive.push(name);
5990
6277
  continue;
@@ -5994,14 +6281,14 @@ async function applyCodeBuddySetup(options) {
5994
6281
  result.preserved.push(target);
5995
6282
  continue;
5996
6283
  }
5997
- await mkdir5(dirname5(target), { recursive: true });
5998
- await writeFile7(target, content, { encoding: "utf8", flag: "wx" });
6284
+ await mkdir6(dirname6(target), { recursive: true });
6285
+ await writeFile8(target, content, { encoding: "utf8", flag: "wx" });
5999
6286
  result.copied.push(target);
6000
6287
  }
6001
6288
  }
6002
6289
  }
6003
6290
  if (options.configureCodeGraph) {
6004
- const path = join17(options.projectRoot, ".mcp.json");
6291
+ const path = join18(options.projectRoot, ".mcp.json");
6005
6292
  const current = await readJsonObject(path);
6006
6293
  if (current === null) {
6007
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");
@@ -6016,7 +6303,7 @@ async function applyCodeBuddySetup(options) {
6016
6303
  codegraph: { command: "codegraph", args: ["serve", "--mcp"] }
6017
6304
  }
6018
6305
  };
6019
- await writeFile7(path, JSON.stringify(next, null, 2) + "\n", "utf8");
6306
+ await writeFile8(path, JSON.stringify(next, null, 2) + "\n", "utf8");
6020
6307
  result.mcpUpdated = true;
6021
6308
  }
6022
6309
  }
@@ -6025,13 +6312,13 @@ async function applyCodeBuddySetup(options) {
6025
6312
  }
6026
6313
 
6027
6314
  // src/commands/refresh.ts
6028
- import { readFile as readFile17 } from "node:fs/promises";
6029
- import { join as join18 } from "node:path";
6315
+ import { readFile as readFile18 } from "node:fs/promises";
6316
+ import { join as join19 } from "node:path";
6030
6317
  import { parse as parseYaml9 } from "yaml";
6031
6318
  async function detectProject(root) {
6032
6319
  let content;
6033
6320
  try {
6034
- content = await readFile17(join18(root, ".harness", "project.yaml"), "utf8");
6321
+ content = await readFile18(join19(root, ".harness", "project.yaml"), "utf8");
6035
6322
  } catch (error) {
6036
6323
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
6037
6324
  return { status: "absent" };
@@ -6863,12 +7150,94 @@ async function runUpdate(options, dependencies) {
6863
7150
  }
6864
7151
  }
6865
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
+
6866
7235
  // src/commands/recovery.ts
6867
- import { stat as stat5 } from "node:fs/promises";
6868
- import { join as join19 } from "node:path";
7236
+ import { stat as stat6 } from "node:fs/promises";
7237
+ import { join as join20 } from "node:path";
6869
7238
  async function exists5(path) {
6870
7239
  try {
6871
- await stat5(path);
7240
+ await stat6(path);
6872
7241
  return true;
6873
7242
  } catch {
6874
7243
  return false;
@@ -6925,7 +7294,7 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
6925
7294
  return 5;
6926
7295
  }
6927
7296
  }
6928
- const initialized = await exists5(join19(dependencies.cwd, ".harness", "project.yaml"));
7297
+ const initialized = await exists5(join20(dependencies.cwd, ".harness", "project.yaml"));
6929
7298
  if (!initialized || options.nonInteractive === true || explicitConfigure(options)) {
6930
7299
  return null;
6931
7300
  }
@@ -6968,8 +7337,8 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
6968
7337
  }
6969
7338
 
6970
7339
  // src/workflow-data/resolve.ts
6971
- import { cp, mkdir as mkdir6, readdir as readdir9, readFile as readFile18, rm as rm8, stat as stat6 } from "node:fs/promises";
6972
- import { dirname as dirname6, join as join20, relative as relative2 } from "node:path";
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";
6973
7342
  import { fileURLToPath } from "node:url";
6974
7343
  var WorkflowDataResolutionError = class extends Error {
6975
7344
  code;
@@ -6983,7 +7352,7 @@ var WorkflowDataResolutionError = class extends Error {
6983
7352
  };
6984
7353
  async function pathExists3(path) {
6985
7354
  try {
6986
- await stat6(path);
7355
+ await stat7(path);
6987
7356
  return true;
6988
7357
  } catch {
6989
7358
  return false;
@@ -7003,17 +7372,17 @@ function workflowPackageName(family, env) {
7003
7372
  return `${scope}/workflow-${family}`;
7004
7373
  }
7005
7374
  async function listFilesRecursive(root, base = root) {
7006
- const entries = await readdir9(root, { withFileTypes: true });
7375
+ const entries = await readdir10(root, { withFileTypes: true });
7007
7376
  const files = [];
7008
7377
  for (const entry of entries) {
7009
- const absolute = join20(root, entry.name);
7378
+ const absolute = join21(root, entry.name);
7010
7379
  if (entry.isDirectory()) {
7011
7380
  files.push(...await listFilesRecursive(absolute, base));
7012
7381
  continue;
7013
7382
  }
7014
7383
  if (!entry.isFile()) continue;
7015
- const rel = relative2(base, absolute).replaceAll("\\", "/");
7016
- files.push({ path: rel, content: await readFile18(absolute, "utf8") });
7384
+ const rel = relative3(base, absolute).replaceAll("\\", "/");
7385
+ files.push({ path: rel, content: await readFile19(absolute, "utf8") });
7017
7386
  }
7018
7387
  return files;
7019
7388
  }
@@ -7026,7 +7395,7 @@ async function verifyWorkflowPackageIntegrity(resourcesRoot) {
7026
7395
  const manifest = await readWorkflowFamilyManifest(resourcesRoot);
7027
7396
  const expected = manifest.content_sha256;
7028
7397
  if (typeof expected !== "string" || expected.length === 0) return;
7029
- const harnessRoot = join20(resourcesRoot, "harness");
7398
+ const harnessRoot = join21(resourcesRoot, "harness");
7030
7399
  if (!await pathExists3(harnessRoot)) {
7031
7400
  throw new WorkflowDataResolutionError(
7032
7401
  "\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u7F3A\u5C11 harness/\uFF0C\u65E0\u6CD5\u6821\u9A8C SHA-256",
@@ -7045,40 +7414,40 @@ async function verifyWorkflowPackageIntegrity(resourcesRoot) {
7045
7414
  }
7046
7415
  }
7047
7416
  async function monorepoResourcesRoot() {
7048
- const here = dirname6(fileURLToPath(import.meta.url));
7417
+ const here = dirname7(fileURLToPath(import.meta.url));
7049
7418
  const candidates = [
7050
7419
  // TypeScript source: packages/cli/src/workflow-data -> packages/workflow-data-harness.
7051
- join20(here, "../../../workflow-data-harness"),
7420
+ join21(here, "../../../workflow-data-harness"),
7052
7421
  // Bundled CLI: packages/cli/dist -> packages/workflow-data-harness.
7053
- join20(here, "../../workflow-data-harness"),
7422
+ join21(here, "../../workflow-data-harness"),
7054
7423
  // Test/build layouts that preserve additional source directory levels.
7055
- join20(here, "../../../../packages/workflow-data-harness")
7424
+ join21(here, "../../../../packages/workflow-data-harness")
7056
7425
  ];
7057
7426
  for (const candidate of candidates) {
7058
- if (await pathExists3(join20(candidate, "harness", "manifests"))) return candidate;
7427
+ if (await pathExists3(join21(candidate, "harness", "manifests"))) return candidate;
7059
7428
  }
7060
7429
  return null;
7061
7430
  }
7062
7431
  async function siblingWorkflowPackage(cwd) {
7063
- const here = dirname6(fileURLToPath(import.meta.url));
7432
+ const here = dirname7(fileURLToPath(import.meta.url));
7064
7433
  const candidates = [
7065
- join20(cwd, "node_modules", "@hunter-harness", "workflow-harness"),
7434
+ join21(cwd, "node_modules", "@hunter-harness", "workflow-harness"),
7066
7435
  // Published layout: node_modules/hunter-harness/dist/bin.js alongside the
7067
7436
  // scoped workflow package in node_modules/@hunter-harness/workflow-harness.
7068
- join20(here, "..", "..", "@hunter-harness", "workflow-harness"),
7437
+ join21(here, "..", "..", "@hunter-harness", "workflow-harness"),
7069
7438
  // Monorepo bundled layout: packages/cli/dist/bin.js.
7070
- join20(here, "..", "..", "workflow-data-harness")
7439
+ join21(here, "..", "..", "workflow-data-harness")
7071
7440
  ];
7072
7441
  for (const candidate of candidates) {
7073
- if (await pathExists3(join20(candidate, "harness", "manifests"))) return candidate;
7442
+ if (await pathExists3(join21(candidate, "harness", "manifests"))) return candidate;
7074
7443
  }
7075
7444
  return null;
7076
7445
  }
7077
7446
  async function readCachedNpmPackageVersion(cacheRoot) {
7078
- const manifestPath = join20(cacheRoot, "package.json");
7447
+ const manifestPath = join21(cacheRoot, "package.json");
7079
7448
  if (!await pathExists3(manifestPath)) return null;
7080
7449
  try {
7081
- const pkg = JSON.parse(await readFile18(manifestPath, "utf8"));
7450
+ const pkg = JSON.parse(await readFile19(manifestPath, "utf8"));
7082
7451
  return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : null;
7083
7452
  } catch {
7084
7453
  return null;
@@ -7100,13 +7469,13 @@ async function latestWorkflowCacheIsStale(cacheRoot, packageName, resolveManifes
7100
7469
  }
7101
7470
  }
7102
7471
  async function materializeWorkflowPackageCache(cacheRoot, packageSpec, extract) {
7103
- await mkdir6(cacheRoot, { recursive: true });
7104
- const staging = join20(cacheRoot, ".extract");
7472
+ await mkdir7(cacheRoot, { recursive: true });
7473
+ const staging = join21(cacheRoot, ".extract");
7105
7474
  await rm8(staging, { recursive: true, force: true });
7106
- await mkdir6(staging, { recursive: true });
7475
+ await mkdir7(staging, { recursive: true });
7107
7476
  await extract(packageSpec, staging);
7108
- const packageDir = await pathExists3(join20(staging, "harness", "manifests")) ? staging : join20(staging, "package");
7109
- if (!await pathExists3(join20(packageDir, "harness", "manifests"))) {
7477
+ const packageDir = await pathExists3(join21(staging, "harness", "manifests")) ? staging : join21(staging, "package");
7478
+ if (!await pathExists3(join21(packageDir, "harness", "manifests"))) {
7110
7479
  throw new WorkflowDataResolutionError(
7111
7480
  "\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u5185\u5BB9\u65E0\u6548\uFF1A\u7F3A\u5C11 harness/manifests",
7112
7481
  "WORKFLOW_DATA_INVALID",
@@ -7135,8 +7504,8 @@ async function resolveWorkflowResourcesRoot(options, argv = []) {
7135
7504
  const packageName = workflowPackageName(family, options.env);
7136
7505
  const packageSpec = version === "latest" ? packageName : `${packageName}@${version}`;
7137
7506
  const cacheKey = packageSpec.replace("/", "+");
7138
- const cacheRoot = join20(options.cwd, ".harness", "cache", "workflow-packages", cacheKey);
7139
- if (await pathExists3(join20(cacheRoot, "harness", "manifests"))) {
7507
+ const cacheRoot = join21(options.cwd, ".harness", "cache", "workflow-packages", cacheKey);
7508
+ if (await pathExists3(join21(cacheRoot, "harness", "manifests"))) {
7140
7509
  if (version === "latest") {
7141
7510
  const stale = await latestWorkflowCacheIsStale(cacheRoot, packageName, options.pacoteManifest);
7142
7511
  if (stale) {
@@ -7182,9 +7551,9 @@ function describeWorkflowDataFetchFailure(error, packageSpec) {
7182
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}`;
7183
7552
  }
7184
7553
  async function readWorkflowFamilyManifest(resourcesRoot) {
7185
- const manifestPath = join20(resourcesRoot, "hunter-workflow-family.json");
7554
+ const manifestPath = join21(resourcesRoot, "hunter-workflow-family.json");
7186
7555
  if (!await pathExists3(manifestPath)) return {};
7187
- return JSON.parse(await readFile18(manifestPath, "utf8"));
7556
+ return JSON.parse(await readFile19(manifestPath, "utf8"));
7188
7557
  }
7189
7558
 
7190
7559
  // src/bin.ts
@@ -7298,6 +7667,12 @@ async function runCli(argv, overrides = {}) {
7298
7667
  dependencies
7299
7668
  );
7300
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
+ });
7301
7676
  try {
7302
7677
  await program.parseAsync(["node", "hunter-harness", ...argv]);
7303
7678
  return exitCode;