hunter-harness 0.1.0 → 0.1.2

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 (71) hide show
  1. package/dist/bin.js +1033 -219
  2. package/package.json +2 -2
  3. package/resources/harness/general/.harness-build.json +1 -1
  4. package/resources/harness/general/harness-archive/SKILL.md +215 -215
  5. package/resources/harness/general/harness-codebase-map/SKILL.md +112 -112
  6. package/resources/harness/general/harness-codebase-map/templates/map-summary.md +3 -3
  7. package/resources/harness/general/harness-knowledge-ingest/SKILL.md +246 -246
  8. package/resources/harness/general/harness-knowledge-query/SKILL.md +164 -164
  9. package/resources/harness/general/harness-plan/SKILL.md +127 -127
  10. package/resources/harness/general/harness-review/SKILL.md +156 -156
  11. package/resources/harness/general/harness-run/SKILL.md +132 -132
  12. package/resources/harness/general/harness-submit/SKILL.md +159 -159
  13. package/resources/harness/general/harness-sync/SKILL.md +82 -82
  14. package/resources/harness/general/harness-test/SKILL.md +180 -180
  15. package/resources/harness/general/scripts/harness_deploy.py +580 -580
  16. package/resources/harness/java/.harness-build.json +1 -1
  17. package/resources/harness/java/harness-apidoc/SKILL.md +86 -86
  18. package/resources/harness/java/harness-archive/SKILL.md +215 -215
  19. package/resources/harness/java/harness-codebase-map/SKILL.md +112 -112
  20. package/resources/harness/java/harness-codebase-map/templates/map-summary.md +3 -3
  21. package/resources/harness/java/harness-knowledge-ingest/SKILL.md +246 -246
  22. package/resources/harness/java/harness-knowledge-query/SKILL.md +164 -164
  23. package/resources/harness/java/harness-package/SKILL.md +87 -87
  24. package/resources/harness/java/harness-plan/SKILL.md +127 -127
  25. package/resources/harness/java/harness-review/SKILL.md +156 -156
  26. package/resources/harness/java/harness-run/SKILL.md +148 -148
  27. package/resources/harness/java/harness-submit/SKILL.md +166 -166
  28. package/resources/harness/java/harness-sync/SKILL.md +82 -82
  29. package/resources/harness/java/harness-test/SKILL.md +192 -192
  30. package/resources/harness/java/scripts/harness_deploy.py +580 -580
  31. package/resources/harness/manifests/general.json +13 -33
  32. package/resources/harness/manifests/java.json +15 -35
  33. package/resources/harness/migrations/0.1.1/general.json +358 -0
  34. package/resources/harness/migrations/0.1.1/java.json +398 -0
  35. package/resources/bootstrap-ir/manifest.json +0 -19
  36. package/resources/bootstrap-ir/skills/harness-apidoc.yaml +0 -24
  37. package/resources/bootstrap-ir/skills/harness-archive.yaml +0 -24
  38. package/resources/bootstrap-ir/skills/harness-codebase-map.yaml +0 -24
  39. package/resources/bootstrap-ir/skills/harness-knowledge-ingest.yaml +0 -24
  40. package/resources/bootstrap-ir/skills/harness-package.yaml +0 -24
  41. package/resources/bootstrap-ir/skills/harness-plan.yaml +0 -24
  42. package/resources/bootstrap-ir/skills/harness-review.yaml +0 -24
  43. package/resources/bootstrap-ir/skills/harness-run.yaml +0 -24
  44. package/resources/bootstrap-ir/skills/harness-skill-optimizer.yaml +0 -28
  45. package/resources/bootstrap-ir/skills/harness-submit.yaml +0 -24
  46. package/resources/bootstrap-ir/skills/harness-sync.yaml +0 -24
  47. package/resources/bootstrap-ir/skills/harness-test.yaml +0 -24
  48. package/resources/bootstrap-ir/templates/claude-code-skill.md +0 -12
  49. package/resources/harness/general/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/archive/2026-01-10-ledger-reconciliation/reports/final/summary-data.json +0 -43
  50. package/resources/harness/general/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/archive/2026-02-14-ledger-snapshot-followup/reports/final/summary-data.json +0 -38
  51. package/resources/harness/general/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/archive/2026-03-05-webhook-contract/reports/final/summary-data.json +0 -36
  52. package/resources/harness/general/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/project.yaml +0 -1
  53. package/resources/harness/general/harness-knowledge-ingest/tests/test_harness_knowledge.py +0 -1792
  54. package/resources/harness/java/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/archive/2026-01-10-ledger-reconciliation/reports/final/summary-data.json +0 -43
  55. package/resources/harness/java/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/archive/2026-02-14-ledger-snapshot-followup/reports/final/summary-data.json +0 -38
  56. package/resources/harness/java/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/archive/2026-03-05-webhook-contract/reports/final/summary-data.json +0 -36
  57. package/resources/harness/java/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/project.yaml +0 -1
  58. package/resources/harness/java/harness-knowledge-ingest/tests/test_harness_knowledge.py +0 -1792
  59. package/resources/manifest.json +0 -19
  60. package/resources/skills/harness-apidoc/SKILL.md +0 -50
  61. package/resources/skills/harness-archive/SKILL.md +0 -48
  62. package/resources/skills/harness-codebase-map/SKILL.md +0 -53
  63. package/resources/skills/harness-knowledge-ingest/SKILL.md +0 -48
  64. package/resources/skills/harness-package/SKILL.md +0 -48
  65. package/resources/skills/harness-plan/SKILL.md +0 -51
  66. package/resources/skills/harness-review/SKILL.md +0 -50
  67. package/resources/skills/harness-run/SKILL.md +0 -48
  68. package/resources/skills/harness-skill-optimizer/SKILL.md +0 -54
  69. package/resources/skills/harness-submit/SKILL.md +0 -47
  70. package/resources/skills/harness-sync/SKILL.md +0 -48
  71. package/resources/skills/harness-test/SKILL.md +0 -50
package/dist/bin.js CHANGED
@@ -414,7 +414,7 @@ function normalize(value) {
414
414
  return value.map(normalize);
415
415
  }
416
416
  if (value !== null && typeof value === "object") {
417
- return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, normalize(item)]));
417
+ return Object.fromEntries(Object.entries(value).filter(([, item2]) => item2 !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item2]) => [key, normalize(item2)]));
418
418
  }
419
419
  return value;
420
420
  }
@@ -789,8 +789,8 @@ function normalizeManagedPath(input) {
789
789
  }
790
790
  function assertNoCaseCollisions(paths) {
791
791
  const seen = /* @__PURE__ */ new Map();
792
- for (const item of paths) {
793
- const normalized = normalizeManagedPath(item);
792
+ for (const item2 of paths) {
793
+ const normalized = normalizeManagedPath(item2);
794
794
  const folded = normalized.toLocaleLowerCase("en-US");
795
795
  const existing = seen.get(folded);
796
796
  if (existing !== void 0 && existing !== normalized) {
@@ -817,8 +817,8 @@ async function assertNoSymlinks(root, relativePath) {
817
817
  for (const segment of normalized.split("/")) {
818
818
  current = join(current, segment);
819
819
  try {
820
- const stat5 = await lstat(current);
821
- if (stat5.isSymbolicLink()) {
820
+ const stat6 = await lstat(current);
821
+ if (stat6.isSymbolicLink()) {
822
822
  throw new UnsafePathError("symbolic links are not managed");
823
823
  }
824
824
  } catch (error) {
@@ -834,7 +834,7 @@ async function assertNoSymlinks(root, relativePath) {
834
834
  async function withRetry(action, options) {
835
835
  const attempts = options.attempts ?? 3;
836
836
  const sleep = options.sleep ?? (async (milliseconds) => {
837
- await new Promise((resolve6) => setTimeout(resolve6, milliseconds));
837
+ await new Promise((resolve7) => setTimeout(resolve7, milliseconds));
838
838
  });
839
839
  let lastError;
840
840
  for (let attempt = 1; attempt <= attempts; attempt += 1) {
@@ -1094,6 +1094,17 @@ function removeManagedBlock(original) {
1094
1094
  const after = original.slice(end).replace(/^(?:\r?\n){1,2}/, "");
1095
1095
  return before + after;
1096
1096
  }
1097
+ function refreshManagedBlock(original, blockContent) {
1098
+ const starts = markerCount(original, MANAGED_BLOCK_START);
1099
+ const ends = markerCount(original, MANAGED_BLOCK_END);
1100
+ const absent = starts === 0 && ends === 0;
1101
+ const malformed = !absent && (starts !== 1 || ends !== 1 || original.indexOf(MANAGED_BLOCK_START) > original.indexOf(MANAGED_BLOCK_END));
1102
+ if (malformed) {
1103
+ return { content: original, action: "preserved_conflict", conflict: true };
1104
+ }
1105
+ const action = absent ? "appended" : "refreshed";
1106
+ return { content: upsertManagedBlock(original, blockContent), action, conflict: false };
1107
+ }
1097
1108
  function escapeRe(value) {
1098
1109
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1099
1110
  }
@@ -1244,14 +1255,13 @@ function decideUpdate(policy, dirty) {
1244
1255
  }
1245
1256
 
1246
1257
  // ../core/dist/project/initialize.js
1247
- import { createHash as createHash2 } from "node:crypto";
1248
- import { readFile as readFile3, stat as stat2 } from "node:fs/promises";
1249
- import { basename, join as join4, resolve as resolve3 } from "node:path";
1258
+ import { readFile as readFile4, stat as stat2 } from "node:fs/promises";
1259
+ import { basename, join as join5, resolve as resolve3 } from "node:path";
1250
1260
  import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
1251
1261
 
1252
1262
  // ../core/dist/transaction/transaction.js
1253
1263
  import { randomUUID as randomUUID2 } from "node:crypto";
1254
- import { copyFile, mkdir as mkdir3, readFile as readFile2, rename as rename2, rm as rm2, stat, writeFile as writeFile2 } from "node:fs/promises";
1264
+ import { copyFile, mkdir as mkdir3, readFile as readFile2, readdir, rename as rename2, rm as rm2, stat, writeFile as writeFile2 } from "node:fs/promises";
1255
1265
  import { dirname as dirname2, join as join3 } from "node:path";
1256
1266
 
1257
1267
  // ../core/dist/state/layout.js
@@ -1277,9 +1287,7 @@ async function ensureStateLayout(projectRoot) {
1277
1287
  mkdir2(layout.baseline, { recursive: true }),
1278
1288
  mkdir2(layout.transactions, { recursive: true }),
1279
1289
  mkdir2(layout.locks, { recursive: true }),
1280
- mkdir2(layout.local, { recursive: true }),
1281
- mkdir2(layout.serverArtifacts, { recursive: true }),
1282
- mkdir2(layout.reports, { recursive: true })
1290
+ mkdir2(layout.local, { recursive: true })
1283
1291
  ]);
1284
1292
  return layout;
1285
1293
  }
@@ -1322,6 +1330,31 @@ async function writeJournal(transactionRoot, journal) {
1322
1330
  updated_at: (/* @__PURE__ */ new Date()).toISOString()
1323
1331
  });
1324
1332
  }
1333
+ async function pruneOlderSuccessful(layout, currentId, kind) {
1334
+ if (kind === void 0)
1335
+ return;
1336
+ let entries;
1337
+ try {
1338
+ entries = await readdir(layout.transactions);
1339
+ } catch (error) {
1340
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
1341
+ return;
1342
+ throw error;
1343
+ }
1344
+ for (const name of entries) {
1345
+ if (name === currentId)
1346
+ continue;
1347
+ const txRoot = join3(layout.transactions, name);
1348
+ try {
1349
+ const journal = JSON.parse(await readFile2(join3(txRoot, "journal.json"), "utf8"));
1350
+ if (journal.state === "committed" && journal.kind === kind) {
1351
+ await rm2(txRoot, { recursive: true, force: true });
1352
+ }
1353
+ } catch {
1354
+ continue;
1355
+ }
1356
+ }
1357
+ }
1325
1358
  async function snapshotPaths(projectRoot, transactionRoot, paths) {
1326
1359
  const snapshots = [];
1327
1360
  for (const path of paths) {
@@ -1461,7 +1494,6 @@ async function runTransaction(projectRoot, rawOperations, options = {}) {
1461
1494
  await atomicWriteJson(join3(transactionRoot, "after", "manifest.json"), after);
1462
1495
  journal.state = "committed";
1463
1496
  await writeJournal(transactionRoot, journal);
1464
- return { transactionId, status: "committed" };
1465
1497
  } catch (error) {
1466
1498
  if (error instanceof InterruptedTransactionError) {
1467
1499
  throw error;
@@ -1471,6 +1503,186 @@ async function runTransaction(projectRoot, rawOperations, options = {}) {
1471
1503
  await rollbackTransaction(projectRoot, transactionId);
1472
1504
  throw error;
1473
1505
  }
1506
+ await rm2(join3(transactionRoot, "staged"), { recursive: true, force: true });
1507
+ await pruneOlderSuccessful(layout, transactionId, options.kind);
1508
+ return { transactionId, status: "committed" };
1509
+ }
1510
+
1511
+ // ../core/dist/project/managed-content.js
1512
+ var AGENTS_MANAGED_BLOCK_CONTENT = [
1513
+ "# Hunter Harness",
1514
+ "",
1515
+ "Use .harness/context-index.json to route rules, Knowledge, and codebase maps.",
1516
+ "Treat .claude/skills/harness-* as editable adapter working copies.",
1517
+ "Do not modify .harness/state or .harness/cache directly."
1518
+ ].join("\n");
1519
+ var CLAUDE_MANAGED_BLOCK_CONTENT = [
1520
+ "@AGENTS.md",
1521
+ "",
1522
+ "# Hunter Harness",
1523
+ "",
1524
+ "- Rules: .claude/rules/",
1525
+ "- Skills: .claude/skills/harness-*/",
1526
+ "- Knowledge: .harness/knowledge/",
1527
+ "- Codebase map: .harness/codebase/map/"
1528
+ ].join("\n");
1529
+ var HARNESS_GENERAL_RULES_CONTENT = "# Hunter Harness Rules\n\n- Report evidence honestly.\n- Do not execute destructive actions without confirmation.\n";
1530
+ var HARNESS_JAVA_RULES_CONTENT = "# Java Profile\n\n- Verify builds and tests with the project build tool.\n";
1531
+
1532
+ // ../core/dist/project/profile-bundle.js
1533
+ import { createHash as createHash2 } from "node:crypto";
1534
+ import { readFile as readFile3, readdir as readdir2 } from "node:fs/promises";
1535
+ import { join as join4 } from "node:path";
1536
+ function isProfile(value) {
1537
+ return value === "general" || value === "java";
1538
+ }
1539
+ function validateRelativeBundlePath(path) {
1540
+ if (typeof path !== "string" || path.length === 0 || path.includes("\0") || path.startsWith("/") || path.startsWith("\\") || /^[A-Za-z]:/.test(path) || path.split("/").some((part) => part === "" || part === "." || part === "..")) {
1541
+ throw new Error("invalid Harness Bundle path");
1542
+ }
1543
+ }
1544
+ async function loadProfileBundle(resourcesRoot, profile) {
1545
+ const raw = JSON.parse(await readFile3(join4(resourcesRoot, "harness", "manifests", `${profile}.json`), "utf8"));
1546
+ if (raw.schema_version !== 1 || raw.profile !== profile || typeof raw.bundle_version !== "string" || raw.generator !== "harness_deploy.py" || !Array.isArray(raw.files)) {
1547
+ throw new Error(`invalid ${profile} Harness Bundle manifest`);
1548
+ }
1549
+ const files = /* @__PURE__ */ new Map();
1550
+ for (const item2 of raw.files) {
1551
+ validateRelativeBundlePath(item2.path);
1552
+ if (typeof item2.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(item2.sha256) || files.has(item2.path)) {
1553
+ throw new Error(`invalid ${profile} Harness Bundle manifest entry`);
1554
+ }
1555
+ const bytes = await readFile3(join4(resourcesRoot, "harness", profile, item2.path));
1556
+ if (createHash2("sha256").update(bytes).digest("hex") !== item2.sha256) {
1557
+ throw new Error(`Harness Bundle hash mismatch: ${item2.path}`);
1558
+ }
1559
+ files.set(item2.path, bytes);
1560
+ }
1561
+ return { manifest: raw, files };
1562
+ }
1563
+ async function managedBundleTargets(resourcesRoot, profile) {
1564
+ const bundle = await loadProfileBundle(resourcesRoot, profile);
1565
+ const targets = new Set(bundleTargetContents(bundle).keys());
1566
+ if (profile === "java")
1567
+ targets.add(".claude/rules/harness-profile-java.md");
1568
+ return targets;
1569
+ }
1570
+ var AGENT_SOURCE_PATH = /^agents\/([^/]+\.md)$/;
1571
+ function projectBundle(bundle) {
1572
+ const records = [];
1573
+ for (const [sourcePath, bytes] of bundle.files) {
1574
+ validateRelativeBundlePath(sourcePath);
1575
+ const agent = AGENT_SOURCE_PATH.exec(sourcePath);
1576
+ const projectedTarget = agent?.[1] !== void 0 ? `.claude/agents/${agent[1]}` : `.claude/skills/${sourcePath}`;
1577
+ const manifestEntry = bundle.manifest.files.find((entry) => entry.path === sourcePath);
1578
+ if (manifestEntry === void 0) {
1579
+ throw new Error(`Harness Bundle missing manifest entry: ${sourcePath}`);
1580
+ }
1581
+ records.push({
1582
+ source_path: sourcePath,
1583
+ target_path: normalizeManagedPath(projectedTarget),
1584
+ sha256: manifestEntry.sha256,
1585
+ bytes
1586
+ });
1587
+ }
1588
+ assertNoCaseCollisions(records.map((record) => record.target_path));
1589
+ return records.sort((left, right) => left.target_path.localeCompare(right.target_path));
1590
+ }
1591
+ function bundleTargetContents(bundle) {
1592
+ return new Map(projectBundle(bundle).map((record) => [record.target_path, record.bytes]));
1593
+ }
1594
+ function ruleTarget(sourcePath, targetPath, content) {
1595
+ return {
1596
+ source_path: sourcePath,
1597
+ target_path: targetPath,
1598
+ sha256: createHash2("sha256").update(content).digest("hex"),
1599
+ bytes: new TextEncoder().encode(content)
1600
+ };
1601
+ }
1602
+ async function managedTargets(resourcesRoot, profile) {
1603
+ const bundle = await loadProfileBundle(resourcesRoot, profile);
1604
+ const records = projectBundle(bundle).map((record) => ({
1605
+ source_path: record.source_path,
1606
+ target_path: record.target_path,
1607
+ sha256: record.sha256,
1608
+ bytes: record.bytes
1609
+ }));
1610
+ records.push(ruleTarget("rules/harness-general.md", ".claude/rules/harness-general.md", HARNESS_GENERAL_RULES_CONTENT));
1611
+ if (profile === "java") {
1612
+ records.push(ruleTarget("rules/harness-profile-java.md", ".claude/rules/harness-profile-java.md", HARNESS_JAVA_RULES_CONTENT));
1613
+ }
1614
+ assertNoCaseCollisions(records.map((record) => record.target_path));
1615
+ return records.sort((left, right) => left.target_path.localeCompare(right.target_path));
1616
+ }
1617
+ function parseHarnessProfile(value) {
1618
+ return isProfile(value) ? value : null;
1619
+ }
1620
+ function parseMigrationManifest(raw) {
1621
+ if (raw === null || typeof raw !== "object") {
1622
+ throw new Error("invalid Harness migration manifest");
1623
+ }
1624
+ const record = raw;
1625
+ if (record.schema_version !== 1 || !isProfile(record.profile) || typeof record.bundle_version !== "string" || !/^sha256:[a-f0-9]{64}$/.test(String(record.bundle_manifest_hash)) || !Array.isArray(record.projection)) {
1626
+ throw new Error("invalid Harness migration manifest");
1627
+ }
1628
+ const projection = [];
1629
+ for (const entry of record.projection) {
1630
+ if (entry === null || typeof entry !== "object") {
1631
+ throw new Error("invalid Harness migration manifest entry");
1632
+ }
1633
+ const item2 = entry;
1634
+ validateRelativeBundlePath(item2.source_path);
1635
+ if (typeof item2.target_path !== "string" || typeof item2.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(item2.sha256)) {
1636
+ throw new Error("invalid Harness migration manifest entry");
1637
+ }
1638
+ const targetPath = normalizeManagedPath(item2.target_path);
1639
+ if (!targetPath.startsWith(".claude/")) {
1640
+ throw new Error("invalid Harness migration manifest target");
1641
+ }
1642
+ projection.push({
1643
+ source_path: item2.source_path,
1644
+ target_path: targetPath,
1645
+ sha256: item2.sha256
1646
+ });
1647
+ }
1648
+ assertNoCaseCollisions(projection.map((item2) => item2.target_path));
1649
+ return {
1650
+ schema_version: 1,
1651
+ profile: record.profile,
1652
+ bundle_version: record.bundle_version,
1653
+ bundle_manifest_hash: record.bundle_manifest_hash,
1654
+ projection
1655
+ };
1656
+ }
1657
+ async function loadMigrationManifests(resourcesRoot) {
1658
+ const migrationsRoot = join4(resourcesRoot, "harness", "migrations");
1659
+ let versionDirs;
1660
+ try {
1661
+ versionDirs = await readdir2(migrationsRoot);
1662
+ } catch (error) {
1663
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
1664
+ return [];
1665
+ }
1666
+ throw error;
1667
+ }
1668
+ const manifests = [];
1669
+ for (const versionDir of versionDirs) {
1670
+ let files;
1671
+ try {
1672
+ files = await readdir2(join4(migrationsRoot, versionDir));
1673
+ } catch (error) {
1674
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
1675
+ continue;
1676
+ }
1677
+ throw error;
1678
+ }
1679
+ for (const file of files) {
1680
+ if (!file.endsWith(".json"))
1681
+ continue;
1682
+ manifests.push(parseMigrationManifest(JSON.parse(await readFile3(join4(migrationsRoot, versionDir, file), "utf8"))));
1683
+ }
1684
+ }
1685
+ return manifests;
1474
1686
  }
1475
1687
 
1476
1688
  // ../core/dist/project/uuid-v7.js
@@ -1498,7 +1710,7 @@ function uuidV7(now = Date.now()) {
1498
1710
  // ../core/dist/project/initialize.js
1499
1711
  async function readOptional(path) {
1500
1712
  try {
1501
- return await readFile3(path, "utf8");
1713
+ return await readFile4(path, "utf8");
1502
1714
  } catch (error) {
1503
1715
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
1504
1716
  return "";
@@ -1518,53 +1730,21 @@ async function exists2(path) {
1518
1730
  }
1519
1731
  }
1520
1732
  async function existingProjectConfig(root) {
1521
- const path = join4(root, ".harness", "project.yaml");
1733
+ const path = join5(root, ".harness", "project.yaml");
1522
1734
  const content = await readOptional(path);
1523
1735
  return content === "" ? null : projectConfigSchema.parse(parseYaml2(content));
1524
1736
  }
1525
1737
  async function operationFor(root, path, content) {
1526
- return await exists2(join4(root, path)) ? { operation: "modify", path, content } : { operation: "add", path, content };
1527
- }
1528
- async function readProfileBundle(resourcesRoot, profile) {
1529
- const manifest = JSON.parse(await readFile3(join4(resourcesRoot, "harness", "manifests", `${profile}.json`), "utf8"));
1530
- if (manifest.schema_version !== 1 || manifest.profile !== profile) {
1531
- throw new Error(`invalid ${profile} Harness Bundle manifest`);
1532
- }
1533
- const files = /* @__PURE__ */ new Map();
1534
- for (const item of manifest.files) {
1535
- const bytes = await readFile3(join4(resourcesRoot, "harness", profile, item.path));
1536
- if (createHash2("sha256").update(bytes).digest("hex") !== item.sha256) {
1537
- throw new Error(`Harness Bundle hash mismatch: ${item.path}`);
1538
- }
1539
- files.set(item.path, bytes);
1540
- }
1541
- return { manifest, files };
1542
- }
1543
- function bundleTargets(bundleFiles) {
1544
- const targets = /* @__PURE__ */ new Map();
1545
- for (const [path, bytes] of bundleFiles) {
1546
- targets.set(`.claude/skills/${path}`, bytes);
1547
- const agent = /^agents\/([^/]+\.md)$/.exec(path);
1548
- if (agent?.[1] !== void 0)
1549
- targets.set(`.claude/agents/${agent[1]}`, bytes);
1550
- }
1551
- return targets;
1738
+ return await exists2(join5(root, path)) ? { operation: "modify", path, content } : { operation: "add", path, content };
1552
1739
  }
1553
1740
  var INSTALLED_BUNDLE_PATH = ".harness/state/local/installed-harness-bundle.json";
1554
- async function previousInstalledFiles(root) {
1555
- const content = await readOptional(join4(root, INSTALLED_BUNDLE_PATH));
1556
- if (content === "")
1557
- return [];
1558
- const parsed = JSON.parse(content);
1559
- return parsed.schema_version === 1 && Array.isArray(parsed.files) ? parsed.files : [];
1560
- }
1561
1741
  async function initializeProject(options) {
1562
1742
  const root = resolve3(options.projectRoot);
1563
1743
  const config = initConfigSchema.parse(options.config);
1564
1744
  const existing = await existingProjectConfig(root);
1565
1745
  const profile = config.profile;
1566
- const bundle = await readProfileBundle(options.resourcesRoot, profile);
1567
- const bundleFiles = bundleTargets(bundle.files);
1746
+ const bundle = await loadProfileBundle(options.resourcesRoot, profile);
1747
+ const managed = await managedTargets(options.resourcesRoot, profile);
1568
1748
  const bundleHash = sha256Bytes(canonicalJson(bundle.manifest.files));
1569
1749
  const projectConfig = projectConfigSchema.parse({
1570
1750
  harness: { name: "hunter-harness", schema_version: 1 },
@@ -1588,23 +1768,8 @@ async function initializeProject(options) {
1588
1768
  artifact_manifest_hash: null,
1589
1769
  files: {}
1590
1770
  });
1591
- const agentsContent = upsertManagedBlock(await readOptional(join4(root, "AGENTS.md")), [
1592
- "# Hunter Harness",
1593
- "",
1594
- "Use .harness/context-index.json to route rules, Knowledge, and codebase maps.",
1595
- "Treat .claude/skills/harness-* as editable adapter working copies.",
1596
- "Do not modify .harness/state or .harness/cache directly."
1597
- ].join("\n"));
1598
- const claudeContent = upsertManagedBlock(await readOptional(join4(root, "CLAUDE.md")), [
1599
- "@AGENTS.md",
1600
- "",
1601
- "# Hunter Harness",
1602
- "",
1603
- "- Rules: .claude/rules/",
1604
- "- Skills: .claude/skills/harness-*/",
1605
- "- Knowledge: .harness/knowledge/",
1606
- "- Codebase map: .harness/codebase/map/"
1607
- ].join("\n"));
1771
+ const agentsContent = upsertManagedBlock(await readOptional(join5(root, "AGENTS.md")), AGENTS_MANAGED_BLOCK_CONTENT);
1772
+ const claudeContent = upsertManagedBlock(await readOptional(join5(root, "CLAUDE.md")), CLAUDE_MANAGED_BLOCK_CONTENT);
1608
1773
  const files = /* @__PURE__ */ new Map([
1609
1774
  [
1610
1775
  ".harness/project.yaml",
@@ -1632,44 +1797,29 @@ async function initializeProject(options) {
1632
1797
  ".harness/knowledge/index.json",
1633
1798
  JSON.stringify({ schema_version: 1, generated_at: null, entries: [] }, null, 2) + "\n"
1634
1799
  ],
1635
- [".harness/knowledge/_candidates/.gitkeep", ""],
1636
- [".harness/knowledge/project-local/.gitkeep", ""],
1637
- [".harness/codebase/map/.gitkeep", ""],
1638
- [".harness/state/local/.gitkeep", ""],
1639
- [".harness/cache/server-artifacts/.gitkeep", ""],
1640
- [".harness/reports/.gitkeep", ""],
1641
- [
1642
- ".harness/README.md",
1643
- "# Hunter Harness\n\nUse npx hunter-harness, update, and push.\n"
1644
- ],
1645
1800
  ["AGENTS.md", agentsContent],
1646
- ["CLAUDE.md", claudeContent],
1647
- [
1648
- ".claude/rules/harness-general.md",
1649
- "# Hunter Harness Rules\n\n- Report evidence honestly.\n- Do not execute destructive actions without confirmation.\n"
1650
- ]
1801
+ ["CLAUDE.md", claudeContent]
1651
1802
  ]);
1652
- if (config.profile === "java") {
1653
- files.set(".claude/rules/harness-profile-java.md", "# Java Profile\n\n- Verify builds and tests with the project build tool.\n");
1654
- }
1655
- for (const [targetPath, bytes] of bundleFiles) {
1656
- files.set(targetPath, bytes);
1803
+ for (const target of managed) {
1804
+ files.set(target.target_path, target.bytes);
1657
1805
  }
1658
- const installedFileList = [...bundleFiles.keys()].sort();
1659
- if (profile === "java")
1660
- installedFileList.push(".claude/rules/harness-profile-java.md");
1661
- const installedManifest = {
1662
- schema_version: 1,
1806
+ const installedState = {
1807
+ schema_version: 2,
1663
1808
  profile,
1664
- files: installedFileList.sort()
1809
+ bundle_version: bundle.manifest.bundle_version,
1810
+ bundle_manifest_hash: bundleHash,
1811
+ installed_at: (/* @__PURE__ */ new Date()).toISOString(),
1812
+ files: managed.map((target) => ({
1813
+ source_path: target.source_path,
1814
+ target_path: target.target_path,
1815
+ sha256: target.sha256
1816
+ })).sort((left, right) => left.target_path.localeCompare(right.target_path))
1665
1817
  };
1666
- files.set(INSTALLED_BUNDLE_PATH, JSON.stringify(installedManifest, null, 2) + "\n");
1667
- const newManaged = new Set(installedManifest.files);
1668
- const deleteOperations = (await previousInstalledFiles(root)).filter((path) => !newManaged.has(path)).map((path) => ({ operation: "delete", path }));
1818
+ files.set(INSTALLED_BUNDLE_PATH, JSON.stringify(installedState, null, 2) + "\n");
1669
1819
  const paths = [...files.keys()].sort((left, right) => left.localeCompare(right));
1670
1820
  if (!options.dryRun) {
1671
1821
  const writeOperations = await Promise.all([...files.entries()].map(async ([path, content]) => operationFor(root, path, content)));
1672
- await runTransaction(root, [...deleteOperations, ...writeOperations], { kind: "init" });
1822
+ await runTransaction(root, writeOperations, { kind: "init" });
1673
1823
  }
1674
1824
  return {
1675
1825
  projectConfig,
@@ -1679,6 +1829,325 @@ async function initializeProject(options) {
1679
1829
  };
1680
1830
  }
1681
1831
 
1832
+ // ../core/dist/project/refresh.js
1833
+ import { createHash as createHash3 } from "node:crypto";
1834
+ import { readFile as readFile5, readdir as readdir3, rmdir, stat as stat3 } from "node:fs/promises";
1835
+ import { dirname as dirname3, join as join6, resolve as resolve4 } from "node:path";
1836
+ import { parse as parseYaml3, stringify as stringifyYaml2 } from "yaml";
1837
+ var INSTALLED_STATE_PATH = ".harness/state/local/installed-harness-bundle.json";
1838
+ var CONTEXT_INDEX_PATH = ".harness/context-index.json";
1839
+ async function exists3(path) {
1840
+ try {
1841
+ await stat3(path);
1842
+ return true;
1843
+ } catch (error) {
1844
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
1845
+ return false;
1846
+ }
1847
+ throw error;
1848
+ }
1849
+ }
1850
+ async function fileHex(path) {
1851
+ try {
1852
+ return createHash3("sha256").update(await readFile5(path)).digest("hex");
1853
+ } catch (error) {
1854
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
1855
+ return null;
1856
+ }
1857
+ throw error;
1858
+ }
1859
+ }
1860
+ async function readOptionalText(path) {
1861
+ try {
1862
+ return await readFile5(path, "utf8");
1863
+ } catch (error) {
1864
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
1865
+ return "";
1866
+ }
1867
+ throw error;
1868
+ }
1869
+ }
1870
+ async function readInstalledState(root) {
1871
+ const content = await readOptionalText(join6(root, INSTALLED_STATE_PATH));
1872
+ if (content === "") {
1873
+ return { profile: null, schemaVersion: null, trusted: /* @__PURE__ */ new Map() };
1874
+ }
1875
+ let parsed;
1876
+ try {
1877
+ parsed = JSON.parse(content);
1878
+ } catch {
1879
+ return { profile: null, schemaVersion: null, trusted: /* @__PURE__ */ new Map() };
1880
+ }
1881
+ const profile = parseHarnessProfile(parsed.profile);
1882
+ const trusted = /* @__PURE__ */ new Map();
1883
+ if (parsed.schema_version === 2 && Array.isArray(parsed.files)) {
1884
+ for (const entry of parsed.files) {
1885
+ if (entry !== null && typeof entry === "object" && "target_path" in entry && "sha256" in entry) {
1886
+ const target = entry.target_path;
1887
+ const sha = entry.sha256;
1888
+ if (typeof target === "string" && typeof sha === "string") {
1889
+ trusted.set(target, sha);
1890
+ }
1891
+ }
1892
+ }
1893
+ }
1894
+ return { profile, schemaVersion: typeof parsed.schema_version === "number" ? parsed.schema_version : null, trusted };
1895
+ }
1896
+ async function readContextIndexBundleHash(root) {
1897
+ const content = await readOptionalText(join6(root, CONTEXT_INDEX_PATH));
1898
+ if (content === "")
1899
+ return null;
1900
+ try {
1901
+ const record = JSON.parse(content);
1902
+ const hash = record.skill_bundle?.bundle_hash;
1903
+ return typeof hash === "string" ? hash : null;
1904
+ } catch {
1905
+ return null;
1906
+ }
1907
+ }
1908
+ async function pruneEmptyParentDirs(root, deletedPaths) {
1909
+ const claudeRoot = join6(root, ".claude");
1910
+ const boundaries = /* @__PURE__ */ new Set([
1911
+ claudeRoot,
1912
+ join6(claudeRoot, "skills"),
1913
+ join6(claudeRoot, "agents")
1914
+ ]);
1915
+ for (const deleted of deletedPaths) {
1916
+ let dir = dirname3(join6(root, deleted));
1917
+ while (dir.startsWith(claudeRoot) && !boundaries.has(dir)) {
1918
+ let entries;
1919
+ try {
1920
+ entries = await readdir3(dir);
1921
+ } catch {
1922
+ break;
1923
+ }
1924
+ if (entries.length > 0)
1925
+ break;
1926
+ try {
1927
+ await rmdir(dir);
1928
+ } catch {
1929
+ break;
1930
+ }
1931
+ dir = dirname3(dir);
1932
+ }
1933
+ }
1934
+ }
1935
+ function item(target, action, reason, oldSha, incomingSha) {
1936
+ return {
1937
+ source_path: target.source_path,
1938
+ target_path: target.target_path,
1939
+ action,
1940
+ reason,
1941
+ old_sha256: oldSha,
1942
+ incoming_sha256: incomingSha
1943
+ };
1944
+ }
1945
+ function conflict(target, reason, oldSha, incomingSha) {
1946
+ return {
1947
+ source_path: target.source_path,
1948
+ target_path: target.target_path,
1949
+ reason,
1950
+ old_sha256: oldSha,
1951
+ incoming_sha256: incomingSha
1952
+ };
1953
+ }
1954
+ function sortByTarget(items) {
1955
+ return [...items].sort((left, right) => left.target_path.localeCompare(right.target_path));
1956
+ }
1957
+ async function refreshMarkdownBlock(root, fileName, blockContent, ops, conflicts, preserved) {
1958
+ const original = await readOptionalText(join6(root, fileName));
1959
+ const current = original === "" ? null : createHash3("sha256").update(original).digest("hex");
1960
+ const refresh = refreshManagedBlock(original, blockContent);
1961
+ const synthetic = {
1962
+ source_path: fileName,
1963
+ target_path: fileName,
1964
+ sha256: createHash3("sha256").update(blockContent).digest("hex"),
1965
+ bytes: new TextEncoder().encode(blockContent)
1966
+ };
1967
+ if (refresh.conflict) {
1968
+ preserved.push(item(synthetic, "preserve", "MALFORMED_MANAGED_BLOCK", current, synthetic.sha256));
1969
+ conflicts.push(conflict(synthetic, "MALFORMED_MANAGED_BLOCK", current, synthetic.sha256));
1970
+ return;
1971
+ }
1972
+ if (refresh.content === original) {
1973
+ return;
1974
+ }
1975
+ ops.push({
1976
+ operation: original === "" ? "add" : "modify",
1977
+ path: fileName,
1978
+ content: refresh.content
1979
+ });
1980
+ }
1981
+ async function reconcileContextIndex(root, bundleVersion, bundleManifestHash) {
1982
+ const existing = await readOptionalText(join6(root, CONTEXT_INDEX_PATH));
1983
+ let record = {};
1984
+ if (existing !== "") {
1985
+ try {
1986
+ record = JSON.parse(existing);
1987
+ } catch {
1988
+ record = {};
1989
+ }
1990
+ }
1991
+ if (Object.keys(record).length === 0) {
1992
+ record = {
1993
+ schema_version: 1,
1994
+ project: { claude_md: "CLAUDE.md", agents_md: "AGENTS.md" },
1995
+ rules: [".claude/rules/harness-general.md"],
1996
+ knowledge: { index: ".harness/knowledge/index.json" },
1997
+ codebase: { map: ".harness/codebase/map", status: "missing" }
1998
+ };
1999
+ }
2000
+ record.skill_bundle = {
2001
+ registry_version: bundleVersion,
2002
+ bundle_hash: bundleManifestHash
2003
+ };
2004
+ return {
2005
+ operation: existing === "" ? "add" : "modify",
2006
+ path: CONTEXT_INDEX_PATH,
2007
+ content: JSON.stringify(record, null, 2) + "\n"
2008
+ };
2009
+ }
2010
+ async function profileTransitionOperation(root, previousProfile, profile) {
2011
+ if (previousProfile === null || previousProfile === profile)
2012
+ return null;
2013
+ const path = ".harness/project.yaml";
2014
+ const content = await readOptionalText(join6(root, path));
2015
+ const project = projectConfigSchema.parse(parseYaml3(content));
2016
+ return {
2017
+ operation: "modify",
2018
+ path,
2019
+ content: stringifyYaml2({
2020
+ ...project,
2021
+ project: { ...project.project, profiles: [profile] }
2022
+ }, { sortMapEntries: true })
2023
+ };
2024
+ }
2025
+ async function refreshProject(options) {
2026
+ const root = resolve4(options.projectRoot);
2027
+ const profile = options.profile;
2028
+ const newManaged = await managedTargets(options.resourcesRoot, profile);
2029
+ const newBundle = await loadProfileBundle(options.resourcesRoot, profile);
2030
+ const bundleManifestHash = sha256Bytes(canonicalJson(newBundle.manifest.files));
2031
+ const installed = await readInstalledState(root);
2032
+ const previousProfile = installed.profile;
2033
+ let trusted = installed.trusted;
2034
+ let migrationOldPaths = null;
2035
+ if (installed.schemaVersion === 1) {
2036
+ const contextHash = await readContextIndexBundleHash(root);
2037
+ if (contextHash !== null) {
2038
+ const migrations = await loadMigrationManifests(options.resourcesRoot);
2039
+ const match = migrations.find((m) => m.bundle_manifest_hash === contextHash && m.profile === installed.profile);
2040
+ if (match !== void 0) {
2041
+ trusted = new Map(match.projection.map((entry) => [entry.target_path, entry.sha256]));
2042
+ migrationOldPaths = new Set(match.projection.map((entry) => entry.target_path));
2043
+ }
2044
+ }
2045
+ }
2046
+ const newTargetSet = new Set(newManaged.map((target) => target.target_path));
2047
+ let oldOnly = [];
2048
+ if (migrationOldPaths !== null) {
2049
+ for (const targetPath of migrationOldPaths) {
2050
+ if (!newTargetSet.has(targetPath)) {
2051
+ oldOnly.push({
2052
+ source_path: targetPath,
2053
+ target_path: targetPath,
2054
+ sha256: trusted.get(targetPath) ?? "",
2055
+ bytes: new Uint8Array()
2056
+ });
2057
+ }
2058
+ }
2059
+ } else if (previousProfile !== null && previousProfile !== profile) {
2060
+ const oldManaged = await managedTargets(options.resourcesRoot, previousProfile);
2061
+ oldOnly = oldManaged.filter((target) => !newTargetSet.has(target.target_path));
2062
+ }
2063
+ const applied = [];
2064
+ const removed = [];
2065
+ const preserved = [];
2066
+ const unchanged = [];
2067
+ const conflicts = [];
2068
+ const ops = [];
2069
+ const newStateFiles = [];
2070
+ for (const target of newManaged) {
2071
+ const incoming = target.sha256;
2072
+ const current = await fileHex(join6(root, target.target_path));
2073
+ if (current === null) {
2074
+ applied.push(item(target, "add", "MISSING_TARGET", null, incoming));
2075
+ ops.push({ operation: "add", path: target.target_path, content: target.bytes });
2076
+ newStateFiles.push({ source_path: target.source_path, target_path: target.target_path, sha256: incoming });
2077
+ continue;
2078
+ }
2079
+ if (current === incoming) {
2080
+ unchanged.push(item(target, "unchanged", "ALREADY_CURRENT", current, incoming));
2081
+ newStateFiles.push({ source_path: target.source_path, target_path: target.target_path, sha256: incoming });
2082
+ continue;
2083
+ }
2084
+ const trustedHash = trusted.get(target.target_path);
2085
+ if (trustedHash !== void 0 && current === trustedHash || options.forceManaged) {
2086
+ const reason = options.forceManaged ? "FORCE_MANAGED" : "BASELINE_CLEAN";
2087
+ applied.push(item(target, "replace", reason, current, incoming));
2088
+ ops.push({ operation: "modify", path: target.target_path, content: target.bytes });
2089
+ newStateFiles.push({ source_path: target.source_path, target_path: target.target_path, sha256: incoming });
2090
+ } else {
2091
+ const reason = trustedHash === void 0 ? "LEGACY_BASELINE_UNKNOWN" : "LOCAL_MODIFICATION";
2092
+ preserved.push(item(target, "preserve", reason, current, incoming));
2093
+ conflicts.push(conflict(target, reason, current, incoming));
2094
+ if (trustedHash !== void 0) {
2095
+ newStateFiles.push({ source_path: target.source_path, target_path: target.target_path, sha256: trustedHash });
2096
+ }
2097
+ }
2098
+ }
2099
+ for (const target of oldOnly) {
2100
+ const current = await fileHex(join6(root, target.target_path));
2101
+ if (current === null) {
2102
+ continue;
2103
+ }
2104
+ const trustedHash = trusted.get(target.target_path);
2105
+ const clean = trustedHash !== void 0 && current === trustedHash;
2106
+ if (clean || options.forceManaged) {
2107
+ const reason = clean ? "BASELINE_CLEAN" : "FORCE_MANAGED";
2108
+ removed.push(item(target, "delete", reason, current, null));
2109
+ ops.push({ operation: "delete", path: target.target_path });
2110
+ } else {
2111
+ const reason = trustedHash === void 0 ? "LEGACY_BASELINE_UNKNOWN" : "LEGACY_PROFILE_FILE_MODIFIED";
2112
+ preserved.push(item(target, "preserve", reason, current, null));
2113
+ conflicts.push(conflict(target, reason, current, null));
2114
+ }
2115
+ }
2116
+ await refreshMarkdownBlock(root, "AGENTS.md", AGENTS_MANAGED_BLOCK_CONTENT, ops, conflicts, preserved);
2117
+ await refreshMarkdownBlock(root, "CLAUDE.md", CLAUDE_MANAGED_BLOCK_CONTENT, ops, conflicts, preserved);
2118
+ const profileOperation = await profileTransitionOperation(root, previousProfile, profile);
2119
+ if (profileOperation !== null)
2120
+ ops.push(profileOperation);
2121
+ ops.push(await reconcileContextIndex(root, newBundle.manifest.bundle_version, bundleManifestHash));
2122
+ const installedState = {
2123
+ schema_version: 2,
2124
+ profile,
2125
+ bundle_version: newBundle.manifest.bundle_version,
2126
+ bundle_manifest_hash: bundleManifestHash,
2127
+ installed_at: (/* @__PURE__ */ new Date()).toISOString(),
2128
+ files: newStateFiles.sort((left, right) => left.target_path.localeCompare(right.target_path))
2129
+ };
2130
+ ops.push({
2131
+ operation: await exists3(join6(root, INSTALLED_STATE_PATH)) ? "modify" : "add",
2132
+ path: INSTALLED_STATE_PATH,
2133
+ content: JSON.stringify(installedState, null, 2) + "\n"
2134
+ });
2135
+ if (!options.dryRun) {
2136
+ await runTransaction(root, ops, { kind: "refresh" });
2137
+ await pruneEmptyParentDirs(root, removed.map((item2) => item2.target_path));
2138
+ }
2139
+ return {
2140
+ profile,
2141
+ previous_profile: previousProfile,
2142
+ dry_run: options.dryRun,
2143
+ applied: sortByTarget(applied),
2144
+ removed: sortByTarget(removed),
2145
+ preserved: sortByTarget(preserved),
2146
+ unchanged: sortByTarget(unchanged),
2147
+ conflicts: sortByTarget(conflicts)
2148
+ };
2149
+ }
2150
+
1682
2151
  // ../core/dist/proposal/diff.js
1683
2152
  function operationPath(operation) {
1684
2153
  return operation.operation === "rename" ? operation.to_path : operation.path;
@@ -1822,7 +2291,7 @@ function highEntropyCandidates(content) {
1822
2291
  value: match[0],
1823
2292
  offset: match.index,
1824
2293
  entropy: shannonEntropy(match[0])
1825
- })).filter((item) => item.entropy >= 4.5);
2294
+ })).filter((item2) => item2.entropy >= 4.5);
1826
2295
  }
1827
2296
 
1828
2297
  // ../core/dist/security/scanner.js
@@ -1918,8 +2387,8 @@ function scanSensitiveFiles(files, options = {}) {
1918
2387
  sha256Bytes(raw.value)
1919
2388
  ].join("\0"));
1920
2389
  const overridable = raw.severity !== "high";
1921
- const explicit = options.overrides?.find((item) => item.finding_fingerprint === fingerprint);
1922
- const inline = ignores.find((item) => item.ruleId === raw.ruleId);
2390
+ const explicit = options.overrides?.find((item2) => item2.finding_fingerprint === fingerprint);
2391
+ const inline = ignores.find((item2) => item2.ruleId === raw.ruleId);
1923
2392
  const override = overridable ? explicit ?? (inline === void 0 ? void 0 : {
1924
2393
  finding_fingerprint: fingerprint,
1925
2394
  actor: "inline-annotation",
@@ -1977,28 +2446,28 @@ function generateProposalPreview(input, scanOptions = {}) {
1977
2446
  }
1978
2447
 
1979
2448
  // ../core/dist/push/push.js
1980
- import { lstat as lstat2, readFile as readFile6, readdir, rm as rm4 } from "node:fs/promises";
1981
- import { join as join7, relative, resolve as resolve4 } from "node:path";
1982
- import { parse as parseYaml3, stringify as stringifyYaml2 } from "yaml";
2449
+ import { lstat as lstat2, readFile as readFile8, readdir as readdir4, rm as rm4 } from "node:fs/promises";
2450
+ import { join as join9, relative, resolve as resolve5 } from "node:path";
2451
+ import { parse as parseYaml4, stringify as stringifyYaml3 } from "yaml";
1983
2452
 
1984
2453
  // ../core/dist/state/baseline.js
1985
- import { readFile as readFile4 } from "node:fs/promises";
1986
- import { join as join5 } from "node:path";
2454
+ import { readFile as readFile6 } from "node:fs/promises";
2455
+ import { join as join7 } from "node:path";
1987
2456
  async function readBaseline(projectRoot) {
1988
- const content = await readFile4(join5(stateLayout(projectRoot).baseline, "manifest.json"), "utf8");
2457
+ const content = await readFile6(join7(stateLayout(projectRoot).baseline, "manifest.json"), "utf8");
1989
2458
  return baselineManifestSchema.parse(JSON.parse(content));
1990
2459
  }
1991
2460
 
1992
2461
  // ../core/dist/state/locks.js
1993
2462
  import { randomUUID as randomUUID3 } from "node:crypto";
1994
- import { readFile as readFile5, rename as rename3, rm as rm3, writeFile as writeFile3 } from "node:fs/promises";
1995
- import { join as join6 } from "node:path";
2463
+ import { readFile as readFile7, rename as rename3, rm as rm3, writeFile as writeFile3 } from "node:fs/promises";
2464
+ import { join as join8 } from "node:path";
1996
2465
  async function readLock(path) {
1997
- return JSON.parse(await readFile5(path, "utf8"));
2466
+ return JSON.parse(await readFile7(path, "utf8"));
1998
2467
  }
1999
2468
  async function acquireProtocolLock(projectRoot, operation, options = {}) {
2000
2469
  const layout = await ensureStateLayout(projectRoot);
2001
- const lockPath = join6(layout.locks, "protocol.lock");
2470
+ const lockPath = join8(layout.locks, "protocol.lock");
2002
2471
  const now = options.now ?? Date.now();
2003
2472
  const staleAfterMs = options.staleAfterMs ?? 15 * 60 * 1e3;
2004
2473
  const record = {
@@ -2061,23 +2530,7 @@ var MANAGED_FILES = [
2061
2530
  ".harness/project.yaml",
2062
2531
  ".harness/context-index.json"
2063
2532
  ];
2064
- var INSTALLED_BUNDLE_PATH2 = ".harness/state/local/installed-harness-bundle.json";
2065
- async function installedBundlePaths(root) {
2066
- try {
2067
- const content = await readFile6(join7(root, INSTALLED_BUNDLE_PATH2), "utf8");
2068
- const parsed = JSON.parse(content);
2069
- if (parsed.schema_version === 1 && Array.isArray(parsed.files)) {
2070
- return new Set(parsed.files);
2071
- }
2072
- } catch (error) {
2073
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
2074
- return /* @__PURE__ */ new Set();
2075
- }
2076
- throw error;
2077
- }
2078
- return /* @__PURE__ */ new Set();
2079
- }
2080
- async function exists3(path) {
2533
+ async function exists4(path) {
2081
2534
  try {
2082
2535
  await lstat2(path);
2083
2536
  return true;
@@ -2089,43 +2542,43 @@ async function exists3(path) {
2089
2542
  }
2090
2543
  }
2091
2544
  async function walkFiles(root, current, output) {
2092
- if (!await exists3(current)) {
2545
+ if (!await exists4(current)) {
2093
2546
  return;
2094
2547
  }
2095
- for (const item of await readdir(current, { withFileTypes: true })) {
2096
- const path = join7(current, item.name);
2097
- if (item.isSymbolicLink()) {
2548
+ for (const item2 of await readdir4(current, { withFileTypes: true })) {
2549
+ const path = join9(current, item2.name);
2550
+ if (item2.isSymbolicLink()) {
2098
2551
  throw new PushWorkflowError("symlink is not pushable", 6, "UNSAFE_SYMLINK");
2099
2552
  }
2100
- if (item.isDirectory()) {
2553
+ if (item2.isDirectory()) {
2101
2554
  await walkFiles(root, path, output);
2102
- } else if (item.isFile()) {
2555
+ } else if (item2.isFile()) {
2103
2556
  output.push(normalizeManagedPath(relative(root, path).replaceAll("\\", "/")));
2104
2557
  }
2105
2558
  }
2106
2559
  }
2107
2560
  async function managedFiles(projectRoot) {
2108
- const root = resolve4(projectRoot);
2561
+ const root = resolve5(projectRoot);
2109
2562
  const paths = [];
2110
2563
  for (const path of MANAGED_FILES) {
2111
- if (await exists3(join7(root, path))) {
2564
+ if (await exists4(join9(root, path))) {
2112
2565
  paths.push(path);
2113
2566
  }
2114
2567
  }
2115
2568
  for (const path of MANAGED_ROOTS) {
2116
- await walkFiles(root, join7(root, path), paths);
2569
+ await walkFiles(root, join9(root, path), paths);
2117
2570
  }
2118
- const skillsRoot = join7(root, ".claude", "skills");
2119
- if (await exists3(skillsRoot)) {
2120
- for (const item of await readdir(skillsRoot, { withFileTypes: true })) {
2121
- if (item.isDirectory() && item.name.startsWith("harness-")) {
2122
- await walkFiles(root, join7(skillsRoot, item.name), paths);
2571
+ const skillsRoot = join9(root, ".claude", "skills");
2572
+ if (await exists4(skillsRoot)) {
2573
+ for (const item2 of await readdir4(skillsRoot, { withFileTypes: true })) {
2574
+ if (item2.isDirectory() && item2.name.startsWith("harness-")) {
2575
+ await walkFiles(root, join9(skillsRoot, item2.name), paths);
2123
2576
  }
2124
2577
  }
2125
2578
  }
2126
2579
  const result = {};
2127
2580
  for (const path of [...new Set(paths)].sort()) {
2128
- result[path] = await readFile6(join7(root, path), "utf8");
2581
+ result[path] = await readFile8(join9(root, path), "utf8");
2129
2582
  }
2130
2583
  return result;
2131
2584
  }
@@ -2134,14 +2587,14 @@ function proposalBaseline(baseline) {
2134
2587
  }
2135
2588
  async function readProject(root) {
2136
2589
  try {
2137
- return projectConfigSchema.parse(parseYaml3(await readFile6(join7(root, ".harness", "project.yaml"), "utf8")));
2590
+ return projectConfigSchema.parse(parseYaml4(await readFile8(join9(root, ".harness", "project.yaml"), "utf8")));
2138
2591
  } catch {
2139
2592
  throw new PushWorkflowError("project configuration is missing or invalid", 3, "PROJECT_CONFIG_INVALID");
2140
2593
  }
2141
2594
  }
2142
2595
  async function readOptionalJson(path) {
2143
2596
  try {
2144
- return JSON.parse(await readFile6(path, "utf8"));
2597
+ return JSON.parse(await readFile8(path, "utf8"));
2145
2598
  } catch (error) {
2146
2599
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
2147
2600
  return null;
@@ -2153,7 +2606,7 @@ async function clientIdFor(root, explicit) {
2153
2606
  if (explicit !== void 0) {
2154
2607
  return explicit;
2155
2608
  }
2156
- const path = join7(root, ".harness", "state", "local", "client.json");
2609
+ const path = join9(root, ".harness", "state", "local", "client.json");
2157
2610
  const existing = await readOptionalJson(path);
2158
2611
  if (typeof existing?.client_id === "string" && /^cli_[A-Za-z0-9_-]+$/.test(existing.client_id)) {
2159
2612
  return existing.client_id;
@@ -2225,7 +2678,7 @@ async function bindProject(root, project, baseline, projectId) {
2225
2678
  {
2226
2679
  operation: "modify",
2227
2680
  path: ".harness/project.yaml",
2228
- content: stringifyYaml2(nextProject, { sortMapEntries: true })
2681
+ content: stringifyYaml3(nextProject, { sortMapEntries: true })
2229
2682
  },
2230
2683
  {
2231
2684
  operation: "modify",
@@ -2236,10 +2689,11 @@ async function bindProject(root, project, baseline, projectId) {
2236
2689
  return { project: nextProject, baseline: nextBaseline };
2237
2690
  }
2238
2691
  async function pushProject(options) {
2239
- const root = resolve4(options.projectRoot);
2692
+ const root = resolve5(options.projectRoot);
2240
2693
  let project = await readProject(root);
2241
2694
  let baseline = await readBaseline(root);
2242
- const installedPaths = await installedBundlePaths(root);
2695
+ const profile = parseHarnessProfile(project.project.profiles[0]);
2696
+ const installedPaths = profile === null ? /* @__PURE__ */ new Set() : await managedBundleTargets(options.resourcesRoot, profile);
2243
2697
  let preview = makePreview(baseline, await managedFiles(root), options.confirmedProjectLocal ?? [], installedPaths);
2244
2698
  if (preview.blocked) {
2245
2699
  throw new PushWorkflowError("sensitive information scan blocked the proposal", 6, "SENSITIVE_CONTENT_BLOCKED");
@@ -2271,7 +2725,7 @@ async function pushProject(options) {
2271
2725
  if (parsedServerUrl.protocol !== "https:") {
2272
2726
  throw new PushWorkflowError("server_url must use HTTPS", 3, "SERVER_URL_INVALID");
2273
2727
  }
2274
- const workflowPath = join7(root, ".harness", "state", "local", "push-workflow.json");
2728
+ const workflowPath = join9(root, ".harness", "state", "local", "push-workflow.json");
2275
2729
  const priorWorkflow = await readOptionalJson(workflowPath);
2276
2730
  const provisionalRequestId = priorWorkflow?.local_project_key === project.project.local_project_key ? priorWorkflow.request_id : uuidV7();
2277
2731
  const lock = await acquireProtocolLock(root, "push", { requestId: provisionalRequestId });
@@ -2362,7 +2816,7 @@ async function pushProject(options) {
2362
2816
  }
2363
2817
  }
2364
2818
  const finalized = await client.finalizeProposal(session.session_id, { schema_version: 1, manifest_sha256: proposalManifestHash }, requestId, workflow.finalize_idempotency_key);
2365
- await atomicWriteJson(join7(root, ".harness", "state", "local", "push-results", finalized.proposal_id + ".json"), {
2819
+ await atomicWriteJson(join9(root, ".harness", "state", "local", "push-results", finalized.proposal_id + ".json"), {
2366
2820
  schema_version: 1,
2367
2821
  request_id: requestId,
2368
2822
  project_id: projectId,
@@ -2387,12 +2841,75 @@ async function pushProject(options) {
2387
2841
  }
2388
2842
  }
2389
2843
 
2844
+ // ../core/dist/state/cleanup.js
2845
+ import { readFile as readFile9, readdir as readdir5, rm as rm5 } from "node:fs/promises";
2846
+ import { join as join10 } from "node:path";
2847
+ function isSafeEntryName(name) {
2848
+ return name.length > 0 && !name.includes("/") && !name.includes("\\") && name !== "." && name !== ".." && !name.includes("\0");
2849
+ }
2850
+ async function listDir(path) {
2851
+ try {
2852
+ return await readdir5(path);
2853
+ } catch (error) {
2854
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
2855
+ return [];
2856
+ }
2857
+ throw error;
2858
+ }
2859
+ }
2860
+ async function cleanupProject(options) {
2861
+ const layout = stateLayout(options.projectRoot);
2862
+ const pruned = [];
2863
+ const removedCache = [];
2864
+ const committedByKind = /* @__PURE__ */ new Map();
2865
+ for (const name of await listDir(layout.transactions)) {
2866
+ if (!isSafeEntryName(name))
2867
+ continue;
2868
+ let journal;
2869
+ try {
2870
+ journal = JSON.parse(await readFile9(join10(layout.transactions, name, "journal.json"), "utf8"));
2871
+ } catch {
2872
+ continue;
2873
+ }
2874
+ if (journal.state === "committed" && typeof journal.kind === "string") {
2875
+ const arr = committedByKind.get(journal.kind) ?? [];
2876
+ arr.push({ id: name, createdAt: typeof journal.created_at === "string" ? journal.created_at : "" });
2877
+ committedByKind.set(journal.kind, arr);
2878
+ }
2879
+ }
2880
+ for (const arr of committedByKind.values()) {
2881
+ arr.sort((left, right) => right.createdAt.localeCompare(left.createdAt));
2882
+ for (let index = 1; index < arr.length; index += 1) {
2883
+ const entry = arr[index];
2884
+ if (entry === void 0)
2885
+ continue;
2886
+ pruned.push(entry.id);
2887
+ if (!options.dryRun) {
2888
+ await rm5(join10(layout.transactions, entry.id), { recursive: true, force: true });
2889
+ }
2890
+ }
2891
+ }
2892
+ for (const name of await listDir(layout.serverArtifacts)) {
2893
+ if (!isSafeEntryName(name))
2894
+ continue;
2895
+ removedCache.push(name);
2896
+ if (!options.dryRun) {
2897
+ await rm5(join10(layout.serverArtifacts, name), { recursive: true, force: true });
2898
+ }
2899
+ }
2900
+ return {
2901
+ dry_run: options.dryRun,
2902
+ pruned_transactions: pruned.sort(),
2903
+ removed_cache: removedCache.sort()
2904
+ };
2905
+ }
2906
+
2390
2907
  // ../core/dist/skill/frontmatter.js
2391
- import { parse as parseYaml4 } from "yaml";
2908
+ import { parse as parseYaml5 } from "yaml";
2392
2909
  import { z as z12 } from "zod";
2393
2910
 
2394
2911
  // ../core/dist/skill/fixer.js
2395
- import { parse as parseYaml5, stringify as stringifyYaml3 } from "yaml";
2912
+ import { parse as parseYaml6, stringify as stringifyYaml4 } from "yaml";
2396
2913
 
2397
2914
  // ../core/dist/skill/agents.js
2398
2915
  var AGENT_DESCRIPTORS = {
@@ -2431,10 +2948,10 @@ var AGENT_DESCRIPTORS = {
2431
2948
  var INSTALLABLE_AGENTS = Object.keys(AGENT_DESCRIPTORS).filter((agent) => AGENT_DESCRIPTORS[agent]?.installable === true);
2432
2949
 
2433
2950
  // ../core/dist/transaction/recovery.js
2434
- import { readFile as readFile7, readdir as readdir2, rm as rm5, stat as stat3 } from "node:fs/promises";
2435
- import { join as join8 } from "node:path";
2951
+ import { readFile as readFile10, readdir as readdir6, rm as rm6, stat as stat4 } from "node:fs/promises";
2952
+ import { join as join11 } from "node:path";
2436
2953
  async function recoverTransaction(projectRoot, transactionId) {
2437
- const journal = JSON.parse(await readFile7(join8(stateLayout(projectRoot).transactions, transactionId, "journal.json"), "utf8"));
2954
+ const journal = JSON.parse(await readFile10(join11(stateLayout(projectRoot).transactions, transactionId, "journal.json"), "utf8"));
2438
2955
  if (journal.state === "committed") {
2439
2956
  return { transactionId, status: "committed" };
2440
2957
  }
@@ -2451,7 +2968,7 @@ async function listTransactions(projectRoot) {
2451
2968
  const root = stateLayout(projectRoot).transactions;
2452
2969
  let names;
2453
2970
  try {
2454
- names = await readdir2(root);
2971
+ names = await readdir6(root);
2455
2972
  } catch (error) {
2456
2973
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
2457
2974
  return [];
@@ -2461,7 +2978,7 @@ async function listTransactions(projectRoot) {
2461
2978
  const transactions = [];
2462
2979
  for (const transactionId of names) {
2463
2980
  try {
2464
- const journal = JSON.parse(await readFile7(join8(root, transactionId, "journal.json"), "utf8"));
2981
+ const journal = JSON.parse(await readFile10(join11(root, transactionId, "journal.json"), "utf8"));
2465
2982
  transactions.push({
2466
2983
  transactionId,
2467
2984
  kind: journal.kind,
@@ -2474,11 +2991,11 @@ async function listTransactions(projectRoot) {
2474
2991
  return transactions.sort((left, right) => right.createdAt.localeCompare(left.createdAt));
2475
2992
  }
2476
2993
  async function pendingTransactions(projectRoot) {
2477
- return (await listTransactions(projectRoot)).filter((item) => RECOVERY_STATES.has(item.state));
2994
+ return (await listTransactions(projectRoot)).filter((item2) => RECOVERY_STATES.has(item2.state));
2478
2995
  }
2479
2996
  async function pathExists(path) {
2480
2997
  try {
2481
- await stat3(path);
2998
+ await stat4(path);
2482
2999
  return true;
2483
3000
  } catch (error) {
2484
3001
  if (error instanceof Error && "code" in error && error.code === "ENOENT") {
@@ -2488,17 +3005,17 @@ async function pathExists(path) {
2488
3005
  }
2489
3006
  }
2490
3007
  async function rollbackLatestCommittedUpdate(projectRoot) {
2491
- const latest = (await listTransactions(projectRoot)).find((item) => item.kind === "update" && item.state === "committed");
3008
+ const latest = (await listTransactions(projectRoot)).find((item2) => item2.kind === "update" && item2.state === "committed");
2492
3009
  if (latest === void 0) {
2493
3010
  throw new Error("no committed update transaction is available for rollback");
2494
3011
  }
2495
- const transactionRoot = join8(stateLayout(projectRoot).transactions, latest.transactionId);
2496
- const journal = JSON.parse(await readFile7(join8(transactionRoot, "journal.json"), "utf8"));
2497
- const after = JSON.parse(await readFile7(join8(transactionRoot, "after", "manifest.json"), "utf8"));
3012
+ const transactionRoot = join11(stateLayout(projectRoot).transactions, latest.transactionId);
3013
+ const journal = JSON.parse(await readFile10(join11(transactionRoot, "journal.json"), "utf8"));
3014
+ const after = JSON.parse(await readFile10(join11(transactionRoot, "after", "manifest.json"), "utf8"));
2498
3015
  for (const entry of after) {
2499
- const target = join8(projectRoot, entry.path);
2500
- const exists5 = await pathExists(target);
2501
- if (exists5 !== entry.exists || exists5 && await sha256File(target) !== entry.hash) {
3016
+ const target = join11(projectRoot, entry.path);
3017
+ const exists6 = await pathExists(target);
3018
+ if (exists6 !== entry.exists || exists6 && await sha256File(target) !== entry.hash) {
2502
3019
  throw new Error("cannot rollback dirty path: " + entry.path);
2503
3020
  }
2504
3021
  }
@@ -2509,16 +3026,16 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
2509
3026
  continue;
2510
3027
  }
2511
3028
  seen.add(snapshot.path);
2512
- const target = join8(projectRoot, snapshot.path);
2513
- const exists5 = await pathExists(target);
3029
+ const target = join11(projectRoot, snapshot.path);
3030
+ const exists6 = await pathExists(target);
2514
3031
  if (snapshot.existed && snapshot.snapshot_name !== null) {
2515
- const content = await readFile7(join8(transactionRoot, "before", snapshot.snapshot_name));
3032
+ const content = await readFile10(join11(transactionRoot, "before", snapshot.snapshot_name));
2516
3033
  operations.push({
2517
- operation: exists5 ? "modify" : "add",
3034
+ operation: exists6 ? "modify" : "add",
2518
3035
  path: snapshot.path,
2519
3036
  content
2520
3037
  });
2521
- } else if (exists5) {
3038
+ } else if (exists6) {
2522
3039
  operations.push({ operation: "delete", path: snapshot.path });
2523
3040
  }
2524
3041
  }
@@ -2529,23 +3046,23 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
2529
3046
  }
2530
3047
  async function cleanupOldTransactions(projectRoot, now = /* @__PURE__ */ new Date()) {
2531
3048
  const transactions = await listTransactions(projectRoot);
2532
- const committedUpdates = transactions.filter((item) => item.kind === "update" && item.state === "committed");
2533
- const keepCommitted = new Set(committedUpdates.slice(0, 10).map((item) => item.transactionId));
3049
+ const committedUpdates = transactions.filter((item2) => item2.kind === "update" && item2.state === "committed");
3050
+ const keepCommitted = new Set(committedUpdates.slice(0, 10).map((item2) => item2.transactionId));
2534
3051
  const removed = [];
2535
- for (const item of transactions) {
2536
- if (RECOVERY_STATES.has(item.state) || keepCommitted.has(item.transactionId)) {
3052
+ for (const item2 of transactions) {
3053
+ if (RECOVERY_STATES.has(item2.state) || keepCommitted.has(item2.transactionId)) {
2537
3054
  continue;
2538
3055
  }
2539
- const ageDays = (now.getTime() - Date.parse(item.createdAt)) / 864e5;
2540
- const removable = item.state === "rolled_back" ? ageDays > 7 : item.state === "committed" && ageDays > 30;
3056
+ const ageDays = (now.getTime() - Date.parse(item2.createdAt)) / 864e5;
3057
+ const removable = item2.state === "rolled_back" ? ageDays > 7 : item2.state === "committed" && ageDays > 30;
2541
3058
  if (!removable) {
2542
3059
  continue;
2543
3060
  }
2544
- await rm5(join8(stateLayout(projectRoot).transactions, item.transactionId), {
3061
+ await rm6(join11(stateLayout(projectRoot).transactions, item2.transactionId), {
2545
3062
  recursive: true,
2546
3063
  force: true
2547
3064
  });
2548
- removed.push(item.transactionId);
3065
+ removed.push(item2.transactionId);
2549
3066
  }
2550
3067
  return removed;
2551
3068
  }
@@ -2573,9 +3090,9 @@ function managedBlockDirty(currentContent, managedBlockHash) {
2573
3090
  }
2574
3091
 
2575
3092
  // ../core/dist/update/update.js
2576
- import { lstat as lstat3, readFile as readFile8, rm as rm6 } from "node:fs/promises";
2577
- import { join as join9, resolve as resolve5 } from "node:path";
2578
- import { parse as parseYaml6 } from "yaml";
3093
+ import { lstat as lstat3, readFile as readFile11, rm as rm7 } from "node:fs/promises";
3094
+ import { join as join12, resolve as resolve6 } from "node:path";
3095
+ import { parse as parseYaml7 } from "yaml";
2579
3096
  var UpdateWorkflowError = class extends Error {
2580
3097
  exitCode;
2581
3098
  code;
@@ -2598,7 +3115,7 @@ async function pathExists2(path) {
2598
3115
  }
2599
3116
  }
2600
3117
  async function optionalContent(path) {
2601
- return await pathExists2(path) ? readFile8(path, "utf8") : null;
3118
+ return await pathExists2(path) ? readFile11(path, "utf8") : null;
2602
3119
  }
2603
3120
  function manifestPayloadHash(manifest) {
2604
3121
  const payload = { ...manifest };
@@ -2616,14 +3133,14 @@ async function loadBlob(root, client, artifactId, operation, requestId, dryRun)
2616
3133
  return null;
2617
3134
  }
2618
3135
  const hash = operation.content_sha256;
2619
- const cacheRoot = join9(root, ".harness", "cache", "server-artifacts", artifactId);
2620
- const cachePath = join9(cacheRoot, "blobs", hash.replace(":", "_"));
3136
+ const cacheRoot = join12(root, ".harness", "cache", "server-artifacts", artifactId);
3137
+ const cachePath = join12(cacheRoot, "blobs", hash.replace(":", "_"));
2621
3138
  if (await pathExists2(cachePath) && await sha256File(cachePath) === hash) {
2622
- return readFile8(cachePath, "utf8");
3139
+ return readFile11(cachePath, "utf8");
2623
3140
  }
2624
3141
  const bytes = await client.downloadArtifactBlob(artifactId, hash, requestId);
2625
3142
  if (bytes.byteLength !== operation.size_bytes || sha256Bytes(bytes) !== hash) {
2626
- await rm6(cachePath, { force: true });
3143
+ await rm7(cachePath, { force: true });
2627
3144
  throw new UpdateWorkflowError("artifact blob size or hash mismatch", 4, "ARTIFACT_HASH_MISMATCH");
2628
3145
  }
2629
3146
  if (!dryRun) {
@@ -2642,34 +3159,34 @@ function nextBaselineEntry(operation, finalContent, projectVersion) {
2642
3159
  ...block === null ? {} : { managed_block_hash: sha256Bytes(block) }
2643
3160
  };
2644
3161
  }
2645
- function transactionOperation(item) {
2646
- if (item.equivalent) {
3162
+ function transactionOperation(item2) {
3163
+ if (item2.equivalent) {
2647
3164
  return null;
2648
3165
  }
2649
- const operation = item.operation;
3166
+ const operation = item2.operation;
2650
3167
  const target = operationTargetPath(operation);
2651
3168
  if (operation.operation === "delete") {
2652
- return item.finalContent === null ? { operation: "delete", path: target } : { operation: "modify", path: target, content: item.finalContent };
3169
+ return item2.finalContent === null ? { operation: "delete", path: target } : { operation: "modify", path: target, content: item2.finalContent };
2653
3170
  }
2654
3171
  if (operation.operation === "rename") {
2655
3172
  return {
2656
3173
  operation: "rename",
2657
3174
  from_path: operation.from_path,
2658
3175
  to_path: operation.to_path,
2659
- content: item.finalContent ?? item.content ?? ""
3176
+ content: item2.finalContent ?? item2.content ?? ""
2660
3177
  };
2661
3178
  }
2662
3179
  return {
2663
3180
  operation: "modify",
2664
3181
  path: target,
2665
- content: item.finalContent ?? item.content ?? ""
3182
+ content: item2.finalContent ?? item2.content ?? ""
2666
3183
  };
2667
3184
  }
2668
3185
  async function updateProject(options) {
2669
- const root = resolve5(options.projectRoot);
3186
+ const root = resolve6(options.projectRoot);
2670
3187
  let project;
2671
3188
  try {
2672
- project = projectConfigSchema.parse(parseYaml6(await readFile8(join9(root, ".harness", "project.yaml"), "utf8")));
3189
+ project = projectConfigSchema.parse(parseYaml7(await readFile11(join12(root, ".harness", "project.yaml"), "utf8")));
2673
3190
  } catch {
2674
3191
  throw new UpdateWorkflowError("project configuration is invalid", 3, "PROJECT_CONFIG_INVALID");
2675
3192
  }
@@ -2754,8 +3271,8 @@ async function updateProject(options) {
2754
3271
  skipped.push({ path: target, operation: operation.operation, reason: "baseline-diverged" });
2755
3272
  continue;
2756
3273
  }
2757
- const sourceContent = await optionalContent(join9(root, source));
2758
- const targetContent = target === source ? sourceContent : await optionalContent(join9(root, target));
3274
+ const sourceContent = await optionalContent(join12(root, source));
3275
+ const targetContent = target === source ? sourceContent : await optionalContent(join12(root, target));
2759
3276
  const incoming = blobs.get(operation) ?? null;
2760
3277
  const incomingHash = contentHash(operation);
2761
3278
  let equivalent = incomingHash !== null && targetContent !== null && sha256Bytes(targetContent) === incomingHash;
@@ -2800,7 +3317,7 @@ async function updateProject(options) {
2800
3317
  }
2801
3318
  prepared.push({ operation, content: incoming, finalContent, equivalent });
2802
3319
  }
2803
- const applied = prepared.map((item) => operationTargetPath(item.operation));
3320
+ const applied = prepared.map((item2) => operationTargetPath(item2.operation));
2804
3321
  if (options.dryRun) {
2805
3322
  return {
2806
3323
  requestId,
@@ -2814,18 +3331,18 @@ async function updateProject(options) {
2814
3331
  dryRun: true
2815
3332
  };
2816
3333
  }
2817
- await atomicWriteJson(join9(root, ".harness", "cache", "server-artifacts", manifest.artifact_id, "manifest.json"), manifest);
3334
+ await atomicWriteJson(join12(root, ".harness", "cache", "server-artifacts", manifest.artifact_id, "manifest.json"), manifest);
2818
3335
  const lock = await acquireProtocolLock(root, "update", { requestId });
2819
3336
  try {
2820
3337
  const nextBaseline = baselineManifestSchema.parse(structuredClone(baseline));
2821
- for (const item of prepared) {
2822
- const target = operationTargetPath(item.operation);
2823
- nextBaseline.files[target] = nextBaselineEntry(item.operation, item.finalContent, manifest.project_version);
2824
- if (item.operation.operation === "rename") {
2825
- nextBaseline.files[item.operation.from_path] = {
3338
+ for (const item2 of prepared) {
3339
+ const target = operationTargetPath(item2.operation);
3340
+ nextBaseline.files[target] = nextBaselineEntry(item2.operation, item2.finalContent, manifest.project_version);
3341
+ if (item2.operation.operation === "rename") {
3342
+ nextBaseline.files[item2.operation.from_path] = {
2826
3343
  baseline_hash: null,
2827
3344
  local_hash_at_apply: null,
2828
- file_kind: item.operation.file_kind,
3345
+ file_kind: item2.operation.file_kind,
2829
3346
  last_applied_version: manifest.project_version,
2830
3347
  deleted: true
2831
3348
  };
@@ -2847,7 +3364,7 @@ async function updateProject(options) {
2847
3364
  skipped,
2848
3365
  transaction_id: transactionId
2849
3366
  };
2850
- const operations = prepared.map((item) => transactionOperation(item)).filter((item) => item !== null);
3367
+ const operations = prepared.map((item2) => transactionOperation(item2)).filter((item2) => item2 !== null);
2851
3368
  operations.push({
2852
3369
  operation: "modify",
2853
3370
  path: ".harness/state/baseline/manifest.json",
@@ -2895,8 +3412,8 @@ async function updateProject(options) {
2895
3412
  }
2896
3413
 
2897
3414
  // src/config/init-config.ts
2898
- import { isAbsolute as isAbsolute2, join as join10 } from "node:path";
2899
- import { readFile as readFile9 } from "node:fs/promises";
3415
+ import { isAbsolute as isAbsolute2, join as join13 } from "node:path";
3416
+ import { readFile as readFile12 } from "node:fs/promises";
2900
3417
  var InitConfigurationError = class extends Error {
2901
3418
  exitCode;
2902
3419
  constructor(message, exitCode = 3, options) {
@@ -2914,9 +3431,9 @@ function normalizeProfile(value) {
2914
3431
  async function resolveInitConfig(cwd, flags, promptMissing) {
2915
3432
  let fileConfig = {};
2916
3433
  if (flags.config !== void 0) {
2917
- const path = isAbsolute2(flags.config) ? flags.config : join10(cwd, flags.config);
3434
+ const path = isAbsolute2(flags.config) ? flags.config : join13(cwd, flags.config);
2918
3435
  try {
2919
- fileConfig = JSON.parse(await readFile9(path, "utf8"));
3436
+ fileConfig = JSON.parse(await readFile12(path, "utf8"));
2920
3437
  } catch (error) {
2921
3438
  throw new InitConfigurationError(
2922
3439
  "unable to read init config: " + (error instanceof Error ? error.message : String(error)),
@@ -2958,8 +3475,175 @@ function serializeCliResult(result) {
2958
3475
  return JSON.stringify(result) + "\n";
2959
3476
  }
2960
3477
 
3478
+ // src/commands/refresh.ts
3479
+ import { readFile as readFile13 } from "node:fs/promises";
3480
+ import { join as join14 } from "node:path";
3481
+ import { parse as parseYaml8 } from "yaml";
3482
+ async function detectProject(root) {
3483
+ let content;
3484
+ try {
3485
+ content = await readFile13(join14(root, ".harness", "project.yaml"), "utf8");
3486
+ } catch (error) {
3487
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
3488
+ return { status: "absent" };
3489
+ }
3490
+ throw error;
3491
+ }
3492
+ const parsed = projectConfigSchema.safeParse(parseYaml8(content));
3493
+ if (!parsed.success) {
3494
+ return { status: "invalid" };
3495
+ }
3496
+ return { status: "valid", config: parsed.data };
3497
+ }
3498
+ function parseProfile(value, current) {
3499
+ if (value === void 0 || value === "") {
3500
+ return current;
3501
+ }
3502
+ if (value === "1" || value === "general") return "general";
3503
+ if (value === "2" || value === "java") return "java";
3504
+ throw new Error("profile must be general or java");
3505
+ }
3506
+ function summarize(result) {
3507
+ const items = [
3508
+ ...result.applied.map((item2) => ({ ...item2, status: result.dry_run ? "planned" : "applied" })),
3509
+ ...result.removed.map((item2) => ({ ...item2, status: result.dry_run ? "planned" : "removed" })),
3510
+ ...result.preserved.map((item2) => ({ ...item2, status: "preserved" })),
3511
+ ...result.unchanged.map((item2) => ({ ...item2, status: "unchanged" }))
3512
+ ];
3513
+ const exitCode = result.conflicts.length > 0 ? 5 : 0;
3514
+ return {
3515
+ schema_version: 1,
3516
+ command: "refresh",
3517
+ request_id: uuidV7(),
3518
+ dry_run: result.dry_run,
3519
+ ok: exitCode === 0,
3520
+ exit_code: exitCode,
3521
+ project_id: null,
3522
+ summary: {
3523
+ applied: result.dry_run ? 0 : result.applied.length,
3524
+ removed: result.dry_run ? 0 : result.removed.length,
3525
+ preserved: result.preserved.length,
3526
+ unchanged: result.unchanged.length,
3527
+ conflicts: result.conflicts.length
3528
+ },
3529
+ items,
3530
+ warnings: result.conflicts,
3531
+ errors: []
3532
+ };
3533
+ }
3534
+ function renderProfileTransitionPreview(result) {
3535
+ const items = [
3536
+ ...result.applied,
3537
+ ...result.removed,
3538
+ ...result.preserved,
3539
+ ...result.unchanged
3540
+ ];
3541
+ return "Preview profile transition:\n" + items.map((item2) => `- ${item2.action}: ${item2.target_path} (${item2.reason})`).join("\n") + "\n";
3542
+ }
3543
+ async function runRefresh(options, dependencies) {
3544
+ const requestId = uuidV7();
3545
+ const detection = await detectProject(dependencies.cwd);
3546
+ if (detection.status === "absent") {
3547
+ dependencies.stderr("Hunter Harness is not initialized; run `hunter-harness` first.\n");
3548
+ return 3;
3549
+ }
3550
+ if (detection.status === "invalid") {
3551
+ dependencies.stderr("PROJECT_CONFIG_INVALID: .harness/project.yaml is invalid\n");
3552
+ return 3;
3553
+ }
3554
+ const currentProfile = detection.config.project.profiles[0] ?? "general";
3555
+ let targetProfile;
3556
+ try {
3557
+ targetProfile = parseProfile(options.profile, currentProfile);
3558
+ } catch (error) {
3559
+ const message = error instanceof Error ? error.message : String(error);
3560
+ dependencies.stderr(message + "\n");
3561
+ return 3;
3562
+ }
3563
+ const dryRun = options.dryRun === true;
3564
+ if (targetProfile !== currentProfile && !dryRun) {
3565
+ try {
3566
+ const preview = await refreshProject({
3567
+ projectRoot: dependencies.cwd,
3568
+ resourcesRoot: dependencies.resourcesRoot,
3569
+ profile: targetProfile,
3570
+ dryRun: true,
3571
+ forceManaged: options.forceManaged === true
3572
+ });
3573
+ const rendered = renderProfileTransitionPreview(preview);
3574
+ if (options.json === true) {
3575
+ dependencies.stderr(rendered);
3576
+ } else {
3577
+ dependencies.stdout(rendered);
3578
+ }
3579
+ } catch (error) {
3580
+ const message = error instanceof Error ? error.message : String(error);
3581
+ dependencies.stderr(message + "\n");
3582
+ return 1;
3583
+ }
3584
+ }
3585
+ if (options.confirmed !== true) {
3586
+ if (options.nonInteractive === true) {
3587
+ if (!options.yes && !dryRun) {
3588
+ dependencies.stderr("non-interactive refresh requires --yes\n");
3589
+ return 2;
3590
+ }
3591
+ } else if (!options.yes && !dryRun) {
3592
+ const label = targetProfile === currentProfile ? `Refresh current profile (${currentProfile})` : `Switch profile ${currentProfile} -> ${targetProfile}`;
3593
+ const answer = await dependencies.prompt(`${label}? [y/N]: `);
3594
+ if (!/^(?:y|yes)$/i.test(answer.trim())) {
3595
+ return 2;
3596
+ }
3597
+ }
3598
+ }
3599
+ try {
3600
+ const result = await refreshProject({
3601
+ projectRoot: dependencies.cwd,
3602
+ resourcesRoot: dependencies.resourcesRoot,
3603
+ profile: targetProfile,
3604
+ dryRun,
3605
+ forceManaged: options.forceManaged === true
3606
+ });
3607
+ const output = summarize(result);
3608
+ if (options.json === true) {
3609
+ dependencies.stdout(serializeCliResult({ ...output, request_id: requestId }));
3610
+ } else {
3611
+ const parts = [];
3612
+ if (result.applied.length > 0) parts.push(`applied ${result.applied.length}`);
3613
+ if (result.removed.length > 0) parts.push(`removed ${result.removed.length}`);
3614
+ if (result.preserved.length > 0) parts.push(`preserved ${result.preserved.length}`);
3615
+ if (result.unchanged.length > 0) parts.push(`unchanged ${result.unchanged.length}`);
3616
+ dependencies.stdout(`Harness refresh (${targetProfile}): ${parts.join(", ") || "no changes"}.
3617
+ `);
3618
+ }
3619
+ return output.exit_code;
3620
+ } catch (error) {
3621
+ const message = error instanceof Error ? error.message : String(error);
3622
+ dependencies.stderr(message + "\n");
3623
+ if (options.json === true) {
3624
+ dependencies.stdout(serializeCliResult({
3625
+ schema_version: 1,
3626
+ command: "refresh",
3627
+ request_id: requestId,
3628
+ dry_run: dryRun,
3629
+ ok: false,
3630
+ exit_code: 1,
3631
+ project_id: null,
3632
+ summary: { applied: 0, removed: 0, preserved: 0, unchanged: 0, conflicts: 0 },
3633
+ items: [],
3634
+ warnings: [],
3635
+ errors: [{ message }]
3636
+ }));
3637
+ }
3638
+ return 1;
3639
+ }
3640
+ }
3641
+
2961
3642
  // src/commands/configure.ts
2962
- async function runConfigure(options, dependencies) {
3643
+ function otherProfile(current) {
3644
+ return current === "general" ? "java" : "general";
3645
+ }
3646
+ async function runFirstInstall(options, dependencies) {
2963
3647
  const requestId = uuidV7();
2964
3648
  try {
2965
3649
  const config = await resolveInitConfig(
@@ -3016,6 +3700,123 @@ async function runConfigure(options, dependencies) {
3016
3700
  return exitCode;
3017
3701
  }
3018
3702
  }
3703
+ async function runExistingProject(options, dependencies, currentProfile) {
3704
+ const refreshOptions = {};
3705
+ if (options.profile !== void 0) refreshOptions.profile = options.profile;
3706
+ if (options.nonInteractive !== void 0) refreshOptions.nonInteractive = options.nonInteractive;
3707
+ if (options.yes !== void 0) refreshOptions.yes = options.yes;
3708
+ if (options.dryRun !== void 0) refreshOptions.dryRun = options.dryRun;
3709
+ if (options.json !== void 0) refreshOptions.json = options.json;
3710
+ if (options.forceManaged !== void 0) refreshOptions.forceManaged = options.forceManaged;
3711
+ if (options.nonInteractive === true) {
3712
+ return runRefresh(refreshOptions, dependencies);
3713
+ }
3714
+ const menu = await dependencies.prompt(
3715
+ `Hunter Harness \u5DF2\u521D\u59CB\u5316\uFF08profile: ${currentProfile}\uFF09\u3002
3716
+ 1. Refresh current profile\uFF08\u9ED8\u8BA4\u4E14\u63A8\u8350\uFF09
3717
+ 2. Switch to the other profile
3718
+ 3. Cancel
3719
+ \u8BF7\u9009\u62E9 [1]: `
3720
+ );
3721
+ const choice = menu.trim();
3722
+ if (choice === "3" || /^c/i.test(choice)) {
3723
+ return 2;
3724
+ }
3725
+ if (choice === "2" || /^s/i.test(choice)) {
3726
+ refreshOptions.profile = otherProfile(currentProfile);
3727
+ } else {
3728
+ refreshOptions.profile = currentProfile;
3729
+ }
3730
+ refreshOptions.confirmed = true;
3731
+ return runRefresh(refreshOptions, dependencies);
3732
+ }
3733
+ async function runConfigure(options, dependencies) {
3734
+ const detection = await detectProject(dependencies.cwd);
3735
+ if (detection.status === "invalid") {
3736
+ dependencies.stderr("PROJECT_CONFIG_INVALID: .harness/project.yaml is invalid; not initializing over it.\n");
3737
+ if (options.json === true) {
3738
+ dependencies.stdout(serializeCliResult({
3739
+ schema_version: 1,
3740
+ command: "configure",
3741
+ request_id: uuidV7(),
3742
+ dry_run: options.dryRun === true,
3743
+ ok: false,
3744
+ exit_code: 3,
3745
+ project_id: null,
3746
+ summary: { planned: 0, applied: 0 },
3747
+ items: [],
3748
+ warnings: [],
3749
+ errors: [{ code: "PROJECT_CONFIG_INVALID", message: "project.yaml is invalid" }]
3750
+ }));
3751
+ }
3752
+ return 3;
3753
+ }
3754
+ if (detection.status === "valid") {
3755
+ const currentProfile = detection.config.project.profiles[0] ?? "general";
3756
+ return runExistingProject(options, dependencies, currentProfile);
3757
+ }
3758
+ return runFirstInstall(options, dependencies);
3759
+ }
3760
+
3761
+ // src/commands/cleanup.ts
3762
+ async function runCleanup(options, dependencies) {
3763
+ const requestId = uuidV7();
3764
+ const dryRun = options.dryRun === true;
3765
+ if (options.nonInteractive === true && options.yes !== true && !dryRun) {
3766
+ dependencies.stderr("non-interactive cleanup requires --yes\n");
3767
+ return 2;
3768
+ }
3769
+ try {
3770
+ const result = await cleanupProject({ projectRoot: dependencies.cwd, dryRun });
3771
+ const output = {
3772
+ schema_version: 1,
3773
+ command: "cleanup",
3774
+ request_id: requestId,
3775
+ dry_run: result.dry_run,
3776
+ ok: true,
3777
+ exit_code: 0,
3778
+ project_id: null,
3779
+ summary: {
3780
+ pruned_transactions: result.pruned_transactions.length,
3781
+ removed_cache: result.removed_cache.length
3782
+ },
3783
+ items: [
3784
+ ...result.pruned_transactions.map((id) => ({ path: id, status: "pruned" })),
3785
+ ...result.removed_cache.map((id) => ({ path: id, status: "removed" }))
3786
+ ],
3787
+ warnings: [],
3788
+ errors: []
3789
+ };
3790
+ if (options.json === true) {
3791
+ dependencies.stdout(serializeCliResult(output));
3792
+ } else {
3793
+ dependencies.stdout(
3794
+ `Harness cleanup: pruned ${result.pruned_transactions.length} transaction(s), removed ${result.removed_cache.length} cache entr(ies).
3795
+ `
3796
+ );
3797
+ }
3798
+ return 0;
3799
+ } catch (error) {
3800
+ const message = error instanceof Error ? error.message : String(error);
3801
+ dependencies.stderr(message + "\n");
3802
+ if (options.json === true) {
3803
+ dependencies.stdout(serializeCliResult({
3804
+ schema_version: 1,
3805
+ command: "cleanup",
3806
+ request_id: requestId,
3807
+ dry_run: dryRun,
3808
+ ok: false,
3809
+ exit_code: 1,
3810
+ project_id: null,
3811
+ summary: { pruned_transactions: 0, removed_cache: 0 },
3812
+ items: [],
3813
+ warnings: [],
3814
+ errors: [{ message }]
3815
+ }));
3816
+ }
3817
+ return 1;
3818
+ }
3819
+ }
3019
3820
 
3020
3821
  // src/commands/push.ts
3021
3822
  async function runPush(options, dependencies) {
@@ -3027,6 +3828,7 @@ async function runPush(options, dependencies) {
3027
3828
  try {
3028
3829
  const result = await pushProject({
3029
3830
  projectRoot: dependencies.cwd,
3831
+ resourcesRoot: dependencies.resourcesRoot,
3030
3832
  ...options.serverUrl === void 0 ? {} : { serverUrl: options.serverUrl },
3031
3833
  ...options.tokenEnv === void 0 ? {} : { tokenEnv: options.tokenEnv },
3032
3834
  env: dependencies.env,
@@ -3125,7 +3927,7 @@ async function runUpdate(options, dependencies) {
3125
3927
  result = await execute(options.dryRun === true);
3126
3928
  }
3127
3929
  const applied = new Set(result.applied);
3128
- const skippedByPath = new Map(result.skipped.map((item) => [item.path, item]));
3930
+ const skippedByPath = new Map(result.skipped.map((item2) => [item2.path, item2]));
3129
3931
  const items = result.operations.map((operation) => {
3130
3932
  const path = operation.operation === "rename" ? operation.to_path : operation.path;
3131
3933
  const skipped = skippedByPath.get(path);
@@ -3187,11 +3989,11 @@ async function runUpdate(options, dependencies) {
3187
3989
  }
3188
3990
 
3189
3991
  // src/commands/recovery.ts
3190
- import { stat as stat4 } from "node:fs/promises";
3191
- import { join as join11 } from "node:path";
3192
- async function exists4(path) {
3992
+ import { stat as stat5 } from "node:fs/promises";
3993
+ import { join as join15 } from "node:path";
3994
+ async function exists5(path) {
3193
3995
  try {
3194
- await stat4(path);
3996
+ await stat5(path);
3195
3997
  return true;
3196
3998
  } catch {
3197
3999
  return false;
@@ -3248,7 +4050,7 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
3248
4050
  return 5;
3249
4051
  }
3250
4052
  }
3251
- const initialized = await exists4(join11(dependencies.cwd, ".harness", "project.yaml"));
4053
+ const initialized = await exists5(join15(dependencies.cwd, ".harness", "project.yaml"));
3252
4054
  if (!initialized || options.nonInteractive === true || explicitConfigure(options)) {
3253
4055
  return null;
3254
4056
  }
@@ -3316,7 +4118,7 @@ function addCommonOptions(command) {
3316
4118
  }
3317
4119
  async function runCli(argv, overrides = {}) {
3318
4120
  const dependencies = defaultDependencies(overrides);
3319
- const program = addCommonOptions(new Command()).name("hunter-harness").description("Local-first, server-governed agent harness").option("--profile <name>").option("--config <file>").showHelpAfterError().exitOverride().configureOutput({
4121
+ const program = addCommonOptions(new Command()).name("hunter-harness").description("Local-first, server-governed agent harness").option("--profile <name>").option("--config <file>").option("--force-managed").showHelpAfterError().exitOverride().configureOutput({
3320
4122
  writeOut: dependencies.stdout,
3321
4123
  writeErr: dependencies.stderr
3322
4124
  });
@@ -3329,6 +4131,12 @@ async function runCli(argv, overrides = {}) {
3329
4131
  }
3330
4132
  exitCode = await runConfigure(options, dependencies);
3331
4133
  });
4134
+ addCommonOptions(program.command("refresh")).description("Local Conservative Refresh of an installed Harness project").option("--profile <name>").option("--force-managed").action(async (options) => {
4135
+ exitCode = await runRefresh(
4136
+ { ...program.opts(), ...options },
4137
+ dependencies
4138
+ );
4139
+ });
3332
4140
  addCommonOptions(program.command("update")).description("Apply approved server artifacts").action(async (options) => {
3333
4141
  exitCode = await runUpdate(
3334
4142
  { ...program.opts(), ...options },
@@ -3338,6 +4146,12 @@ async function runCli(argv, overrides = {}) {
3338
4146
  addCommonOptions(program.command("push")).description("Create a governed proposal").action(async (options) => {
3339
4147
  exitCode = await runPush({ ...program.opts(), ...options }, dependencies);
3340
4148
  });
4149
+ addCommonOptions(program.command("cleanup")).description("Prune completed transactions and obsolete server cache").action(async (options) => {
4150
+ exitCode = await runCleanup(
4151
+ { ...program.opts(), ...options },
4152
+ dependencies
4153
+ );
4154
+ });
3341
4155
  try {
3342
4156
  await program.parseAsync(["node", "hunter-harness", ...argv]);
3343
4157
  return exitCode;