hunter-harness 0.2.30 → 0.2.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/bin.js +401 -164
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -1233,7 +1233,7 @@ async function assertNoSymlinks(root, relativePath) {
1233
1233
  async function withRetry(action, options) {
1234
1234
  const attempts = options.attempts ?? 3;
1235
1235
  const sleep = options.sleep ?? (async (milliseconds) => {
1236
- await new Promise((resolve11) => setTimeout(resolve11, milliseconds));
1236
+ await new Promise((resolve13) => setTimeout(resolve13, milliseconds));
1237
1237
  });
1238
1238
  let lastError;
1239
1239
  for (let attempt = 1; attempt <= attempts; attempt += 1) {
@@ -2309,18 +2309,39 @@ function portable(path) {
2309
2309
  function normalizeRuleContent(content) {
2310
2310
  return content.replace(/\r\n/g, "\n").trimEnd() + "\n";
2311
2311
  }
2312
- function canonicalImportContent(content) {
2312
+ function parseAgentRule(content) {
2313
2313
  const normalized = content.replace(/\r\n/g, "\n");
2314
- if (!normalized.startsWith("---\n"))
2315
- return normalizeRuleContent(normalized);
2314
+ if (!normalized.startsWith("---\n")) {
2315
+ return {
2316
+ canonical: normalizeRuleContent(normalized),
2317
+ globalFrontmatterPrefix: null
2318
+ };
2319
+ }
2316
2320
  const closing = normalized.indexOf("\n---\n", 4);
2317
- if (closing < 0)
2318
- return null;
2321
+ if (closing < 0) {
2322
+ return { canonical: null, globalFrontmatterPrefix: null };
2323
+ }
2319
2324
  const frontmatter = normalized.slice(4, closing);
2320
2325
  if (/^\s*(?:globs?|paths?)\s*:/im.test(frontmatter) || /^\s*alwaysApply\s*:\s*false\s*$/im.test(frontmatter)) {
2321
- return null;
2326
+ return { canonical: null, globalFrontmatterPrefix: null };
2322
2327
  }
2323
- return normalizeRuleContent(normalized.slice(closing + 5));
2328
+ const bodyStart = closing + 5;
2329
+ return {
2330
+ canonical: normalizeRuleContent(normalized.slice(bodyStart).replace(/^\n+/, "")),
2331
+ globalFrontmatterPrefix: normalized.slice(0, bodyStart)
2332
+ };
2333
+ }
2334
+ function canonicalImportContent(content) {
2335
+ return parseAgentRule(content).canonical;
2336
+ }
2337
+ function projectionContent(target, current, canonical) {
2338
+ if (current === null || !target.toLowerCase().endsWith(".mdc"))
2339
+ return canonical;
2340
+ const parsed = parseAgentRule(current);
2341
+ if (parsed.globalFrontmatterPrefix === null)
2342
+ return canonical;
2343
+ return `${parsed.globalFrontmatterPrefix}
2344
+ ${canonical}`;
2324
2345
  }
2325
2346
  async function collectImportCandidates(root, previous, result) {
2326
2347
  const candidates = [];
@@ -2422,9 +2443,10 @@ async function synchronizeProjectRules(projectRoot, agents, surface = "both") {
2422
2443
  result.unchanged.push(target);
2423
2444
  next.targets[target] = current === null ? incomingHash : sha256(current);
2424
2445
  } else if (current === null || previous.targets[target] === sha256(current)) {
2425
- await atomicWrite(path, content);
2446
+ const projected = projectionContent(target, current, content);
2447
+ await atomicWrite(path, projected);
2426
2448
  result.written.push(target);
2427
- next.targets[target] = incomingHash;
2449
+ next.targets[target] = sha256(projected);
2428
2450
  } else {
2429
2451
  result.conflicts.push(target);
2430
2452
  next.targets[target] = previous.targets[target] ?? sha256(current);
@@ -3209,16 +3231,157 @@ async function synchronizeRuleCandidates(projectRoot, options = {}) {
3209
3231
  };
3210
3232
  }
3211
3233
 
3212
- // ../core/dist/project/refresh.js
3234
+ // ../core/dist/project/rule-review.js
3213
3235
  import { createHash as createHash7 } from "node:crypto";
3214
- import { readFile as readFile7, readdir as readdir5, rmdir } from "node:fs/promises";
3215
- import { dirname as dirname5, join as join8, resolve as resolve7 } from "node:path";
3236
+ import { mkdir as mkdir6, readFile as readFile7, rename as rename5, writeFile as writeFile6 } from "node:fs/promises";
3237
+ import { basename as basename4, dirname as dirname5, join as join8, relative as relative2, resolve as resolve7 } from "node:path";
3238
+ var CANDIDATE_PATH2 = ".harness/knowledge/rule-candidates.json";
3239
+ var DECISION_PATH = ".harness/knowledge/rule-decisions.json";
3240
+ var RULES_ROOT2 = ".harness/rules";
3241
+ function sha2563(content) {
3242
+ return createHash7("sha256").update(content).digest("hex");
3243
+ }
3244
+ async function readOptional2(path) {
3245
+ try {
3246
+ return await readFile7(path, "utf8");
3247
+ } catch (error) {
3248
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
3249
+ return null;
3250
+ throw error;
3251
+ }
3252
+ }
3253
+ async function readJson(path, fallback) {
3254
+ const content = await readOptional2(path);
3255
+ if (content === null)
3256
+ return fallback;
3257
+ return JSON.parse(content);
3258
+ }
3259
+ async function atomicWrite3(path, content) {
3260
+ await mkdir6(dirname5(path), { recursive: true });
3261
+ const temporary = `${path}.${process.pid}.tmp`;
3262
+ await writeFile6(temporary, content, "utf8");
3263
+ await rename5(temporary, path);
3264
+ }
3265
+ function candidateRevision(candidate) {
3266
+ return sha2563(JSON.stringify(candidate));
3267
+ }
3268
+ function portable3(path) {
3269
+ return path.replaceAll("\\", "/");
3270
+ }
3271
+ function ruleTarget2(root, input) {
3272
+ const portablePath = portable3(input);
3273
+ if (!portablePath.startsWith(`${RULES_ROOT2}/`) || !portablePath.toLowerCase().endsWith(".md") || basename4(portablePath) === "") {
3274
+ throw new Error(`RULE_PATCH_PATH_INVALID: ${input}`);
3275
+ }
3276
+ const target = resolve7(root, ...portablePath.split("/"));
3277
+ const rulesRoot = resolve7(root, ...RULES_ROOT2.split("/"));
3278
+ const relativePath = portable3(relative2(rulesRoot, target));
3279
+ if (relativePath === ".." || relativePath.startsWith("../")) {
3280
+ throw new Error(`RULE_PATCH_PATH_INVALID: ${input}`);
3281
+ }
3282
+ return target;
3283
+ }
3284
+ function validateDecision(decision, candidates) {
3285
+ const candidate = candidates.get(decision.candidate_id);
3286
+ if (candidate === void 0) {
3287
+ throw new Error(`RULE_CANDIDATE_UNKNOWN: ${decision.candidate_id}`);
3288
+ }
3289
+ if (candidateRevision(candidate) !== decision.candidate_revision) {
3290
+ throw new Error(`RULE_CANDIDATE_STALE: ${decision.candidate_id}`);
3291
+ }
3292
+ if (decision.dispositions.length === 0 || decision.reason.trim().length < 8) {
3293
+ throw new Error(`RULE_DECISION_INVALID: ${decision.candidate_id}`);
3294
+ }
3295
+ if (decision.dispositions.includes("defer") && decision.review_after === void 0) {
3296
+ throw new Error(`RULE_DECISION_REVIEW_AFTER_REQUIRED: ${decision.candidate_id}`);
3297
+ }
3298
+ if (decision.dispositions.includes("public-rule") !== (decision.rule_patch !== void 0)) {
3299
+ throw new Error(`RULE_PATCH_REQUIRED: ${decision.candidate_id}`);
3300
+ }
3301
+ return candidate;
3302
+ }
3303
+ async function readCandidates(root) {
3304
+ return readJson(join8(root, ...CANDIDATE_PATH2.split("/")), { schema_version: 1, source_hashes: {}, candidates: [] });
3305
+ }
3306
+ async function readDecisions(root) {
3307
+ return readJson(join8(root, ...DECISION_PATH.split("/")), { schema_version: 1, decisions: [] });
3308
+ }
3309
+ async function exportRuleReviewQueue(projectRoot) {
3310
+ const root = resolve7(projectRoot);
3311
+ const candidates = await readCandidates(root);
3312
+ const decisions = await readDecisions(root);
3313
+ const decided = new Set(decisions.decisions.map((decision) => `${decision.candidate_id}\0${decision.candidate_revision}`));
3314
+ const pending = candidates.candidates.flatMap((candidate) => {
3315
+ const revision = candidateRevision(candidate);
3316
+ return decided.has(`${candidate.id}\0${revision}`) ? [] : [{
3317
+ candidate_id: candidate.id,
3318
+ candidate_revision: revision,
3319
+ candidate
3320
+ }];
3321
+ });
3322
+ return {
3323
+ schema_version: 1,
3324
+ pending,
3325
+ decided: candidates.candidates.length - pending.length
3326
+ };
3327
+ }
3328
+ async function applyRuleReviewDecisions(projectRoot, input) {
3329
+ if (input.schema_version !== 1 || !Array.isArray(input.decisions)) {
3330
+ throw new Error("RULE_DECISIONS_INVALID");
3331
+ }
3332
+ const root = resolve7(projectRoot);
3333
+ const candidateManifest = await readCandidates(root);
3334
+ const candidates = new Map(candidateManifest.candidates.map((candidate) => [
3335
+ candidate.id,
3336
+ candidate
3337
+ ]));
3338
+ const plannedPatches = [];
3339
+ for (const decision of input.decisions) {
3340
+ validateDecision(decision, candidates);
3341
+ if (decision.rule_patch === void 0)
3342
+ continue;
3343
+ const target = ruleTarget2(root, decision.rule_patch.target_path);
3344
+ const current = await readOptional2(target);
3345
+ const currentHash = current === null ? null : sha2563(current);
3346
+ if (currentHash !== decision.rule_patch.expected_sha256) {
3347
+ throw new Error(`RULE_PATCH_STALE: ${decision.rule_patch.target_path}`);
3348
+ }
3349
+ plannedPatches.push({ path: target, content: decision.rule_patch.content });
3350
+ }
3351
+ const currentManifest = await readDecisions(root);
3352
+ const replacements = new Map(input.decisions.map((decision) => [
3353
+ `${decision.candidate_id}\0${decision.candidate_revision}`,
3354
+ decision
3355
+ ]));
3356
+ for (const decision of currentManifest.decisions) {
3357
+ const key = `${decision.candidate_id}\0${decision.candidate_revision}`;
3358
+ if (!replacements.has(key))
3359
+ replacements.set(key, decision);
3360
+ }
3361
+ const next = {
3362
+ schema_version: 1,
3363
+ decisions: [...replacements.values()].sort((left, right) => left.candidate_id.localeCompare(right.candidate_id) || left.candidate_revision.localeCompare(right.candidate_revision))
3364
+ };
3365
+ for (const patch of plannedPatches)
3366
+ await atomicWrite3(patch.path, patch.content);
3367
+ await atomicWrite3(join8(root, ...DECISION_PATH.split("/")), JSON.stringify(next, null, 2) + "\n");
3368
+ return {
3369
+ applied: plannedPatches.length,
3370
+ recorded: input.decisions.length,
3371
+ path: DECISION_PATH
3372
+ };
3373
+ }
3374
+
3375
+ // ../core/dist/project/refresh.js
3376
+ import { createHash as createHash8 } from "node:crypto";
3377
+ import { readFile as readFile8, readdir as readdir5, rmdir } from "node:fs/promises";
3378
+ import { dirname as dirname6, join as join9, resolve as resolve8 } from "node:path";
3216
3379
  import { parse as parseYaml3, stringify as stringifyYaml2 } from "yaml";
3217
3380
  var INSTALLED_STATE_PATH = ".harness/state/local/installed-harness-bundle.json";
3218
3381
  var CONTEXT_INDEX_PATH2 = ".harness/context-index.json";
3219
3382
  async function fileHex2(path) {
3220
3383
  try {
3221
- return createHash7("sha256").update(await readFile7(path)).digest("hex");
3384
+ return createHash8("sha256").update(await readFile8(path)).digest("hex");
3222
3385
  } catch (error) {
3223
3386
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
3224
3387
  return null;
@@ -3228,7 +3391,7 @@ async function fileHex2(path) {
3228
3391
  }
3229
3392
  async function readOptionalText(path) {
3230
3393
  try {
3231
- return await readFile7(path, "utf8");
3394
+ return await readFile8(path, "utf8");
3232
3395
  } catch (error) {
3233
3396
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
3234
3397
  return "";
@@ -3237,7 +3400,7 @@ async function readOptionalText(path) {
3237
3400
  }
3238
3401
  }
3239
3402
  async function readInstalledState(root) {
3240
- const content = await readOptionalText(join8(root, INSTALLED_STATE_PATH));
3403
+ const content = await readOptionalText(join9(root, INSTALLED_STATE_PATH));
3241
3404
  if (content === "") {
3242
3405
  return {
3243
3406
  profile: null,
@@ -3307,7 +3470,7 @@ async function readInstalledState(root) {
3307
3470
  };
3308
3471
  }
3309
3472
  async function readInstalledAgentConfiguration(projectRoot) {
3310
- const installed = await readInstalledState(resolve7(projectRoot));
3473
+ const installed = await readInstalledState(resolve8(projectRoot));
3311
3474
  return {
3312
3475
  agents: installed.adapters,
3313
3476
  profiles: Object.fromEntries(installed.adapters.map((agent) => [
@@ -3317,7 +3480,7 @@ async function readInstalledAgentConfiguration(projectRoot) {
3317
3480
  };
3318
3481
  }
3319
3482
  async function readContextIndexBundleHash(root) {
3320
- const content = await readOptionalText(join8(root, CONTEXT_INDEX_PATH2));
3483
+ const content = await readOptionalText(join9(root, CONTEXT_INDEX_PATH2));
3321
3484
  if (content === "")
3322
3485
  return null;
3323
3486
  try {
@@ -3329,9 +3492,9 @@ async function readContextIndexBundleHash(root) {
3329
3492
  }
3330
3493
  }
3331
3494
  async function pruneEmptyParentDirs(root, deletedPaths, boundaryPaths) {
3332
- const boundaries = new Set(boundaryPaths.map((path) => join8(root, path)));
3495
+ const boundaries = new Set(boundaryPaths.map((path) => join9(root, path)));
3333
3496
  for (const deleted of deletedPaths) {
3334
- let dir = dirname5(join8(root, deleted));
3497
+ let dir = dirname6(join9(root, deleted));
3335
3498
  while (dir.startsWith(root) && !boundaries.has(dir)) {
3336
3499
  let entries;
3337
3500
  try {
@@ -3346,7 +3509,7 @@ async function pruneEmptyParentDirs(root, deletedPaths, boundaryPaths) {
3346
3509
  } catch {
3347
3510
  break;
3348
3511
  }
3349
- dir = dirname5(dir);
3512
+ dir = dirname6(dir);
3350
3513
  }
3351
3514
  }
3352
3515
  }
@@ -3373,7 +3536,7 @@ function sortByTarget(items) {
3373
3536
  return [...items].sort((left, right) => left.target_path.localeCompare(right.target_path));
3374
3537
  }
3375
3538
  async function reconcileContextIndex(root, agents, profiles, manifests, codebuddySurface2, verifications) {
3376
- const existing = await readOptionalText(join8(root, CONTEXT_INDEX_PATH2));
3539
+ const existing = await readOptionalText(join9(root, CONTEXT_INDEX_PATH2));
3377
3540
  let codebase = {
3378
3541
  map: ".harness/codebase/map",
3379
3542
  status: "missing"
@@ -3426,7 +3589,7 @@ async function reconcileContextIndex(root, agents, profiles, manifests, codebudd
3426
3589
  }
3427
3590
  async function projectTransitionOperation(root, agents, profiles, codebuddySurface2) {
3428
3591
  const path = ".harness/project.yaml";
3429
- const content = await readOptionalText(join8(root, path));
3592
+ const content = await readOptionalText(join9(root, path));
3430
3593
  if (content === "")
3431
3594
  return null;
3432
3595
  const project = parseYaml3(content);
@@ -3461,11 +3624,11 @@ function mergeTargets(targets) {
3461
3624
  }).sort((left, right) => left.target_path.localeCompare(right.target_path));
3462
3625
  }
3463
3626
  async function reconcileMarkdownBlock(root, fileName, blockId, content, remove, ops, conflicts, preserved) {
3464
- const original = await readOptionalText(join8(root, fileName));
3627
+ const original = await readOptionalText(join9(root, fileName));
3465
3628
  const synthetic = {
3466
3629
  source_path: fileName,
3467
3630
  target_path: fileName,
3468
- sha256: createHash7("sha256").update(content).digest("hex"),
3631
+ sha256: createHash8("sha256").update(content).digest("hex"),
3469
3632
  bytes: new TextEncoder().encode(content)
3470
3633
  };
3471
3634
  let next;
@@ -3480,7 +3643,7 @@ async function reconcileMarkdownBlock(root, fileName, blockId, content, remove,
3480
3643
  next = refreshed.content;
3481
3644
  }
3482
3645
  } catch {
3483
- const current = original === "" ? null : createHash7("sha256").update(original).digest("hex");
3646
+ const current = original === "" ? null : createHash8("sha256").update(original).digest("hex");
3484
3647
  preserved.push(item(synthetic, "preserve", "MALFORMED_MANAGED_BLOCK", current, synthetic.sha256));
3485
3648
  conflicts.push(conflict(synthetic, "MALFORMED_MANAGED_BLOCK", current, synthetic.sha256));
3486
3649
  return;
@@ -3500,7 +3663,7 @@ function stateWithoutInstalledAt(value) {
3500
3663
  return copy;
3501
3664
  }
3502
3665
  async function refreshProject(options) {
3503
- const root = resolve7(options.projectRoot);
3666
+ const root = resolve8(options.projectRoot);
3504
3667
  const installed = await readInstalledState(root);
3505
3668
  const oldAgents = installed.adapters.length > 0 ? installed.adapters : ["claude-code"];
3506
3669
  const selectedAgents = sortHarnessAgents(options.agents);
@@ -3584,7 +3747,7 @@ async function refreshProject(options) {
3584
3747
  const newStateFiles = installed.files.filter((entry) => entry.owner === "shared" || !selectedSet.has(entry.owner));
3585
3748
  for (const target of newManaged) {
3586
3749
  const incoming = target.sha256;
3587
- const current = await fileHex2(join8(root, target.target_path));
3750
+ const current = await fileHex2(join9(root, target.target_path));
3588
3751
  if (current === null) {
3589
3752
  applied.push(item(target, "add", "MISSING_TARGET", null, incoming));
3590
3753
  ops.push({ operation: "add", path: target.target_path, content: target.bytes });
@@ -3612,7 +3775,7 @@ async function refreshProject(options) {
3612
3775
  }
3613
3776
  }
3614
3777
  for (const target of oldOnly) {
3615
- const current = await fileHex2(join8(root, target.target_path));
3778
+ const current = await fileHex2(join9(root, target.target_path));
3616
3779
  if (current === null) {
3617
3780
  continue;
3618
3781
  }
@@ -3655,7 +3818,7 @@ async function refreshProject(options) {
3655
3818
  const verifyEntries = [];
3656
3819
  for (const target of verifyTargets) {
3657
3820
  const rel = target.target_path.replace(/\\/g, "/");
3658
- const actual = await fileHex2(join8(root, target.target_path));
3821
+ const actual = await fileHex2(join9(root, target.target_path));
3659
3822
  verifyEntries.push({ relpath: rel, sha256: actual ?? "" });
3660
3823
  if (actual === null) {
3661
3824
  verifyMismatches.push({ relpath: rel, expected: target.sha256, actual: "<missing>" });
@@ -3688,19 +3851,19 @@ async function refreshProject(options) {
3688
3851
  owner: "shared",
3689
3852
  target_path: "AGENTS.md",
3690
3853
  block_id: AGENTS_CORE_BLOCK_ID,
3691
- content_sha256: createHash7("sha256").update(AGENTS_MANAGED_BLOCK_CONTENT).digest("hex")
3854
+ content_sha256: createHash8("sha256").update(AGENTS_MANAGED_BLOCK_CONTENT).digest("hex")
3692
3855
  },
3693
3856
  ...selectedSet.has("claude-code") ? [{
3694
3857
  owner: "claude-code",
3695
3858
  target_path: "CLAUDE.md",
3696
3859
  block_id: CLAUDE_BLOCK_ID,
3697
- content_sha256: createHash7("sha256").update(CLAUDE_MANAGED_BLOCK_CONTENT).digest("hex")
3860
+ content_sha256: createHash8("sha256").update(CLAUDE_MANAGED_BLOCK_CONTENT).digest("hex")
3698
3861
  }] : [],
3699
3862
  ...selectedSet.has("codebuddy") ? [{
3700
3863
  owner: "codebuddy",
3701
3864
  target_path: "CODEBUDDY.md",
3702
3865
  block_id: CODEBUDDY_BLOCK_ID,
3703
- content_sha256: createHash7("sha256").update(CODEBUDDY_MANAGED_BLOCK_CONTENT).digest("hex")
3866
+ content_sha256: createHash8("sha256").update(CODEBUDDY_MANAGED_BLOCK_CONTENT).digest("hex")
3704
3867
  }] : []
3705
3868
  ].sort((left, right) => left.target_path.localeCompare(right.target_path) || left.block_id.localeCompare(right.block_id));
3706
3869
  const filesByTarget = new Map(newStateFiles.map((entry) => [entry.target_path, entry]));
@@ -3716,7 +3879,7 @@ async function refreshProject(options) {
3716
3879
  files: [...filesByTarget.values()].sort((left, right) => left.target_path.localeCompare(right.target_path) || left.source_path.localeCompare(right.source_path)),
3717
3880
  managed_blocks: managedBlocks
3718
3881
  };
3719
- const existingState = await readOptionalText(join8(root, INSTALLED_STATE_PATH));
3882
+ const existingState = await readOptionalText(join9(root, INSTALLED_STATE_PATH));
3720
3883
  let existingParsed = null;
3721
3884
  try {
3722
3885
  existingParsed = existingState === "" ? null : JSON.parse(existingState);
@@ -3771,7 +3934,7 @@ function buildMarkerCoreHash(text) {
3771
3934
  }
3772
3935
  }
3773
3936
  async function collectFreshness(options) {
3774
- const root = resolve7(options.projectRoot);
3937
+ const root = resolve8(options.projectRoot);
3775
3938
  const installed = await readInstalledState(root);
3776
3939
  const codebuddySurface2 = options.codebuddySurface ?? "both";
3777
3940
  const agents = [];
@@ -3816,18 +3979,18 @@ async function collectFreshness(options) {
3816
3979
  identity.adapterHash = sha256Bytes(canonicalJson(targets.map((target) => ({ path: target.target_path.replace(/\\/g, "/"), sha256: target.sha256 })).sort((a, b) => a.path.localeCompare(b.path))));
3817
3980
  const installedProjection = await Promise.all(targets.map(async (target) => ({
3818
3981
  path: target.target_path.replace(/\\/g, "/"),
3819
- sha256: await fileHex2(join8(root, target.target_path))
3982
+ sha256: await fileHex2(join9(root, target.target_path))
3820
3983
  })));
3821
3984
  identity.installedAdapterHash = sha256Bytes(canonicalJson(installedProjection.sort((a, b) => a.path.localeCompare(b.path))));
3822
3985
  const markerTarget = targets.find((target) => target.target_path.replace(/\\/g, "/").endsWith(`/${BUILD_MARKER_BUNDLE_PATH}`) || target.target_path === BUILD_MARKER_BUNDLE_PATH);
3823
3986
  if (markerTarget !== void 0) {
3824
- identity.installedCoreHash = buildMarkerCoreHash(await readOptionalText(join8(root, markerTarget.target_path)));
3987
+ identity.installedCoreHash = buildMarkerCoreHash(await readOptionalText(join9(root, markerTarget.target_path)));
3825
3988
  }
3826
3989
  const mismatchDetails = [];
3827
3990
  const contentEntries = [];
3828
3991
  for (const target of targets) {
3829
3992
  const rel = target.target_path.replace(/\\/g, "/");
3830
- const actual = await fileHex2(join8(root, target.target_path));
3993
+ const actual = await fileHex2(join9(root, target.target_path));
3831
3994
  contentEntries.push({ relpath: rel, sha256: actual ?? "" });
3832
3995
  if (actual === null) {
3833
3996
  mismatchDetails.push({ relpath: rel, expected: target.sha256, actual: "<missing>" });
@@ -3850,7 +4013,7 @@ async function collectFreshness(options) {
3850
4013
  const drifted = [];
3851
4014
  const missing = [];
3852
4015
  for (const target of targets) {
3853
- const current = await fileHex2(join8(root, target.target_path));
4016
+ const current = await fileHex2(join9(root, target.target_path));
3854
4017
  if (current === null) {
3855
4018
  missing.push(target.target_path);
3856
4019
  } else if (current !== target.sha256) {
@@ -4098,11 +4261,11 @@ function rawFindings(content) {
4098
4261
  if (rule.id === "HH_PASSWORD_VALUE" && isObviousPlaceholder(value)) {
4099
4262
  continue;
4100
4263
  }
4101
- const relative4 = match[0].indexOf(value);
4264
+ const relative5 = match[0].indexOf(value);
4102
4265
  findings.push({
4103
4266
  ruleId: rule.id,
4104
4267
  severity: rule.severity,
4105
- offset: match.index + Math.max(0, relative4),
4268
+ offset: match.index + Math.max(0, relative5),
4106
4269
  value
4107
4270
  });
4108
4271
  }
@@ -4214,8 +4377,8 @@ function generateProposalPreview(input, scanOptions = {}) {
4214
4377
  }
4215
4378
 
4216
4379
  // ../core/dist/push/credentials.js
4217
- import { readFile as readFile8, writeFile as writeFile6 } from "node:fs/promises";
4218
- import { join as join9 } from "node:path";
4380
+ import { readFile as readFile9, writeFile as writeFile7 } from "node:fs/promises";
4381
+ import { join as join10 } from "node:path";
4219
4382
  import { parse as parseYaml4, stringify as stringifyYaml3 } from "yaml";
4220
4383
  var CREDENTIALS_LOCAL_RELATIVE = ".harness/credentials.local.yaml";
4221
4384
  var CREDENTIALS_GITIGNORE_LINES = [
@@ -4285,7 +4448,7 @@ function mergeLocalCredentials(existing, patch) {
4285
4448
  }
4286
4449
  async function readLocalCredentials(projectRoot) {
4287
4450
  try {
4288
- const raw = await readFile8(join9(projectRoot, CREDENTIALS_LOCAL_RELATIVE), "utf8");
4451
+ const raw = await readFile9(join10(projectRoot, CREDENTIALS_LOCAL_RELATIVE), "utf8");
4289
4452
  return parseLocalCredentials(parseYaml4(raw));
4290
4453
  } catch (error) {
4291
4454
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
@@ -4296,13 +4459,13 @@ async function readLocalCredentials(projectRoot) {
4296
4459
  }
4297
4460
  async function writeLocalCredentials(projectRoot, credentials) {
4298
4461
  const normalized = validateLocalCredentials(credentials);
4299
- await writeFile6(join9(projectRoot, CREDENTIALS_LOCAL_RELATIVE), stringifyYaml3(normalized, { sortMapEntries: true }) + "\n", "utf8");
4462
+ await writeFile7(join10(projectRoot, CREDENTIALS_LOCAL_RELATIVE), stringifyYaml3(normalized, { sortMapEntries: true }) + "\n", "utf8");
4300
4463
  }
4301
4464
  async function ensureCredentialsGitignore(projectRoot) {
4302
- const gitignorePath = join9(projectRoot, ".gitignore");
4465
+ const gitignorePath = join10(projectRoot, ".gitignore");
4303
4466
  let content = "";
4304
4467
  try {
4305
- content = await readFile8(gitignorePath, "utf8");
4468
+ content = await readFile9(gitignorePath, "utf8");
4306
4469
  } catch (error) {
4307
4470
  if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) {
4308
4471
  throw error;
@@ -4325,7 +4488,7 @@ async function ensureCredentialsGitignore(projectRoot) {
4325
4488
  return;
4326
4489
  }
4327
4490
  const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
4328
- await writeFile6(gitignorePath, content + suffix + missing.join("\n") + "\n", "utf8");
4491
+ await writeFile7(gitignorePath, content + suffix + missing.join("\n") + "\n", "utf8");
4329
4492
  }
4330
4493
  function resolvePushAuth(input) {
4331
4494
  const tokenEnv = input.tokenEnv ?? input.projectTokenEnv;
@@ -4353,28 +4516,28 @@ function resolvePushAuth(input) {
4353
4516
  }
4354
4517
 
4355
4518
  // ../core/dist/push/push.js
4356
- import { lstat as lstat3, readFile as readFile12, readdir as readdir6, rm as rm5 } from "node:fs/promises";
4357
- import { join as join13, relative as relative2, resolve as resolve9 } from "node:path";
4519
+ import { lstat as lstat3, readFile as readFile13, readdir as readdir6, rm as rm5 } from "node:fs/promises";
4520
+ import { join as join14, relative as relative3, resolve as resolve10 } from "node:path";
4358
4521
  import { parse as parseYaml5, stringify as stringifyYaml4 } from "yaml";
4359
4522
 
4360
4523
  // ../core/dist/state/baseline.js
4361
- import { readFile as readFile9 } from "node:fs/promises";
4362
- import { join as join10 } from "node:path";
4524
+ import { readFile as readFile10 } from "node:fs/promises";
4525
+ import { join as join11 } from "node:path";
4363
4526
  async function readBaseline(projectRoot) {
4364
- const content = await readFile9(join10(stateLayout(projectRoot).baseline, "manifest.json"), "utf8");
4527
+ const content = await readFile10(join11(stateLayout(projectRoot).baseline, "manifest.json"), "utf8");
4365
4528
  return baselineManifestSchema.parse(JSON.parse(content));
4366
4529
  }
4367
4530
 
4368
4531
  // ../core/dist/state/locks.js
4369
4532
  import { randomUUID as randomUUID3 } from "node:crypto";
4370
- import { readFile as readFile10, rename as rename5, rm as rm3, writeFile as writeFile7 } from "node:fs/promises";
4371
- import { join as join11 } from "node:path";
4533
+ import { readFile as readFile11, rename as rename6, rm as rm3, writeFile as writeFile8 } from "node:fs/promises";
4534
+ import { join as join12 } from "node:path";
4372
4535
  async function readLock(path) {
4373
- return JSON.parse(await readFile10(path, "utf8"));
4536
+ return JSON.parse(await readFile11(path, "utf8"));
4374
4537
  }
4375
4538
  async function acquireProtocolLock(projectRoot, operation, options = {}) {
4376
4539
  const layout = await ensureStateLayout(projectRoot);
4377
- const lockPath = join11(layout.locks, "protocol.lock");
4540
+ const lockPath = join12(layout.locks, "protocol.lock");
4378
4541
  const now = options.now ?? Date.now();
4379
4542
  const staleAfterMs = options.staleAfterMs ?? 15 * 60 * 1e3;
4380
4543
  const record = {
@@ -4387,7 +4550,7 @@ async function acquireProtocolLock(projectRoot, operation, options = {}) {
4387
4550
  };
4388
4551
  for (let attempt = 0; attempt < 2; attempt += 1) {
4389
4552
  try {
4390
- await writeFile7(lockPath, JSON.stringify(record, null, 2), { flag: "wx" });
4553
+ await writeFile8(lockPath, JSON.stringify(record, null, 2), { flag: "wx" });
4391
4554
  return {
4392
4555
  path: lockPath,
4393
4556
  operation,
@@ -4409,15 +4572,15 @@ async function acquireProtocolLock(projectRoot, operation, options = {}) {
4409
4572
  if (now - current.heartbeat_at_ms <= staleAfterMs) {
4410
4573
  throw new Error("protocol lock is active for operation " + current.operation, { cause: error });
4411
4574
  }
4412
- await rename5(lockPath, lockPath + ".stale-" + randomUUID3());
4575
+ await rename6(lockPath, lockPath + ".stale-" + randomUUID3());
4413
4576
  }
4414
4577
  }
4415
4578
  throw new Error("unable to acquire protocol lock");
4416
4579
  }
4417
4580
 
4418
4581
  // ../core/dist/sync/synchronize.js
4419
- import { lstat as lstat2, readFile as readFile11, rm as rm4 } from "node:fs/promises";
4420
- import { join as join12, resolve as resolve8 } from "node:path";
4582
+ import { lstat as lstat2, readFile as readFile12, rm as rm4 } from "node:fs/promises";
4583
+ import { join as join13, resolve as resolve9 } from "node:path";
4421
4584
 
4422
4585
  // ../core/dist/update/conflicts.js
4423
4586
  function operationTargetPath(operation) {
@@ -4700,8 +4863,8 @@ async function pathExists(path) {
4700
4863
  }
4701
4864
  }
4702
4865
  async function optionalContent(root, path) {
4703
- const full = join12(root, path);
4704
- return await pathExists(full) ? readFile11(full, "utf8") : null;
4866
+ const full = join13(root, path);
4867
+ return await pathExists(full) ? readFile12(full, "utf8") : null;
4705
4868
  }
4706
4869
  function manifestPayloadHash(manifest) {
4707
4870
  const payload = { ...manifest };
@@ -4713,10 +4876,10 @@ async function loadBlob(root, client, artifactId, operation, requestId, dryRun)
4713
4876
  return null;
4714
4877
  }
4715
4878
  const hash = operation.content_sha256;
4716
- const cacheRoot = join12(root, ".harness", "cache", "server-artifacts", artifactId);
4717
- const cachePath = join12(cacheRoot, "blobs", hash.replace(":", "_"));
4879
+ const cacheRoot = join13(root, ".harness", "cache", "server-artifacts", artifactId);
4880
+ const cachePath = join13(cacheRoot, "blobs", hash.replace(":", "_"));
4718
4881
  if (await pathExists(cachePath) && await sha256File(cachePath) === hash) {
4719
- return readFile11(cachePath, "utf8");
4882
+ return readFile12(cachePath, "utf8");
4720
4883
  }
4721
4884
  const bytes = await client.downloadArtifactBlob(artifactId, hash, requestId);
4722
4885
  if (bytes.byteLength !== operation.size_bytes || sha256Bytes(bytes) !== hash) {
@@ -4822,7 +4985,7 @@ async function planSingleArtifact(root, baseline, manifest, client, requestId, d
4822
4985
  });
4823
4986
  }
4824
4987
  async function saveConflictReport(root, requestId, manifest, plan) {
4825
- const reportPath = join12(root, ".harness", "reports", "conflicts-" + requestId + ".json");
4988
+ const reportPath = join13(root, ".harness", "reports", "conflicts-" + requestId + ".json");
4826
4989
  await atomicWriteJson(reportPath, {
4827
4990
  schema_version: 1,
4828
4991
  request_id: requestId,
@@ -4833,7 +4996,7 @@ async function saveConflictReport(root, requestId, manifest, plan) {
4833
4996
  });
4834
4997
  }
4835
4998
  async function synchronizeArtifacts(options, initialBaseline) {
4836
- const root = resolve8(options.projectRoot);
4999
+ const root = resolve9(options.projectRoot);
4837
5000
  const projectId = options.project.project.project_id;
4838
5001
  if (projectId === null) {
4839
5002
  throw new Error("PROJECT_NOT_BOUND");
@@ -4940,7 +5103,7 @@ async function synchronizeArtifacts(options, initialBaseline) {
4940
5103
  }
4941
5104
  continue;
4942
5105
  }
4943
- await atomicWriteJson(join12(root, ".harness", "cache", "server-artifacts", manifest.artifact_id, "manifest.json"), manifest);
5106
+ await atomicWriteJson(join13(root, ".harness", "cache", "server-artifacts", manifest.artifact_id, "manifest.json"), manifest);
4944
5107
  const lock = await acquireProtocolLock(root, "update", { requestId: options.requestId });
4945
5108
  try {
4946
5109
  const nextBaseline = applyBaselineUpdates(baseline, plan);
@@ -5010,7 +5173,7 @@ function baselineEntryFromOperation(manifest, operation, localContent) {
5010
5173
  };
5011
5174
  }
5012
5175
  async function advanceBaselineFromArtifact(options, baseline) {
5013
- const root = resolve8(options.projectRoot);
5176
+ const root = resolve9(options.projectRoot);
5014
5177
  if (options.manifest.project_version === null) {
5015
5178
  throw new Error("artifact manifest missing project_version");
5016
5179
  }
@@ -5112,19 +5275,19 @@ async function walkFiles(root, current, output) {
5112
5275
  return;
5113
5276
  }
5114
5277
  for (const item2 of await readdir6(current, { withFileTypes: true })) {
5115
- const path = join13(current, item2.name);
5278
+ const path = join14(current, item2.name);
5116
5279
  if (item2.isSymbolicLink()) {
5117
5280
  throw new PushWorkflowError("symlink is not pushable", 6, "UNSAFE_SYMLINK");
5118
5281
  }
5119
5282
  if (item2.isDirectory()) {
5120
5283
  await walkFiles(root, path, output);
5121
5284
  } else if (item2.isFile()) {
5122
- output.push(normalizeManagedPath(relative2(root, path).replaceAll("\\", "/")));
5285
+ output.push(normalizeManagedPath(relative3(root, path).replaceAll("\\", "/")));
5123
5286
  }
5124
5287
  }
5125
5288
  }
5126
5289
  async function walkArchiveSummaries(root, output) {
5127
- const archiveRoot = join13(root, ".harness", "archive");
5290
+ const archiveRoot = join14(root, ".harness", "archive");
5128
5291
  if (!await exists3(archiveRoot))
5129
5292
  return;
5130
5293
  for (const item2 of await readdir6(archiveRoot, { withFileTypes: true })) {
@@ -5133,7 +5296,7 @@ async function walkArchiveSummaries(root, output) {
5133
5296
  }
5134
5297
  if (!item2.isDirectory())
5135
5298
  continue;
5136
- const summaryPath = join13(archiveRoot, item2.name, "reports", "final", "summary-data.json");
5299
+ const summaryPath = join14(archiveRoot, item2.name, "reports", "final", "summary-data.json");
5137
5300
  try {
5138
5301
  const stats = await lstat3(summaryPath);
5139
5302
  if (stats.isSymbolicLink()) {
@@ -5146,7 +5309,7 @@ async function walkArchiveSummaries(root, output) {
5146
5309
  continue;
5147
5310
  throw error;
5148
5311
  }
5149
- const relativePath = normalizeManagedPath(relative2(root, summaryPath).replaceAll("\\", "/"));
5312
+ const relativePath = normalizeManagedPath(relative3(root, summaryPath).replaceAll("\\", "/"));
5150
5313
  if (ARCHIVE_SUMMARY_PATH.test(relativePath)) {
5151
5314
  output.push(relativePath);
5152
5315
  }
@@ -5163,17 +5326,17 @@ async function walkHarnessEntries(root, directory, output) {
5163
5326
  return;
5164
5327
  for (const item2 of await readdir6(directory, { withFileTypes: true })) {
5165
5328
  if (item2.name.startsWith("harness-")) {
5166
- const path = join13(directory, item2.name);
5329
+ const path = join14(directory, item2.name);
5167
5330
  if (item2.isDirectory()) {
5168
5331
  await walkFiles(root, path, output);
5169
5332
  } else if (item2.isFile()) {
5170
- output.push(normalizeManagedPath(relative2(root, path).replaceAll("\\", "/")));
5333
+ output.push(normalizeManagedPath(relative3(root, path).replaceAll("\\", "/")));
5171
5334
  }
5172
5335
  }
5173
5336
  }
5174
5337
  }
5175
5338
  async function managedFiles(projectRoot, project) {
5176
- const root = resolve9(projectRoot);
5339
+ const root = resolve10(projectRoot);
5177
5340
  const paths = [];
5178
5341
  const adapters = getAdapters(enabledHarnessAgents(project));
5179
5342
  const managedFiles2 = [
@@ -5182,26 +5345,26 @@ async function managedFiles(projectRoot, project) {
5182
5345
  ...adapters.some((adapter) => adapter.name === "codebuddy") ? ["CODEBUDDY.md"] : []
5183
5346
  ];
5184
5347
  for (const path of managedFiles2) {
5185
- if (await exists3(join13(root, path))) {
5348
+ if (await exists3(join14(root, path))) {
5186
5349
  paths.push(path);
5187
5350
  }
5188
5351
  }
5189
5352
  for (const path of SHARED_MANAGED_ROOTS) {
5190
- await walkFiles(root, join13(root, path), paths);
5353
+ await walkFiles(root, join14(root, path), paths);
5191
5354
  }
5192
5355
  await walkArchiveSummaries(root, paths);
5193
5356
  for (const adapter of adapters) {
5194
5357
  if (adapter.rulesRoot !== null) {
5195
- await walkHarnessEntries(root, join13(root, adapter.rulesRoot), paths);
5358
+ await walkHarnessEntries(root, join14(root, adapter.rulesRoot), paths);
5196
5359
  }
5197
- await walkHarnessEntries(root, join13(root, adapter.skillsRoot), paths);
5360
+ await walkHarnessEntries(root, join14(root, adapter.skillsRoot), paths);
5198
5361
  if (adapter.agentsRoot !== null) {
5199
- await walkHarnessEntries(root, join13(root, adapter.agentsRoot), paths);
5362
+ await walkHarnessEntries(root, join14(root, adapter.agentsRoot), paths);
5200
5363
  }
5201
5364
  }
5202
5365
  const result = {};
5203
5366
  for (const path of [...new Set(paths)].sort()) {
5204
- result[path] = await readFile12(join13(root, path), "utf8");
5367
+ result[path] = await readFile13(join14(root, path), "utf8");
5205
5368
  }
5206
5369
  return result;
5207
5370
  }
@@ -5210,14 +5373,14 @@ function proposalBaseline(baseline) {
5210
5373
  }
5211
5374
  async function readProject(root) {
5212
5375
  try {
5213
- return projectConfigSchema.parse(parseYaml5(await readFile12(join13(root, ".harness", "project.yaml"), "utf8")));
5376
+ return projectConfigSchema.parse(parseYaml5(await readFile13(join14(root, ".harness", "project.yaml"), "utf8")));
5214
5377
  } catch {
5215
5378
  throw new PushWorkflowError("project configuration is missing or invalid", 3, "PROJECT_CONFIG_INVALID");
5216
5379
  }
5217
5380
  }
5218
5381
  async function readOptionalJson(path) {
5219
5382
  try {
5220
- return JSON.parse(await readFile12(path, "utf8"));
5383
+ return JSON.parse(await readFile13(path, "utf8"));
5221
5384
  } catch (error) {
5222
5385
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
5223
5386
  return null;
@@ -5229,7 +5392,7 @@ async function clientIdFor(root, explicit) {
5229
5392
  if (explicit !== void 0) {
5230
5393
  return explicit;
5231
5394
  }
5232
- const path = join13(root, ".harness", "state", "local", "client.json");
5395
+ const path = join14(root, ".harness", "state", "local", "client.json");
5233
5396
  const existing = await readOptionalJson(path);
5234
5397
  if (typeof existing?.client_id === "string" && /^cli_[A-Za-z0-9_-]+$/.test(existing.client_id)) {
5235
5398
  return existing.client_id;
@@ -5425,7 +5588,7 @@ async function bindProject(root, project, baseline, projectId) {
5425
5588
  return { project: nextProject, baseline: nextBaseline };
5426
5589
  }
5427
5590
  async function pushProject(options) {
5428
- const root = resolve9(options.projectRoot);
5591
+ const root = resolve10(options.projectRoot);
5429
5592
  let project = await readProject(root);
5430
5593
  let baseline = await readBaseline(root);
5431
5594
  const profile = parseHarnessProfile(project.project.profiles[0]);
@@ -5491,7 +5654,7 @@ async function pushProject(options) {
5491
5654
  if (options.confirmProposal !== void 0 && !await options.confirmProposal(preview)) {
5492
5655
  return { preview, proposalId: null, projectId: project.project.project_id, cancelled: true };
5493
5656
  }
5494
- const workflowPath = join13(root, ".harness", "state", "local", "push-workflow.json");
5657
+ const workflowPath = join14(root, ".harness", "state", "local", "push-workflow.json");
5495
5658
  const priorWorkflow = await readOptionalJson(workflowPath);
5496
5659
  const provisionalRequestId = priorWorkflow?.local_project_key === project.project.local_project_key ? priorWorkflow.request_id : uuidV7();
5497
5660
  const lock = await acquireProtocolLock(root, "push", { requestId: provisionalRequestId });
@@ -5646,7 +5809,7 @@ async function pushProject(options) {
5646
5809
  pushWarning = "BASELINE_ADVANCE_DEFERRED";
5647
5810
  }
5648
5811
  }
5649
- await atomicWriteJson(join13(root, ".harness", "state", "local", "push-results", finalized.proposal_id + ".json"), {
5812
+ await atomicWriteJson(join14(root, ".harness", "state", "local", "push-results", finalized.proposal_id + ".json"), {
5650
5813
  schema_version: 1,
5651
5814
  request_id: requestId,
5652
5815
  project_id: projectId,
@@ -5691,8 +5854,8 @@ async function pushProject(options) {
5691
5854
  }
5692
5855
 
5693
5856
  // ../core/dist/state/cleanup.js
5694
- import { readFile as readFile13, readdir as readdir7, rm as rm6 } from "node:fs/promises";
5695
- import { join as join14 } from "node:path";
5857
+ import { readFile as readFile14, readdir as readdir7, rm as rm6 } from "node:fs/promises";
5858
+ import { join as join15 } from "node:path";
5696
5859
  function isSafeEntryName(name) {
5697
5860
  return name.length > 0 && !name.includes("/") && !name.includes("\\") && name !== "." && name !== ".." && !name.includes("\0");
5698
5861
  }
@@ -5716,7 +5879,7 @@ async function cleanupProject(options) {
5716
5879
  continue;
5717
5880
  let journal;
5718
5881
  try {
5719
- journal = JSON.parse(await readFile13(join14(layout.transactions, name, "journal.json"), "utf8"));
5882
+ journal = JSON.parse(await readFile14(join15(layout.transactions, name, "journal.json"), "utf8"));
5720
5883
  } catch {
5721
5884
  continue;
5722
5885
  }
@@ -5734,7 +5897,7 @@ async function cleanupProject(options) {
5734
5897
  continue;
5735
5898
  pruned.push(entry.id);
5736
5899
  if (!options.dryRun) {
5737
- await rm6(join14(layout.transactions, entry.id), { recursive: true, force: true });
5900
+ await rm6(join15(layout.transactions, entry.id), { recursive: true, force: true });
5738
5901
  }
5739
5902
  }
5740
5903
  }
@@ -5743,7 +5906,7 @@ async function cleanupProject(options) {
5743
5906
  continue;
5744
5907
  removedCache.push(name);
5745
5908
  if (!options.dryRun) {
5746
- await rm6(join14(layout.serverArtifacts, name), { recursive: true, force: true });
5909
+ await rm6(join15(layout.serverArtifacts, name), { recursive: true, force: true });
5747
5910
  }
5748
5911
  }
5749
5912
  return {
@@ -5803,10 +5966,10 @@ var SKILL_TARGET_AGENTS = Object.freeze([
5803
5966
  ]);
5804
5967
 
5805
5968
  // ../core/dist/transaction/recovery.js
5806
- import { readFile as readFile14, readdir as readdir8, rm as rm7, stat as stat4 } from "node:fs/promises";
5807
- import { join as join15 } from "node:path";
5969
+ import { readFile as readFile15, readdir as readdir8, rm as rm7, stat as stat4 } from "node:fs/promises";
5970
+ import { join as join16 } from "node:path";
5808
5971
  async function recoverTransaction(projectRoot, transactionId) {
5809
- const journal = JSON.parse(await readFile14(join15(stateLayout(projectRoot).transactions, transactionId, "journal.json"), "utf8"));
5972
+ const journal = JSON.parse(await readFile15(join16(stateLayout(projectRoot).transactions, transactionId, "journal.json"), "utf8"));
5810
5973
  if (journal.state === "committed") {
5811
5974
  return { transactionId, status: "committed" };
5812
5975
  }
@@ -5833,7 +5996,7 @@ async function listTransactions(projectRoot) {
5833
5996
  const transactions = [];
5834
5997
  for (const transactionId of names) {
5835
5998
  try {
5836
- const journal = JSON.parse(await readFile14(join15(root, transactionId, "journal.json"), "utf8"));
5999
+ const journal = JSON.parse(await readFile15(join16(root, transactionId, "journal.json"), "utf8"));
5837
6000
  transactions.push({
5838
6001
  transactionId,
5839
6002
  kind: journal.kind,
@@ -5864,11 +6027,11 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
5864
6027
  if (latest === void 0) {
5865
6028
  throw new Error("no committed update transaction is available for rollback");
5866
6029
  }
5867
- const transactionRoot = join15(stateLayout(projectRoot).transactions, latest.transactionId);
5868
- const journal = JSON.parse(await readFile14(join15(transactionRoot, "journal.json"), "utf8"));
5869
- const after = JSON.parse(await readFile14(join15(transactionRoot, "after", "manifest.json"), "utf8"));
6030
+ const transactionRoot = join16(stateLayout(projectRoot).transactions, latest.transactionId);
6031
+ const journal = JSON.parse(await readFile15(join16(transactionRoot, "journal.json"), "utf8"));
6032
+ const after = JSON.parse(await readFile15(join16(transactionRoot, "after", "manifest.json"), "utf8"));
5870
6033
  for (const entry of after) {
5871
- const target = join15(projectRoot, entry.path);
6034
+ const target = join16(projectRoot, entry.path);
5872
6035
  const exists6 = await pathExists2(target);
5873
6036
  if (exists6 !== entry.exists || exists6 && await sha256File(target) !== entry.hash) {
5874
6037
  throw new Error("cannot rollback dirty path: " + entry.path);
@@ -5881,10 +6044,10 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
5881
6044
  continue;
5882
6045
  }
5883
6046
  seen.add(snapshot.path);
5884
- const target = join15(projectRoot, snapshot.path);
6047
+ const target = join16(projectRoot, snapshot.path);
5885
6048
  const exists6 = await pathExists2(target);
5886
6049
  if (snapshot.existed && snapshot.snapshot_name !== null) {
5887
- const content = await readFile14(join15(transactionRoot, "before", snapshot.snapshot_name));
6050
+ const content = await readFile15(join16(transactionRoot, "before", snapshot.snapshot_name));
5888
6051
  operations.push({
5889
6052
  operation: exists6 ? "modify" : "add",
5890
6053
  path: snapshot.path,
@@ -5913,7 +6076,7 @@ async function cleanupOldTransactions(projectRoot, now = /* @__PURE__ */ new Dat
5913
6076
  if (!removable) {
5914
6077
  continue;
5915
6078
  }
5916
- await rm7(join15(stateLayout(projectRoot).transactions, item2.transactionId), {
6079
+ await rm7(join16(stateLayout(projectRoot).transactions, item2.transactionId), {
5917
6080
  recursive: true,
5918
6081
  force: true
5919
6082
  });
@@ -5923,8 +6086,8 @@ async function cleanupOldTransactions(projectRoot, now = /* @__PURE__ */ new Dat
5923
6086
  }
5924
6087
 
5925
6088
  // ../core/dist/update/update.js
5926
- import { readFile as readFile15 } from "node:fs/promises";
5927
- import { join as join16, resolve as resolve10 } from "node:path";
6089
+ import { readFile as readFile16 } from "node:fs/promises";
6090
+ import { join as join17, resolve as resolve11 } from "node:path";
5928
6091
  import { parse as parseYaml8 } from "yaml";
5929
6092
  var UpdateWorkflowError = class extends Error {
5930
6093
  exitCode;
@@ -5937,10 +6100,10 @@ var UpdateWorkflowError = class extends Error {
5937
6100
  }
5938
6101
  };
5939
6102
  async function updateProject(options) {
5940
- const root = resolve10(options.projectRoot);
6103
+ const root = resolve11(options.projectRoot);
5941
6104
  let project;
5942
6105
  try {
5943
- project = projectConfigSchema.parse(parseYaml8(await readFile15(join16(root, ".harness", "project.yaml"), "utf8")));
6106
+ project = projectConfigSchema.parse(parseYaml8(await readFile16(join17(root, ".harness", "project.yaml"), "utf8")));
5944
6107
  } catch {
5945
6108
  throw new UpdateWorkflowError("project configuration is invalid", 3, "PROJECT_CONFIG_INVALID");
5946
6109
  }
@@ -6030,8 +6193,8 @@ async function updateProject(options) {
6030
6193
  }
6031
6194
 
6032
6195
  // src/config/init-config.ts
6033
- import { isAbsolute as isAbsolute2, join as join17 } from "node:path";
6034
- import { readFile as readFile16 } from "node:fs/promises";
6196
+ import { isAbsolute as isAbsolute2, join as join18 } from "node:path";
6197
+ import { readFile as readFile17 } from "node:fs/promises";
6035
6198
  var InitConfigurationError = class extends Error {
6036
6199
  exitCode;
6037
6200
  code;
@@ -6114,9 +6277,9 @@ function hasOwn(record, key) {
6114
6277
  async function resolveInitConfig(cwd, flags, prompts = {}, warnings = []) {
6115
6278
  let fileConfig = {};
6116
6279
  if (flags.config !== void 0) {
6117
- const path = isAbsolute2(flags.config) ? flags.config : join17(cwd, flags.config);
6280
+ const path = isAbsolute2(flags.config) ? flags.config : join18(cwd, flags.config);
6118
6281
  try {
6119
- fileConfig = JSON.parse(await readFile16(path, "utf8"));
6282
+ fileConfig = JSON.parse(await readFile17(path, "utf8"));
6120
6283
  } catch (error) {
6121
6284
  throw new InitConfigurationError(
6122
6285
  "unable to read init config: " + (error instanceof Error ? error.message : String(error)),
@@ -6198,8 +6361,8 @@ function serializeCliResult(result) {
6198
6361
  }
6199
6362
 
6200
6363
  // src/config/codebuddy-setup.ts
6201
- import { mkdir as mkdir6, readFile as readFile17, readdir as readdir9, stat as stat5, writeFile as writeFile8 } from "node:fs/promises";
6202
- import { basename as basename4, dirname as dirname6, extname as extname2, join as join18 } from "node:path";
6364
+ import { mkdir as mkdir7, readFile as readFile18, readdir as readdir9, stat as stat5, writeFile as writeFile9 } from "node:fs/promises";
6365
+ import { basename as basename5, dirname as dirname7, extname as extname2, join as join19 } from "node:path";
6203
6366
  var MANAGED_RULE_NAMES = /* @__PURE__ */ new Set([
6204
6367
  "harness-general.md",
6205
6368
  "harness-general.mdc",
@@ -6218,7 +6381,7 @@ async function exists4(path) {
6218
6381
  }
6219
6382
  async function readJsonObject(path) {
6220
6383
  try {
6221
- const parsed = JSON.parse(await readFile17(path, "utf8"));
6384
+ const parsed = JSON.parse(await readFile18(path, "utf8"));
6222
6385
  return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
6223
6386
  } catch (error) {
6224
6387
  if (error instanceof Error && "code" in error && error.code === "ENOENT") return {};
@@ -6226,7 +6389,7 @@ async function readJsonObject(path) {
6226
6389
  }
6227
6390
  }
6228
6391
  async function inspectCodeBuddySetup(projectRoot, surface = "both") {
6229
- const rulesRoot = join18(projectRoot, ".claude", "rules");
6392
+ const rulesRoot = join19(projectRoot, ".claude", "rules");
6230
6393
  let ruleNames = [];
6231
6394
  try {
6232
6395
  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();
@@ -6237,11 +6400,11 @@ async function inspectCodeBuddySetup(projectRoot, surface = "both") {
6237
6400
  const currentClaudeRules = [];
6238
6401
  const conflictingClaudeRules = [];
6239
6402
  for (const name of ruleNames) {
6240
- const sourceContent = await readFile17(join18(rulesRoot, name), "utf8");
6403
+ const sourceContent = await readFile18(join19(rulesRoot, name), "utf8");
6241
6404
  const targetContents = await Promise.all(
6242
6405
  destinationTargets(projectRoot, surface, name).map(async (target) => {
6243
6406
  try {
6244
- return await readFile17(target, "utf8");
6407
+ return await readFile18(target, "utf8");
6245
6408
  } catch (error) {
6246
6409
  if (error instanceof Error && "code" in error && error.code === "ENOENT") return null;
6247
6410
  throw error;
@@ -6255,22 +6418,22 @@ async function inspectCodeBuddySetup(projectRoot, surface = "both") {
6255
6418
  currentClaudeRules.push(name);
6256
6419
  }
6257
6420
  }
6258
- const mcp = await readJsonObject(join18(projectRoot, ".mcp.json"));
6421
+ const mcp = await readJsonObject(join19(projectRoot, ".mcp.json"));
6259
6422
  const servers = mcp?.mcpServers;
6260
6423
  const configured = servers !== null && typeof servers === "object" && !Array.isArray(servers) && Object.prototype.hasOwnProperty.call(servers, "codegraph");
6261
6424
  return {
6262
6425
  claudeRules,
6263
6426
  currentClaudeRules,
6264
6427
  conflictingClaudeRules,
6265
- hasCodeGraphIndex: await exists4(join18(projectRoot, ".codegraph")),
6428
+ hasCodeGraphIndex: await exists4(join19(projectRoot, ".codegraph")),
6266
6429
  codeGraphConfigured: configured
6267
6430
  };
6268
6431
  }
6269
6432
  function destinationTargets(root, surface, name) {
6270
- const stem = basename4(name, extname2(name));
6433
+ const stem = basename5(name, extname2(name));
6271
6434
  const targets = [];
6272
- if (surface !== "cli") targets.push(join18(root, ".codebuddy", ".rules", `${stem}.mdc`));
6273
- if (surface !== "ide") targets.push(join18(root, ".codebuddy", "rules", `${stem}.md`));
6435
+ if (surface !== "cli") targets.push(join19(root, ".codebuddy", ".rules", `${stem}.mdc`));
6436
+ if (surface !== "ide") targets.push(join19(root, ".codebuddy", "rules", `${stem}.md`));
6274
6437
  return targets;
6275
6438
  }
6276
6439
  async function applyCodeBuddySetup(options) {
@@ -6284,8 +6447,8 @@ async function applyCodeBuddySetup(options) {
6284
6447
  if (options.syncClaudeRules) {
6285
6448
  const plan = await inspectCodeBuddySetup(options.projectRoot, options.surface);
6286
6449
  for (const name of plan.claudeRules) {
6287
- const source = join18(options.projectRoot, ".claude", "rules", name);
6288
- const content = await readFile17(source, "utf8");
6450
+ const source = join19(options.projectRoot, ".claude", "rules", name);
6451
+ const content = await readFile18(source, "utf8");
6289
6452
  if (SENSITIVE_ASSIGNMENT.test(content) || PRIVATE_KEY_BLOCK.test(content)) {
6290
6453
  result.skippedSensitive.push(name);
6291
6454
  continue;
@@ -6295,14 +6458,14 @@ async function applyCodeBuddySetup(options) {
6295
6458
  result.preserved.push(target);
6296
6459
  continue;
6297
6460
  }
6298
- await mkdir6(dirname6(target), { recursive: true });
6299
- await writeFile8(target, content, { encoding: "utf8", flag: "wx" });
6461
+ await mkdir7(dirname7(target), { recursive: true });
6462
+ await writeFile9(target, content, { encoding: "utf8", flag: "wx" });
6300
6463
  result.copied.push(target);
6301
6464
  }
6302
6465
  }
6303
6466
  }
6304
6467
  if (options.configureCodeGraph) {
6305
- const path = join18(options.projectRoot, ".mcp.json");
6468
+ const path = join19(options.projectRoot, ".mcp.json");
6306
6469
  const current = await readJsonObject(path);
6307
6470
  if (current === null) {
6308
6471
  result.warnings.push(".mcp.json \u4E0D\u662F\u6709\u6548 JSON\uFF0C\u5DF2\u4FDD\u7559\u539F\u6587\u4EF6\u5E76\u8DF3\u8FC7 CodeGraph MCP \u914D\u7F6E");
@@ -6317,7 +6480,7 @@ async function applyCodeBuddySetup(options) {
6317
6480
  codegraph: { command: "codegraph", args: ["serve", "--mcp"] }
6318
6481
  }
6319
6482
  };
6320
- await writeFile8(path, JSON.stringify(next, null, 2) + "\n", "utf8");
6483
+ await writeFile9(path, JSON.stringify(next, null, 2) + "\n", "utf8");
6321
6484
  result.mcpUpdated = true;
6322
6485
  }
6323
6486
  }
@@ -6326,13 +6489,13 @@ async function applyCodeBuddySetup(options) {
6326
6489
  }
6327
6490
 
6328
6491
  // src/commands/refresh.ts
6329
- import { readFile as readFile18 } from "node:fs/promises";
6330
- import { join as join19 } from "node:path";
6492
+ import { readFile as readFile19 } from "node:fs/promises";
6493
+ import { join as join20 } from "node:path";
6331
6494
  import { parse as parseYaml9 } from "yaml";
6332
6495
  async function detectProject(root) {
6333
6496
  let content;
6334
6497
  try {
6335
- content = await readFile18(join19(root, ".harness", "project.yaml"), "utf8");
6498
+ content = await readFile19(join20(root, ".harness", "project.yaml"), "utf8");
6336
6499
  } catch (error) {
6337
6500
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
6338
6501
  return { status: "absent" };
@@ -7195,6 +7358,7 @@ async function runRulesSync(options, dependencies) {
7195
7358
  configuredSurface(detection.config, options.codebuddySurface)
7196
7359
  );
7197
7360
  const learning = options.learn === false ? null : await synchronizeRuleCandidates(dependencies.cwd);
7361
+ const ruleReview = learning === null ? null : await exportRuleReviewQueue(dependencies.cwd);
7198
7362
  const exitCode = projections.conflicts.length > 0 ? 5 : 0;
7199
7363
  const payload = {
7200
7364
  schema_version: 1,
@@ -7211,7 +7375,8 @@ async function runRulesSync(options, dependencies) {
7211
7375
  unchanged: projections.unchanged.length,
7212
7376
  conflicts: projections.conflicts.length,
7213
7377
  agent_specific: projections.agent_specific.length,
7214
- rule_candidates: learning?.candidates ?? 0
7378
+ rule_candidates: learning?.candidates ?? 0,
7379
+ rule_review_pending: ruleReview?.pending.length ?? 0
7215
7380
  },
7216
7381
  items: [
7217
7382
  ...projections.migrated.map((path) => ({ path, status: "migrated" })),
@@ -7222,6 +7387,12 @@ async function runRulesSync(options, dependencies) {
7222
7387
  status: learning.changed ? "updated" : "unchanged",
7223
7388
  candidates: learning.candidates,
7224
7389
  scanned: learning.scanned
7390
+ }],
7391
+ ...ruleReview === null ? [] : [{
7392
+ path: ".harness/knowledge/rule-decisions.json",
7393
+ status: ruleReview.pending.length > 0 ? "pending-review" : "current",
7394
+ pending: ruleReview.pending.length,
7395
+ decided: ruleReview.decided
7225
7396
  }]
7226
7397
  ],
7227
7398
  warnings: [
@@ -7246,9 +7417,69 @@ async function runRulesSync(options, dependencies) {
7246
7417
  }
7247
7418
  }
7248
7419
 
7420
+ // src/commands/rules-review.ts
7421
+ import { readFile as readFile20 } from "node:fs/promises";
7422
+ import { resolve as resolve12 } from "node:path";
7423
+ async function runRulesReview(options, dependencies) {
7424
+ const detection = await detectProject(dependencies.cwd);
7425
+ if (detection.status === "absent") {
7426
+ dependencies.stderr("\u5C1A\u672A\u521D\u59CB\u5316 Hunter Harness\uFF1B\u8BF7\u5148\u8FD0\u884C `hunter-harness`\u3002\n");
7427
+ return 3;
7428
+ }
7429
+ if (detection.status === "invalid") {
7430
+ dependencies.stderr("PROJECT_CONFIG_INVALID\uFF1A.harness/project.yaml \u65E0\u6548\n");
7431
+ return 3;
7432
+ }
7433
+ try {
7434
+ const queue = await exportRuleReviewQueue(dependencies.cwd);
7435
+ let summary;
7436
+ let items;
7437
+ if (options.apply === void 0) {
7438
+ summary = { pending: queue.pending.length, decided: queue.decided };
7439
+ items = queue.pending;
7440
+ } else {
7441
+ const input = JSON.parse(
7442
+ await readFile20(resolve12(dependencies.cwd, options.apply), "utf8")
7443
+ );
7444
+ const result = await applyRuleReviewDecisions(dependencies.cwd, input);
7445
+ summary = { applied: result.applied, recorded: result.recorded };
7446
+ items = [{ path: result.path, status: "updated" }];
7447
+ }
7448
+ const payload = {
7449
+ schema_version: 1,
7450
+ command: "rules-review",
7451
+ request_id: uuidV7(),
7452
+ dry_run: false,
7453
+ ok: true,
7454
+ exit_code: 0,
7455
+ project_id: detection.config.project.project_id,
7456
+ summary,
7457
+ items,
7458
+ warnings: [],
7459
+ errors: []
7460
+ };
7461
+ if (options.json === true) dependencies.stdout(serializeCliResult(payload));
7462
+ else if (options.apply === void 0) {
7463
+ dependencies.stdout(
7464
+ `\u516C\u5171\u89C4\u5219\u5019\u9009\uFF1A\u5F85\u8BC4\u5BA1 ${summary.pending ?? 0}\uFF0C\u5DF2\u51B3\u5B9A ${summary.decided ?? 0}\u3002
7465
+ `
7466
+ );
7467
+ } else {
7468
+ dependencies.stdout(
7469
+ `\u516C\u5171\u89C4\u5219\u51B3\u7B56\uFF1A\u5E94\u7528 ${summary.applied ?? 0}\uFF0C\u8BB0\u5F55 ${summary.recorded ?? 0}\u3002
7470
+ `
7471
+ );
7472
+ }
7473
+ return 0;
7474
+ } catch (error) {
7475
+ dependencies.stderr((error instanceof Error ? error.message : String(error)) + "\n");
7476
+ return 1;
7477
+ }
7478
+ }
7479
+
7249
7480
  // src/commands/recovery.ts
7250
7481
  import { stat as stat6 } from "node:fs/promises";
7251
- import { join as join20 } from "node:path";
7482
+ import { join as join21 } from "node:path";
7252
7483
  async function exists5(path) {
7253
7484
  try {
7254
7485
  await stat6(path);
@@ -7308,7 +7539,7 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
7308
7539
  return 5;
7309
7540
  }
7310
7541
  }
7311
- const initialized = await exists5(join20(dependencies.cwd, ".harness", "project.yaml"));
7542
+ const initialized = await exists5(join21(dependencies.cwd, ".harness", "project.yaml"));
7312
7543
  if (!initialized || options.nonInteractive === true || explicitConfigure(options)) {
7313
7544
  return null;
7314
7545
  }
@@ -7351,8 +7582,8 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
7351
7582
  }
7352
7583
 
7353
7584
  // src/workflow-data/resolve.ts
7354
- import { cp, mkdir as mkdir7, readdir as readdir10, readFile as readFile19, rm as rm8, stat as stat7 } from "node:fs/promises";
7355
- import { dirname as dirname7, join as join21, relative as relative3 } from "node:path";
7585
+ import { cp, mkdir as mkdir8, readdir as readdir10, readFile as readFile21, rm as rm8, stat as stat7 } from "node:fs/promises";
7586
+ import { dirname as dirname8, join as join22, relative as relative4 } from "node:path";
7356
7587
  import { fileURLToPath } from "node:url";
7357
7588
  var WorkflowDataResolutionError = class extends Error {
7358
7589
  code;
@@ -7389,14 +7620,14 @@ async function listFilesRecursive(root, base = root) {
7389
7620
  const entries = await readdir10(root, { withFileTypes: true });
7390
7621
  const files = [];
7391
7622
  for (const entry of entries) {
7392
- const absolute = join21(root, entry.name);
7623
+ const absolute = join22(root, entry.name);
7393
7624
  if (entry.isDirectory()) {
7394
7625
  files.push(...await listFilesRecursive(absolute, base));
7395
7626
  continue;
7396
7627
  }
7397
7628
  if (!entry.isFile()) continue;
7398
- const rel = relative3(base, absolute).replaceAll("\\", "/");
7399
- files.push({ path: rel, content: await readFile19(absolute, "utf8") });
7629
+ const rel = relative4(base, absolute).replaceAll("\\", "/");
7630
+ files.push({ path: rel, content: await readFile21(absolute, "utf8") });
7400
7631
  }
7401
7632
  return files;
7402
7633
  }
@@ -7409,7 +7640,7 @@ async function verifyWorkflowPackageIntegrity(resourcesRoot) {
7409
7640
  const manifest = await readWorkflowFamilyManifest(resourcesRoot);
7410
7641
  const expected = manifest.content_sha256;
7411
7642
  if (typeof expected !== "string" || expected.length === 0) return;
7412
- const harnessRoot = join21(resourcesRoot, "harness");
7643
+ const harnessRoot = join22(resourcesRoot, "harness");
7413
7644
  if (!await pathExists3(harnessRoot)) {
7414
7645
  throw new WorkflowDataResolutionError(
7415
7646
  "\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u7F3A\u5C11 harness/\uFF0C\u65E0\u6CD5\u6821\u9A8C SHA-256",
@@ -7428,40 +7659,40 @@ async function verifyWorkflowPackageIntegrity(resourcesRoot) {
7428
7659
  }
7429
7660
  }
7430
7661
  async function monorepoResourcesRoot() {
7431
- const here = dirname7(fileURLToPath(import.meta.url));
7662
+ const here = dirname8(fileURLToPath(import.meta.url));
7432
7663
  const candidates = [
7433
7664
  // TypeScript source: packages/cli/src/workflow-data -> packages/workflow-data-harness.
7434
- join21(here, "../../../workflow-data-harness"),
7665
+ join22(here, "../../../workflow-data-harness"),
7435
7666
  // Bundled CLI: packages/cli/dist -> packages/workflow-data-harness.
7436
- join21(here, "../../workflow-data-harness"),
7667
+ join22(here, "../../workflow-data-harness"),
7437
7668
  // Test/build layouts that preserve additional source directory levels.
7438
- join21(here, "../../../../packages/workflow-data-harness")
7669
+ join22(here, "../../../../packages/workflow-data-harness")
7439
7670
  ];
7440
7671
  for (const candidate of candidates) {
7441
- if (await pathExists3(join21(candidate, "harness", "manifests"))) return candidate;
7672
+ if (await pathExists3(join22(candidate, "harness", "manifests"))) return candidate;
7442
7673
  }
7443
7674
  return null;
7444
7675
  }
7445
7676
  async function siblingWorkflowPackage(cwd) {
7446
- const here = dirname7(fileURLToPath(import.meta.url));
7677
+ const here = dirname8(fileURLToPath(import.meta.url));
7447
7678
  const candidates = [
7448
- join21(cwd, "node_modules", "@hunter-harness", "workflow-harness"),
7679
+ join22(cwd, "node_modules", "@hunter-harness", "workflow-harness"),
7449
7680
  // Published layout: node_modules/hunter-harness/dist/bin.js alongside the
7450
7681
  // scoped workflow package in node_modules/@hunter-harness/workflow-harness.
7451
- join21(here, "..", "..", "@hunter-harness", "workflow-harness"),
7682
+ join22(here, "..", "..", "@hunter-harness", "workflow-harness"),
7452
7683
  // Monorepo bundled layout: packages/cli/dist/bin.js.
7453
- join21(here, "..", "..", "workflow-data-harness")
7684
+ join22(here, "..", "..", "workflow-data-harness")
7454
7685
  ];
7455
7686
  for (const candidate of candidates) {
7456
- if (await pathExists3(join21(candidate, "harness", "manifests"))) return candidate;
7687
+ if (await pathExists3(join22(candidate, "harness", "manifests"))) return candidate;
7457
7688
  }
7458
7689
  return null;
7459
7690
  }
7460
7691
  async function readCachedNpmPackageVersion(cacheRoot) {
7461
- const manifestPath = join21(cacheRoot, "package.json");
7692
+ const manifestPath = join22(cacheRoot, "package.json");
7462
7693
  if (!await pathExists3(manifestPath)) return null;
7463
7694
  try {
7464
- const pkg = JSON.parse(await readFile19(manifestPath, "utf8"));
7695
+ const pkg = JSON.parse(await readFile21(manifestPath, "utf8"));
7465
7696
  return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : null;
7466
7697
  } catch {
7467
7698
  return null;
@@ -7483,13 +7714,13 @@ async function latestWorkflowCacheIsStale(cacheRoot, packageName, resolveManifes
7483
7714
  }
7484
7715
  }
7485
7716
  async function materializeWorkflowPackageCache(cacheRoot, packageSpec, extract) {
7486
- await mkdir7(cacheRoot, { recursive: true });
7487
- const staging = join21(cacheRoot, ".extract");
7717
+ await mkdir8(cacheRoot, { recursive: true });
7718
+ const staging = join22(cacheRoot, ".extract");
7488
7719
  await rm8(staging, { recursive: true, force: true });
7489
- await mkdir7(staging, { recursive: true });
7720
+ await mkdir8(staging, { recursive: true });
7490
7721
  await extract(packageSpec, staging);
7491
- const packageDir = await pathExists3(join21(staging, "harness", "manifests")) ? staging : join21(staging, "package");
7492
- if (!await pathExists3(join21(packageDir, "harness", "manifests"))) {
7722
+ const packageDir = await pathExists3(join22(staging, "harness", "manifests")) ? staging : join22(staging, "package");
7723
+ if (!await pathExists3(join22(packageDir, "harness", "manifests"))) {
7493
7724
  throw new WorkflowDataResolutionError(
7494
7725
  "\u5DE5\u4F5C\u6D41\u6570\u636E\u5305\u5185\u5BB9\u65E0\u6548\uFF1A\u7F3A\u5C11 harness/manifests",
7495
7726
  "WORKFLOW_DATA_INVALID",
@@ -7518,8 +7749,8 @@ async function resolveWorkflowResourcesRoot(options, argv = []) {
7518
7749
  const packageName = workflowPackageName(family, options.env);
7519
7750
  const packageSpec = version === "latest" ? packageName : `${packageName}@${version}`;
7520
7751
  const cacheKey = packageSpec.replace("/", "+");
7521
- const cacheRoot = join21(options.cwd, ".harness", "cache", "workflow-packages", cacheKey);
7522
- if (await pathExists3(join21(cacheRoot, "harness", "manifests"))) {
7752
+ const cacheRoot = join22(options.cwd, ".harness", "cache", "workflow-packages", cacheKey);
7753
+ if (await pathExists3(join22(cacheRoot, "harness", "manifests"))) {
7523
7754
  if (version === "latest") {
7524
7755
  const stale = await latestWorkflowCacheIsStale(cacheRoot, packageName, options.pacoteManifest);
7525
7756
  if (stale) {
@@ -7565,9 +7796,9 @@ function describeWorkflowDataFetchFailure(error, packageSpec) {
7565
7796
  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}`;
7566
7797
  }
7567
7798
  async function readWorkflowFamilyManifest(resourcesRoot) {
7568
- const manifestPath = join21(resourcesRoot, "hunter-workflow-family.json");
7799
+ const manifestPath = join22(resourcesRoot, "hunter-workflow-family.json");
7569
7800
  if (!await pathExists3(manifestPath)) return {};
7570
- return JSON.parse(await readFile19(manifestPath, "utf8"));
7801
+ return JSON.parse(await readFile21(manifestPath, "utf8"));
7571
7802
  }
7572
7803
 
7573
7804
  // src/bin.ts
@@ -7687,6 +7918,12 @@ async function runCli(argv, overrides = {}) {
7687
7918
  dependencies
7688
7919
  );
7689
7920
  });
7921
+ program.command("rules-review").description("\u5BFC\u51FA\u5F85\u8BC4\u5BA1\u516C\u5171\u89C4\u5219\u5019\u9009\uFF0C\u6216\u5E94\u7528\u7ECF\u7528\u6237\u786E\u8BA4\u7684\u89C4\u5219\u51B3\u7B56").option("--apply <file>", "\u5E94\u7528\u5305\u542B\u5019\u9009 revision \u548C\u76EE\u6807 hash \u7684\u51B3\u7B56 JSON").option("--json").action(async (options) => {
7922
+ exitCode = await runRulesReview(
7923
+ { ...program.opts(), ...options },
7924
+ dependencies
7925
+ );
7926
+ });
7690
7927
  try {
7691
7928
  await program.parseAsync(["node", "hunter-harness", ...argv]);
7692
7929
  return exitCode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hunter-harness",
3
- "version": "0.2.30",
3
+ "version": "0.2.31",
4
4
  "description": "Local-first, server-governed agent harness",
5
5
  "license": "MIT",
6
6
  "type": "module",