hunter-harness 0.2.19 → 0.2.20

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 +399 -111
  2. package/package.json +36 -36
package/dist/bin.js CHANGED
@@ -164,7 +164,15 @@ var requestMetadataSchema = z2.object({
164
164
  }).strict();
165
165
 
166
166
  // ../contracts/dist/registry.js
167
- var registryAgentSchema = z3.enum(["claude-code", "codex", "cursor", "generic", "mcp"]);
167
+ var skillTargetAgentSchema = z3.enum(["claude-code", "codex", "cursor", "codebuddy"]);
168
+ var registryAgentSchema = z3.enum([
169
+ "claude-code",
170
+ "codex",
171
+ "cursor",
172
+ "codebuddy",
173
+ "generic",
174
+ "mcp"
175
+ ]);
168
176
  var registrySkillStatusSchema = z3.enum([
169
177
  "draft",
170
178
  "pending_review",
@@ -180,6 +188,7 @@ var registrySkillProposalStatusSchema = z3.enum([
180
188
  var registrySemverSchema = z3.string().regex(/^\d+\.\d+\.\d+$/);
181
189
  var registrySlugSchema = z3.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/);
182
190
  var checkStatusSchema = z3.enum(["green", "yellow", "red"]);
191
+ var skillVariantStatusSchema = z3.enum(["ready", "degraded", "unsupported"]);
183
192
  var sourceFileSchema = z3.object({
184
193
  path: z3.string(),
185
194
  content: z3.string()
@@ -204,6 +213,39 @@ var skillUsageExampleSchema = z3.object({
204
213
  result: z3.string(),
205
214
  files: z3.array(z3.string()).default([])
206
215
  }).strict();
216
+ var sensitiveFindingViewSchema = z3.object({
217
+ rule_id: z3.string().min(1),
218
+ severity: z3.enum(["high", "medium", "low"]),
219
+ path: z3.string().min(1),
220
+ line: z3.number().int().positive(),
221
+ column: z3.number().int().positive(),
222
+ fingerprint: sha256Schema,
223
+ redacted_preview: z3.string(),
224
+ overridable: z3.boolean()
225
+ }).strict();
226
+ var sensitiveReviewSubmissionSchema = z3.object({
227
+ scanner_version: z3.string().min(1),
228
+ finding_fingerprints: z3.array(sha256Schema).min(1),
229
+ reason: z3.string().trim().min(3).max(500)
230
+ }).strict();
231
+ var sensitiveReviewEvidenceSchema = sensitiveReviewSubmissionSchema.extend({
232
+ actor: z3.string().min(1),
233
+ accepted_at: z3.iso.datetime()
234
+ }).strict();
235
+ var skillVariantSchema = z3.object({
236
+ agent: skillTargetAgentSchema,
237
+ status: skillVariantStatusSchema,
238
+ buildHash: sha256Schema.nullable(),
239
+ adapterVersion: z3.string().min(1),
240
+ components: z3.array(z3.string().min(1))
241
+ }).strict();
242
+ var skillReleaseSchema = z3.object({
243
+ version: registrySemverSchema,
244
+ packageName: z3.string().min(1),
245
+ packageDigest: z3.string().min(1),
246
+ variants: z3.partialRecord(skillTargetAgentSchema, skillVariantSchema),
247
+ created_at: z3.iso.datetime()
248
+ }).strict();
207
249
  var agentSkillConfigSchema = z3.object({
208
250
  agent: registryAgentSchema,
209
251
  enabled: z3.boolean(),
@@ -250,6 +292,24 @@ var publishSkillRequestSchema = z3.object({
250
292
  version: registrySemverSchema,
251
293
  releaseNote: z3.string().optional()
252
294
  }).strict();
295
+ var publishUnifiedSkillRequestSchema = z3.object({
296
+ version: registrySemverSchema,
297
+ sourceAgent: skillTargetAgentSchema,
298
+ draftRevision: z3.number().int().positive(),
299
+ releaseNote: z3.string().trim().min(1).max(2e3).optional()
300
+ }).strict();
301
+ var publishSkillResponseSchema = z3.object({
302
+ release: z3.object({
303
+ slug: registrySlugSchema,
304
+ version: registrySemverSchema
305
+ }).strict(),
306
+ npmRelease: z3.object({
307
+ status: z3.enum(["published", "idempotent"]),
308
+ packageName: z3.string().min(1),
309
+ version: registrySemverSchema,
310
+ tarballHash: z3.string().min(1)
311
+ }).strict()
312
+ }).strict();
253
313
  var setDefaultAgentRequestSchema = z3.object({
254
314
  defaultAgent: registryAgentSchema,
255
315
  revision: z3.number().int().positive()
@@ -931,94 +991,148 @@ var semanticSearchHitSchema = z12.object({
931
991
  project_id: z12.string()
932
992
  }).strict();
933
993
 
934
- // ../contracts/dist/workflow-family.js
994
+ // ../contracts/dist/skill-package.js
935
995
  import { z as z13 } from "zod";
936
- var workflowBundleManifestSchema = z13.object({
937
- schema_version: z13.literal(1),
996
+ var skillComponentRoleSchema = z13.enum(["skill", "subagent"]);
997
+ var skillBundlePathSchema = z13.string().min(1).refine((value) => {
998
+ if (value === ".")
999
+ return true;
1000
+ return !value.includes("\0") && !value.includes("\\") && !value.startsWith("/") && !/^[A-Za-z]:/.test(value) && value.split("/").every((part) => part !== "" && part !== "." && part !== "..");
1001
+ }, "path must be a normalized relative POSIX path");
1002
+ var skillBundleComponentSchema = z13.object({
1003
+ role: skillComponentRoleSchema,
1004
+ source: skillBundlePathSchema,
1005
+ name: registrySlugSchema.optional(),
1006
+ variants: z13.partialRecord(skillTargetAgentSchema, skillBundlePathSchema).optional()
1007
+ }).strict().superRefine((component, context) => {
1008
+ if (component.role === "skill" && component.variants !== void 0) {
1009
+ context.addIssue({ code: "custom", message: "skill components cannot define agent variants" });
1010
+ }
1011
+ if (component.role === "subagent" && (component.name === void 0 || component.variants === void 0 || Object.keys(component.variants).length === 0)) {
1012
+ context.addIssue({ code: "custom", message: "subagent components require a name and variants" });
1013
+ }
1014
+ }).superRefine((component, context) => {
1015
+ if (component.role === "subagent" && component.source !== ".") {
1016
+ context.addIssue({ code: "custom", message: "subagent source must be '.'; variants select native files" });
1017
+ }
1018
+ });
1019
+ var authorSkillBundleManifestSchema = z13.object({
1020
+ apiVersion: z13.literal("hunter-harness/v1"),
1021
+ kind: z13.literal("SkillBundle"),
1022
+ components: z13.array(skillBundleComponentSchema).min(1)
1023
+ }).strict().superRefine((manifest, context) => {
1024
+ if (!manifest.components.some((component) => component.role === "skill")) {
1025
+ context.addIssue({ code: "custom", message: "bundle requires a skill component" });
1026
+ }
1027
+ });
1028
+ var skillPackageFileSchema = z13.object({
1029
+ path: skillBundlePathSchema,
1030
+ sha256: sha256Schema,
1031
+ size: z13.number().int().nonnegative()
1032
+ }).strict();
1033
+ var skillPackageVariantSchema = z13.object({
1034
+ status: skillVariantStatusSchema,
1035
+ adapterVersion: z13.string().min(1),
1036
+ buildHash: sha256Schema.nullable(),
1037
+ components: z13.array(z13.string().min(1))
1038
+ }).strict();
1039
+ var skillPackageManifestV3Schema = z13.object({
1040
+ schema_version: z13.literal(3),
1041
+ slug: registrySlugSchema,
1042
+ version: registrySemverSchema,
1043
+ files: z13.array(skillPackageFileSchema),
1044
+ components: z13.array(skillBundleComponentSchema).min(1),
1045
+ variants: z13.record(skillTargetAgentSchema, skillPackageVariantSchema)
1046
+ }).strict();
1047
+
1048
+ // ../contracts/dist/workflow-family.js
1049
+ import { z as z14 } from "zod";
1050
+ var workflowBundleManifestSchema = z14.object({
1051
+ schema_version: z14.literal(1),
938
1052
  profile: registrySlugSchema,
939
- files: z13.array(z13.object({
940
- path: z13.string().min(1),
1053
+ files: z14.array(z14.object({
1054
+ path: z14.string().min(1),
941
1055
  sha256: sha256Schema
942
1056
  }).strict()).min(1)
943
1057
  }).strict();
944
- var workflowFamilyBundleArtifactSchema = z13.object({
945
- artifact_id: z13.string().regex(/^wfb_/),
1058
+ var workflowFamilyBundleArtifactSchema = z14.object({
1059
+ artifact_id: z14.string().regex(/^wfb_/),
946
1060
  family_slug: registrySlugSchema,
947
1061
  profile: registrySlugSchema,
948
1062
  version: registrySemverSchema,
949
1063
  content_sha256: sha256Schema,
950
- size_bytes: z13.number().int().nonnegative(),
1064
+ size_bytes: z14.number().int().nonnegative(),
951
1065
  bundle_manifest: workflowBundleManifestSchema,
952
- created_at: z13.iso.datetime()
1066
+ created_at: z14.iso.datetime()
953
1067
  }).strict();
954
- var workflowFamilyVersionProfileSchema = z13.object({
1068
+ var workflowFamilyVersionProfileSchema = z14.object({
955
1069
  profile: registrySlugSchema,
956
1070
  bundle_manifest: workflowBundleManifestSchema,
957
- artifact_id: z13.string().regex(/^wfb_/),
958
- sourceFiles: z13.array(sourceFileSchema).default([])
1071
+ artifact_id: z14.string().regex(/^wfb_/),
1072
+ sourceFiles: z14.array(sourceFileSchema).default([])
959
1073
  }).strict();
960
- var workflowFamilyVersionSchema = z13.object({
1074
+ var workflowFamilyVersionSchema = z14.object({
961
1075
  family_slug: registrySlugSchema,
962
1076
  version: registrySemverSchema,
963
- profiles: z13.array(workflowFamilyVersionProfileSchema).min(1),
964
- artifacts: z13.array(workflowFamilyBundleArtifactSchema),
965
- changeNote: z13.string().nullable(),
966
- created_at: z13.iso.datetime()
1077
+ profiles: z14.array(workflowFamilyVersionProfileSchema).min(1),
1078
+ artifacts: z14.array(workflowFamilyBundleArtifactSchema),
1079
+ changeNote: z14.string().nullable(),
1080
+ created_at: z14.iso.datetime()
967
1081
  }).strict();
968
- var workflowFamilyDraftProfileSchema = z13.object({
1082
+ var workflowFamilyDraftProfileSchema = z14.object({
969
1083
  profile: registrySlugSchema,
970
- sourceFiles: z13.array(sourceFileSchema),
1084
+ sourceFiles: z14.array(sourceFileSchema),
971
1085
  bundle_manifest: workflowBundleManifestSchema
972
1086
  }).strict();
973
- var workflowFamilyDraftStateSchema = z13.object({
1087
+ var workflowFamilyDraftStateSchema = z14.object({
974
1088
  family_slug: registrySlugSchema,
975
- profiles: z13.array(workflowFamilyDraftProfileSchema),
976
- required_profiles: z13.array(registrySlugSchema),
1089
+ profiles: z14.array(workflowFamilyDraftProfileSchema),
1090
+ required_profiles: z14.array(registrySlugSchema),
977
1091
  draftVersion: registrySemverSchema.nullable(),
978
1092
  checks: skillCheckResultSchema.nullable(),
979
- releaseNote: z13.string().nullable(),
980
- revision: z13.number().int(),
981
- created_at: z13.string(),
982
- updated_at: z13.string()
1093
+ releaseNote: z14.string().nullable(),
1094
+ revision: z14.number().int(),
1095
+ created_at: z14.string(),
1096
+ updated_at: z14.string()
983
1097
  }).strict();
984
- var workflowFamilySchema = z13.object({
985
- family_id: z13.string().regex(/^wff_/),
1098
+ var workflowFamilySchema = z14.object({
1099
+ family_id: z14.string().regex(/^wff_/),
986
1100
  slug: registrySlugSchema,
987
- displayName: z13.string().min(1).max(120),
988
- description: z13.string().min(1).max(1e3),
989
- tags: z13.array(registrySlugSchema).default([]),
1101
+ displayName: z14.string().min(1).max(120),
1102
+ description: z14.string().min(1).max(1e3),
1103
+ tags: z14.array(registrySlugSchema).default([]),
990
1104
  latest_version: registrySemverSchema.nullable(),
991
- required_profiles: z13.array(registrySlugSchema).min(1),
992
- revision: z13.number().int().positive(),
993
- npmReleases: z13.array(npmReleaseRecordSchema).optional().default([]),
994
- created_at: z13.iso.datetime(),
995
- updated_at: z13.iso.datetime()
1105
+ required_profiles: z14.array(registrySlugSchema).min(1),
1106
+ revision: z14.number().int().positive(),
1107
+ npmReleases: z14.array(npmReleaseRecordSchema).optional().default([]),
1108
+ created_at: z14.iso.datetime(),
1109
+ updated_at: z14.iso.datetime()
996
1110
  }).strict();
997
- var workflowFamilyMutationSchema = z13.object({
1111
+ var workflowFamilyMutationSchema = z14.object({
998
1112
  slug: registrySlugSchema,
999
- displayName: z13.string().min(1).max(120),
1000
- description: z13.string().min(1).max(1e3),
1001
- tags: z13.array(registrySlugSchema).default([]),
1002
- required_profiles: z13.array(registrySlugSchema).min(1)
1113
+ displayName: z14.string().min(1).max(120),
1114
+ description: z14.string().min(1).max(1e3),
1115
+ tags: z14.array(registrySlugSchema).default([]),
1116
+ required_profiles: z14.array(registrySlugSchema).min(1)
1003
1117
  }).strict();
1004
- var publishWorkflowFamilyRequestSchema = z13.object({
1118
+ var publishWorkflowFamilyRequestSchema = z14.object({
1005
1119
  version: registrySemverSchema,
1006
- releaseNote: z13.string().optional()
1120
+ releaseNote: z14.string().optional()
1007
1121
  }).strict();
1008
- var registryProjectWorkflowBindingSchema = z13.object({
1009
- project_id: z13.string().regex(/^prj_/),
1122
+ var registryProjectWorkflowBindingSchema = z14.object({
1123
+ project_id: z14.string().regex(/^prj_/),
1010
1124
  family_slug: registrySlugSchema,
1011
1125
  profile: registrySlugSchema,
1012
1126
  version: registrySemverSchema.nullable().optional(),
1013
- revision: z13.number().int().positive(),
1014
- updated_at: z13.iso.datetime()
1127
+ revision: z14.number().int().positive(),
1128
+ updated_at: z14.iso.datetime()
1015
1129
  }).strict();
1016
- var bindProjectWorkflowFamilyRequestSchema = z13.object({
1017
- schema_version: z13.literal(1),
1130
+ var bindProjectWorkflowFamilyRequestSchema = z14.object({
1131
+ schema_version: z14.literal(1),
1018
1132
  family_slug: registrySlugSchema,
1019
1133
  profile: registrySlugSchema,
1020
1134
  version: registrySemverSchema.nullable().optional(),
1021
- revision: z13.number().int().nullable()
1135
+ revision: z14.number().int().nullable()
1022
1136
  }).strict();
1023
1137
 
1024
1138
  // ../core/dist/fs/hash.js
@@ -1030,6 +1144,10 @@ function sha256Bytes(content) {
1030
1144
  async function sha256File(path) {
1031
1145
  return sha256Bytes(await readFile(path));
1032
1146
  }
1147
+ function aggregateInstalledContentHash(files) {
1148
+ const lines = [...files].sort((a, b) => a.relpath.localeCompare(b.relpath)).map((entry) => `${entry.relpath}:${entry.sha256}`);
1149
+ return createHash("sha256").update(lines.join("\n"), "utf8").digest("hex");
1150
+ }
1033
1151
 
1034
1152
  // ../core/dist/fs/path-safety.js
1035
1153
  import { lstat } from "node:fs/promises";
@@ -1613,7 +1731,7 @@ function decideUpdate(policy, dirty) {
1613
1731
 
1614
1732
  // ../core/dist/project/initialize.js
1615
1733
  import { createHash as createHash4 } from "node:crypto";
1616
- import { readFile as readFile4, stat as stat2 } from "node:fs/promises";
1734
+ import { readFile as readFile4, stat as stat2, writeFile as writeFile3 } from "node:fs/promises";
1617
1735
  import { basename, join as join5, resolve as resolve3 } from "node:path";
1618
1736
  import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
1619
1737
 
@@ -2311,6 +2429,27 @@ function uuidV7(now = Date.now()) {
2311
2429
  }
2312
2430
 
2313
2431
  // ../core/dist/project/initialize.js
2432
+ var CONTEXT_INDEX_PATH = ".harness/context-index.json";
2433
+ async function fileHex(path) {
2434
+ try {
2435
+ const data = await readFile4(path);
2436
+ return createHash4("sha256").update(data).digest("hex");
2437
+ } catch {
2438
+ return null;
2439
+ }
2440
+ }
2441
+ async function readExistingSkillBundles(root) {
2442
+ const text = await readOptional(join5(root, CONTEXT_INDEX_PATH));
2443
+ if (text === "")
2444
+ return /* @__PURE__ */ new Map();
2445
+ try {
2446
+ const parsed = JSON.parse(text);
2447
+ const bundles = parsed.skill_bundles ?? {};
2448
+ return new Map(Object.entries(bundles));
2449
+ } catch {
2450
+ return /* @__PURE__ */ new Map();
2451
+ }
2452
+ }
2314
2453
  var TargetCollisionError = class extends Error {
2315
2454
  code = "TARGET_COLLISION";
2316
2455
  exitCode = 7;
@@ -2405,6 +2544,7 @@ async function initializeProject(options) {
2405
2544
  const manifests = [];
2406
2545
  const adaptersIndex = {};
2407
2546
  const skillBundles = {};
2547
+ const existingContextBundles = await readExistingSkillBundles(root);
2408
2548
  let primaryBundleHash = "";
2409
2549
  let primaryRegistryVersion = "";
2410
2550
  for (const agent of enabledAgents) {
@@ -2424,7 +2564,8 @@ async function initializeProject(options) {
2424
2564
  });
2425
2565
  skillBundles[agent] = {
2426
2566
  registry_version: bundle.manifest.bundle_version,
2427
- bundle_hash: bundleHash
2567
+ bundle_hash: bundleHash,
2568
+ ...existingContextBundles.get(agent) ?? {}
2428
2569
  };
2429
2570
  adaptersIndex[agent] = adapter.contextIndex(adapterContext);
2430
2571
  for (const target of targets) {
@@ -2541,6 +2682,7 @@ async function initializeProject(options) {
2541
2682
  if (!options.dryRun) {
2542
2683
  const writeOperations = await Promise.all([...files.entries()].map(async ([path, content]) => operationFor(root, path, content)));
2543
2684
  await runTransaction(root, writeOperations, { kind: "init" });
2685
+ await enrichContextIndexWithVerification(root, enabledAgents, profile, options.resourcesRoot, adapterContext);
2544
2686
  }
2545
2687
  return {
2546
2688
  projectConfig,
@@ -2549,6 +2691,52 @@ async function initializeProject(options) {
2549
2691
  registryVersion: primaryRegistryVersion
2550
2692
  };
2551
2693
  }
2694
+ async function enrichContextIndexWithVerification(root, agents, profile, resourcesRoot, adapterContext) {
2695
+ const contextPath = join5(root, CONTEXT_INDEX_PATH);
2696
+ const existing = await readOptional(join5(contextPath));
2697
+ if (existing === "")
2698
+ return;
2699
+ let parsed;
2700
+ try {
2701
+ parsed = JSON.parse(existing);
2702
+ } catch {
2703
+ return;
2704
+ }
2705
+ const bundles = parsed.skill_bundles ?? {};
2706
+ for (const agent of agents) {
2707
+ const entry = bundles[agent];
2708
+ if (!entry)
2709
+ continue;
2710
+ let loadedBundle;
2711
+ try {
2712
+ loadedBundle = await loadAgentBundle(resourcesRoot, profile, agent);
2713
+ } catch {
2714
+ continue;
2715
+ }
2716
+ const targets = managedTargetsFor(getAdapter(agent), loadedBundle, adapterContext);
2717
+ const mismatches = [];
2718
+ const entries = [];
2719
+ for (const target of targets) {
2720
+ const rel = target.target_path.replace(/\\/g, "/");
2721
+ const actual = await fileHex(join5(root, target.target_path));
2722
+ entries.push({ relpath: rel, sha256: actual ?? "" });
2723
+ if (actual === null) {
2724
+ mismatches.push({ relpath: rel, expected: target.sha256, actual: "<missing>" });
2725
+ } else if (actual !== target.sha256) {
2726
+ mismatches.push({ relpath: rel, expected: target.sha256, actual });
2727
+ }
2728
+ }
2729
+ entry.installedContentHash = aggregateInstalledContentHash(entries);
2730
+ const newStatus = mismatches.length === 0 ? "verified" : "degraded";
2731
+ if (newStatus === "verified" && typeof entry.verifiedAt === "string" && entry.verifiedAt !== "") {
2732
+ } else {
2733
+ entry.verifiedAt = (/* @__PURE__ */ new Date()).toISOString();
2734
+ }
2735
+ entry.verificationStatus = newStatus;
2736
+ entry.mismatchDetails = mismatches;
2737
+ }
2738
+ await writeFile3(contextPath, JSON.stringify(parsed, null, 2) + "\n", "utf8");
2739
+ }
2552
2740
 
2553
2741
  // ../core/dist/project/refresh.js
2554
2742
  import { createHash as createHash5 } from "node:crypto";
@@ -2556,8 +2744,8 @@ import { readFile as readFile5, readdir as readdir3, rmdir } from "node:fs/promi
2556
2744
  import { dirname as dirname3, join as join6, resolve as resolve4 } from "node:path";
2557
2745
  import { parse as parseYaml3, stringify as stringifyYaml2 } from "yaml";
2558
2746
  var INSTALLED_STATE_PATH = ".harness/state/local/installed-harness-bundle.json";
2559
- var CONTEXT_INDEX_PATH = ".harness/context-index.json";
2560
- async function fileHex(path) {
2747
+ var CONTEXT_INDEX_PATH2 = ".harness/context-index.json";
2748
+ async function fileHex2(path) {
2561
2749
  try {
2562
2750
  return createHash5("sha256").update(await readFile5(path)).digest("hex");
2563
2751
  } catch (error) {
@@ -2658,7 +2846,7 @@ async function readInstalledAgentConfiguration(projectRoot) {
2658
2846
  };
2659
2847
  }
2660
2848
  async function readContextIndexBundleHash(root) {
2661
- const content = await readOptionalText(join6(root, CONTEXT_INDEX_PATH));
2849
+ const content = await readOptionalText(join6(root, CONTEXT_INDEX_PATH2));
2662
2850
  if (content === "")
2663
2851
  return null;
2664
2852
  try {
@@ -2713,8 +2901,8 @@ function conflict(target, reason, oldSha, incomingSha) {
2713
2901
  function sortByTarget(items) {
2714
2902
  return [...items].sort((left, right) => left.target_path.localeCompare(right.target_path));
2715
2903
  }
2716
- async function reconcileContextIndex(root, agents, profiles, manifests, codebuddySurface2) {
2717
- const existing = await readOptionalText(join6(root, CONTEXT_INDEX_PATH));
2904
+ async function reconcileContextIndex(root, agents, profiles, manifests, codebuddySurface2, verifications) {
2905
+ const existing = await readOptionalText(join6(root, CONTEXT_INDEX_PATH2));
2718
2906
  let codebase = {
2719
2907
  map: ".harness/codebase/map",
2720
2908
  status: "missing"
@@ -2741,17 +2929,27 @@ async function reconcileContextIndex(root, agents, profiles, manifests, codebudd
2741
2929
  },
2742
2930
  knowledge: { index: ".harness/knowledge/index.json" },
2743
2931
  codebase,
2744
- skill_bundles: Object.fromEntries(manifests.map((manifest) => [
2745
- manifest.adapter,
2746
- { registry_version: manifest.bundle_version, bundle_hash: manifest.bundle_manifest_hash }
2747
- ]))
2932
+ skill_bundles: Object.fromEntries(manifests.map((manifest) => {
2933
+ const ver = verifications.get(manifest.adapter);
2934
+ return [
2935
+ manifest.adapter,
2936
+ {
2937
+ registry_version: manifest.bundle_version,
2938
+ bundle_hash: manifest.bundle_manifest_hash,
2939
+ installedContentHash: ver?.installedContentHash ?? null,
2940
+ verifiedAt: ver?.verifiedAt ?? null,
2941
+ verificationStatus: ver?.verificationStatus ?? "unknown",
2942
+ mismatchDetails: ver?.mismatchDetails ?? []
2943
+ }
2944
+ ];
2945
+ }))
2748
2946
  };
2749
2947
  const next = JSON.stringify(record, null, 2) + "\n";
2750
2948
  if (existing === next)
2751
2949
  return null;
2752
2950
  return {
2753
2951
  operation: existing === "" ? "add" : "modify",
2754
- path: CONTEXT_INDEX_PATH,
2952
+ path: CONTEXT_INDEX_PATH2,
2755
2953
  content: next
2756
2954
  };
2757
2955
  }
@@ -2915,7 +3113,7 @@ async function refreshProject(options) {
2915
3113
  const newStateFiles = installed.files.filter((entry) => entry.owner === "shared" || !selectedSet.has(entry.owner));
2916
3114
  for (const target of newManaged) {
2917
3115
  const incoming = target.sha256;
2918
- const current = await fileHex(join6(root, target.target_path));
3116
+ const current = await fileHex2(join6(root, target.target_path));
2919
3117
  if (current === null) {
2920
3118
  applied.push(item(target, "add", "MISSING_TARGET", null, incoming));
2921
3119
  ops.push({ operation: "add", path: target.target_path, content: target.bytes });
@@ -2943,7 +3141,7 @@ async function refreshProject(options) {
2943
3141
  }
2944
3142
  }
2945
3143
  for (const target of oldOnly) {
2946
- const current = await fileHex(join6(root, target.target_path));
3144
+ const current = await fileHex2(join6(root, target.target_path));
2947
3145
  if (current === null) {
2948
3146
  continue;
2949
3147
  }
@@ -2969,7 +3167,48 @@ async function refreshProject(options) {
2969
3167
  const projectOperation = await projectTransitionOperation(root, agents, profiles, codebuddySurface2);
2970
3168
  if (projectOperation !== null)
2971
3169
  ops.push(projectOperation);
2972
- const contextOperation = await reconcileContextIndex(root, agents, profiles, manifests, codebuddySurface2);
3170
+ const verifications = /* @__PURE__ */ new Map();
3171
+ for (const agent of selectedAgents) {
3172
+ const agentProfile = profiles.get(agent) ?? profile;
3173
+ let bundleForVerify;
3174
+ try {
3175
+ bundleForVerify = await loadAgentBundle(options.resourcesRoot, agentProfile, agent);
3176
+ } catch {
3177
+ continue;
3178
+ }
3179
+ const verifyTargets = managedTargetsFor(getAdapter(agent), bundleForVerify, {
3180
+ profile: agentProfile,
3181
+ codebuddySurface: codebuddySurface2
3182
+ });
3183
+ const verifyMismatches = [];
3184
+ const verifyEntries = [];
3185
+ for (const target of verifyTargets) {
3186
+ const rel = target.target_path.replace(/\\/g, "/");
3187
+ const actual = await fileHex2(join6(root, target.target_path));
3188
+ verifyEntries.push({ relpath: rel, sha256: actual ?? "" });
3189
+ if (actual === null) {
3190
+ verifyMismatches.push({ relpath: rel, expected: target.sha256, actual: "<missing>" });
3191
+ } else if (actual !== target.sha256) {
3192
+ verifyMismatches.push({ relpath: rel, expected: target.sha256, actual });
3193
+ }
3194
+ }
3195
+ verifications.set(agent, {
3196
+ adapter: agent,
3197
+ bundleVersion: bundleForVerify.manifest.bundle_version,
3198
+ installedBundleVersion: null,
3199
+ manifestHash: null,
3200
+ installedManifestHash: null,
3201
+ coreHash: null,
3202
+ installedCoreHash: null,
3203
+ adapterHash: null,
3204
+ installedAdapterHash: null,
3205
+ installedContentHash: aggregateInstalledContentHash(verifyEntries),
3206
+ verifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
3207
+ verificationStatus: verifyMismatches.length === 0 ? "verified" : "degraded",
3208
+ mismatchDetails: verifyMismatches
3209
+ });
3210
+ }
3211
+ const contextOperation = await reconcileContextIndex(root, agents, profiles, manifests, codebuddySurface2, verifications);
2973
3212
  if (contextOperation !== null)
2974
3213
  ops.push(contextOperation);
2975
3214
  const managedBlocks = [
@@ -3040,6 +3279,14 @@ async function refreshProject(options) {
3040
3279
  function freshnessEntry(agent, profile, status, identity) {
3041
3280
  return { agent, profile, status, identity, driftedFiles: [], missingFiles: [] };
3042
3281
  }
3282
+ function emptyVerification() {
3283
+ return {
3284
+ installedContentHash: null,
3285
+ verifiedAt: null,
3286
+ verificationStatus: "unknown",
3287
+ mismatchDetails: []
3288
+ };
3289
+ }
3043
3290
  var BUILD_MARKER_BUNDLE_PATH = ".harness-build.json";
3044
3291
  function buildMarkerCoreHash(text) {
3045
3292
  if (text === null || text === "")
@@ -3069,7 +3316,8 @@ async function collectFreshness(options) {
3069
3316
  coreHash: null,
3070
3317
  installedCoreHash: null,
3071
3318
  adapterHash: null,
3072
- installedAdapterHash: null
3319
+ installedAdapterHash: null,
3320
+ ...emptyVerification()
3073
3321
  };
3074
3322
  let officialHash = null;
3075
3323
  let officialVersion = null;
@@ -3096,13 +3344,29 @@ async function collectFreshness(options) {
3096
3344
  identity.adapterHash = sha256Bytes(canonicalJson(targets.map((target) => ({ path: target.target_path.replace(/\\/g, "/"), sha256: target.sha256 })).sort((a, b) => a.path.localeCompare(b.path))));
3097
3345
  const installedProjection = await Promise.all(targets.map(async (target) => ({
3098
3346
  path: target.target_path.replace(/\\/g, "/"),
3099
- sha256: await fileHex(join6(root, target.target_path))
3347
+ sha256: await fileHex2(join6(root, target.target_path))
3100
3348
  })));
3101
3349
  identity.installedAdapterHash = sha256Bytes(canonicalJson(installedProjection.sort((a, b) => a.path.localeCompare(b.path))));
3102
3350
  const markerTarget = targets.find((target) => target.target_path.replace(/\\/g, "/").endsWith(`/${BUILD_MARKER_BUNDLE_PATH}`) || target.target_path === BUILD_MARKER_BUNDLE_PATH);
3103
3351
  if (markerTarget !== void 0) {
3104
3352
  identity.installedCoreHash = buildMarkerCoreHash(await readOptionalText(join6(root, markerTarget.target_path)));
3105
3353
  }
3354
+ const mismatchDetails = [];
3355
+ const contentEntries = [];
3356
+ for (const target of targets) {
3357
+ const rel = target.target_path.replace(/\\/g, "/");
3358
+ const actual = await fileHex2(join6(root, target.target_path));
3359
+ contentEntries.push({ relpath: rel, sha256: actual ?? "" });
3360
+ if (actual === null) {
3361
+ mismatchDetails.push({ relpath: rel, expected: target.sha256, actual: "<missing>" });
3362
+ } else if (actual !== target.sha256) {
3363
+ mismatchDetails.push({ relpath: rel, expected: target.sha256, actual });
3364
+ }
3365
+ }
3366
+ identity.installedContentHash = aggregateInstalledContentHash(contentEntries);
3367
+ identity.verifiedAt = (/* @__PURE__ */ new Date()).toISOString();
3368
+ identity.verificationStatus = mismatchDetails.length === 0 ? "verified" : "degraded";
3369
+ identity.mismatchDetails = mismatchDetails;
3106
3370
  if (requestedProfile !== installedProfile) {
3107
3371
  agents.push(freshnessEntry(agent, installedProfile, "PROFILE_MISMATCH", identity));
3108
3372
  continue;
@@ -3114,7 +3378,7 @@ async function collectFreshness(options) {
3114
3378
  const drifted = [];
3115
3379
  const missing = [];
3116
3380
  for (const target of targets) {
3117
- const current = await fileHex(join6(root, target.target_path));
3381
+ const current = await fileHex2(join6(root, target.target_path));
3118
3382
  if (current === null) {
3119
3383
  missing.push(target.target_path);
3120
3384
  } else if (current !== target.sha256) {
@@ -3304,7 +3568,7 @@ function highEntropyCandidates(content) {
3304
3568
  }
3305
3569
 
3306
3570
  // ../core/dist/security/scanner.js
3307
- var SENSITIVE_SCANNER_VERSION = "1.0.0";
3571
+ var SENSITIVE_SCANNER_VERSION = "1.1.0";
3308
3572
  var RULES = [
3309
3573
  {
3310
3574
  id: "HH_PRIVATE_KEY",
@@ -3359,6 +3623,9 @@ function rawFindings(content) {
3359
3623
  for (const rule of RULES) {
3360
3624
  for (const match of content.matchAll(rule.pattern)) {
3361
3625
  const value = match[rule.valueGroup ?? 0] ?? match[0];
3626
+ if (rule.id === "HH_PASSWORD_VALUE" && isObviousPlaceholder(value)) {
3627
+ continue;
3628
+ }
3362
3629
  const relative3 = match[0].indexOf(value);
3363
3630
  findings.push({
3364
3631
  ruleId: rule.id,
@@ -3369,6 +3636,8 @@ function rawFindings(content) {
3369
3636
  }
3370
3637
  }
3371
3638
  for (const candidate of highEntropyCandidates(content)) {
3639
+ if (!hasSensitiveLexicalContext(content, candidate.offset))
3640
+ continue;
3372
3641
  findings.push({
3373
3642
  ruleId: "HH_HIGH_ENTROPY",
3374
3643
  severity: "high",
@@ -3378,6 +3647,17 @@ function rawFindings(content) {
3378
3647
  }
3379
3648
  return findings;
3380
3649
  }
3650
+ function isObviousPlaceholder(value) {
3651
+ const trimmed = value.trim();
3652
+ return /^\$\([^)]+\)$/.test(trimmed) || /^\$\{[A-Za-z_][A-Za-z0-9_]*\}$/.test(trimmed) || /^\{\{[^{}]+\}\}$/.test(trimmed) || /^<[^<>]+>$/.test(trimmed) || /^(?:example|placeholder|your[-_][a-z0-9_-]+)$/i.test(trimmed);
3653
+ }
3654
+ function hasSensitiveLexicalContext(content, offset) {
3655
+ const lineStart = content.lastIndexOf("\n", Math.max(0, offset - 1)) + 1;
3656
+ const nextLineBreak = content.indexOf("\n", offset);
3657
+ const lineEnd = nextLineBreak === -1 ? content.length : nextLineBreak;
3658
+ const line = content.slice(lineStart, lineEnd);
3659
+ return /(?:secret|token|password|passwd|pwd|api[_-]?key|credential|authorization|bearer)\s*[:=]\s*["']?[A-Za-z0-9._~+/-]{8,}/i.test(line);
3660
+ }
3381
3661
  function scanSensitiveFiles(files, options = {}) {
3382
3662
  const findings = [];
3383
3663
  const evidence = [];
@@ -3436,6 +3716,8 @@ function scanSensitiveFiles(files, options = {}) {
3436
3716
  return {
3437
3717
  scanner_version: SENSITIVE_SCANNER_VERSION,
3438
3718
  blocked: findings.some((finding) => finding.disposition === "blocked"),
3719
+ hard_blocked: findings.some((finding) => finding.disposition === "blocked" && finding.severity === "high"),
3720
+ review_required: findings.some((finding) => finding.disposition === "blocked" && finding.severity !== "high"),
3439
3721
  findings,
3440
3722
  override_evidence: Object.freeze(evidence)
3441
3723
  };
@@ -3460,7 +3742,7 @@ function generateProposalPreview(input, scanOptions = {}) {
3460
3742
  }
3461
3743
 
3462
3744
  // ../core/dist/push/credentials.js
3463
- import { readFile as readFile6, writeFile as writeFile3 } from "node:fs/promises";
3745
+ import { readFile as readFile6, writeFile as writeFile4 } from "node:fs/promises";
3464
3746
  import { join as join7 } from "node:path";
3465
3747
  import { parse as parseYaml4, stringify as stringifyYaml3 } from "yaml";
3466
3748
  var CREDENTIALS_LOCAL_RELATIVE = ".harness/credentials.local.yaml";
@@ -3542,7 +3824,7 @@ async function readLocalCredentials(projectRoot) {
3542
3824
  }
3543
3825
  async function writeLocalCredentials(projectRoot, credentials) {
3544
3826
  const normalized = validateLocalCredentials(credentials);
3545
- await writeFile3(join7(projectRoot, CREDENTIALS_LOCAL_RELATIVE), stringifyYaml3(normalized, { sortMapEntries: true }) + "\n", "utf8");
3827
+ await writeFile4(join7(projectRoot, CREDENTIALS_LOCAL_RELATIVE), stringifyYaml3(normalized, { sortMapEntries: true }) + "\n", "utf8");
3546
3828
  }
3547
3829
  async function ensureCredentialsGitignore(projectRoot) {
3548
3830
  const gitignorePath = join7(projectRoot, ".gitignore");
@@ -3571,7 +3853,7 @@ async function ensureCredentialsGitignore(projectRoot) {
3571
3853
  return;
3572
3854
  }
3573
3855
  const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
3574
- await writeFile3(gitignorePath, content + suffix + missing.join("\n") + "\n", "utf8");
3856
+ await writeFile4(gitignorePath, content + suffix + missing.join("\n") + "\n", "utf8");
3575
3857
  }
3576
3858
  function resolvePushAuth(input) {
3577
3859
  const tokenEnv = input.tokenEnv ?? input.projectTokenEnv;
@@ -3613,7 +3895,7 @@ async function readBaseline(projectRoot) {
3613
3895
 
3614
3896
  // ../core/dist/state/locks.js
3615
3897
  import { randomUUID as randomUUID3 } from "node:crypto";
3616
- import { readFile as readFile8, rename as rename3, rm as rm3, writeFile as writeFile4 } from "node:fs/promises";
3898
+ import { readFile as readFile8, rename as rename3, rm as rm3, writeFile as writeFile5 } from "node:fs/promises";
3617
3899
  import { join as join9 } from "node:path";
3618
3900
  async function readLock(path) {
3619
3901
  return JSON.parse(await readFile8(path, "utf8"));
@@ -3633,7 +3915,7 @@ async function acquireProtocolLock(projectRoot, operation, options = {}) {
3633
3915
  };
3634
3916
  for (let attempt = 0; attempt < 2; attempt += 1) {
3635
3917
  try {
3636
- await writeFile4(lockPath, JSON.stringify(record, null, 2), { flag: "wx" });
3918
+ await writeFile5(lockPath, JSON.stringify(record, null, 2), { flag: "wx" });
3637
3919
  return {
3638
3920
  path: lockPath,
3639
3921
  operation,
@@ -4998,46 +5280,52 @@ async function cleanupProject(options) {
4998
5280
 
4999
5281
  // ../core/dist/skill/frontmatter.js
5000
5282
  import { parse as parseYaml6 } from "yaml";
5001
- import { z as z14 } from "zod";
5283
+ import { z as z15 } from "zod";
5002
5284
 
5003
5285
  // ../core/dist/skill/fixer.js
5004
5286
  import { parse as parseYaml7, stringify as stringifyYaml5 } from "yaml";
5005
5287
 
5006
- // ../core/dist/skill/agents.js
5007
- var AGENT_DESCRIPTORS = {
5008
- "claude-code": {
5288
+ // ../core/dist/skill/agent-surfaces.js
5289
+ var SURFACES = Object.freeze({
5290
+ "claude-code": Object.freeze({
5009
5291
  agent: "claude-code",
5010
- installable: true,
5011
- installTarget: (slug) => `.claude/skills/${slug}/`,
5012
- installMode: "folder"
5013
- },
5014
- codex: {
5292
+ skillRoots: Object.freeze({ project: ".claude/skills", user: ".claude/skills" }),
5293
+ subagentRoots: Object.freeze({ project: ".claude/agents", user: ".claude/agents" }),
5294
+ subagentFormat: "markdown",
5295
+ subagentExtension: ".md",
5296
+ nativeSkillAliases: Object.freeze([])
5297
+ }),
5298
+ codex: Object.freeze({
5015
5299
  agent: "codex",
5016
- installable: true,
5017
- installTarget: () => "AGENTS.md",
5018
- installMode: "managed_block",
5019
- blockId: (slug) => `harness-skill-${slug}`
5020
- },
5021
- cursor: {
5300
+ skillRoots: Object.freeze({ project: ".agents/skills", user: ".agents/skills" }),
5301
+ subagentRoots: Object.freeze({ project: ".codex/agents", user: ".codex/agents" }),
5302
+ subagentFormat: "toml",
5303
+ subagentExtension: ".toml",
5304
+ nativeSkillAliases: Object.freeze([])
5305
+ }),
5306
+ cursor: Object.freeze({
5022
5307
  agent: "cursor",
5023
- installable: true,
5024
- installTarget: (slug) => `.cursor/rules/${slug}.mdc`,
5025
- installMode: "file"
5026
- },
5027
- generic: {
5028
- agent: "generic",
5029
- installable: true,
5030
- installTarget: (slug) => `.agent-skills/${slug}.md`,
5031
- installMode: "file"
5032
- },
5033
- mcp: {
5034
- agent: "mcp",
5035
- installable: false,
5036
- installTarget: () => "",
5037
- installMode: "file"
5038
- }
5039
- };
5040
- var INSTALLABLE_AGENTS = Object.keys(AGENT_DESCRIPTORS).filter((agent) => AGENT_DESCRIPTORS[agent]?.installable === true);
5308
+ skillRoots: Object.freeze({ project: ".cursor/skills", user: ".cursor/skills" }),
5309
+ subagentRoots: Object.freeze({ project: ".cursor/agents", user: ".cursor/agents" }),
5310
+ subagentFormat: "markdown",
5311
+ subagentExtension: ".md",
5312
+ nativeSkillAliases: Object.freeze([".agents/skills"])
5313
+ }),
5314
+ codebuddy: Object.freeze({
5315
+ agent: "codebuddy",
5316
+ skillRoots: Object.freeze({ project: ".codebuddy/skills", user: ".codebuddy/skills" }),
5317
+ subagentRoots: Object.freeze({ project: ".codebuddy/agents", user: ".codebuddy/agents" }),
5318
+ subagentFormat: "markdown",
5319
+ subagentExtension: ".md",
5320
+ nativeSkillAliases: Object.freeze([])
5321
+ })
5322
+ });
5323
+ var SKILL_TARGET_AGENTS = Object.freeze([
5324
+ "claude-code",
5325
+ "codex",
5326
+ "cursor",
5327
+ "codebuddy"
5328
+ ]);
5041
5329
 
5042
5330
  // ../core/dist/transaction/recovery.js
5043
5331
  import { readFile as readFile12, readdir as readdir6, rm as rm7, stat as stat3 } from "node:fs/promises";
@@ -5420,7 +5708,7 @@ function serializeCliResult(result) {
5420
5708
  }
5421
5709
 
5422
5710
  // src/config/codebuddy-setup.ts
5423
- import { mkdir as mkdir4, readFile as readFile15, readdir as readdir7, stat as stat4, writeFile as writeFile5 } from "node:fs/promises";
5711
+ import { mkdir as mkdir4, readFile as readFile15, readdir as readdir7, stat as stat4, writeFile as writeFile6 } from "node:fs/promises";
5424
5712
  import { basename as basename2, dirname as dirname4, extname, join as join16 } from "node:path";
5425
5713
  var MANAGED_RULE_NAMES = /* @__PURE__ */ new Set([
5426
5714
  "harness-general.md",
@@ -5494,7 +5782,7 @@ async function applyCodeBuddySetup(options) {
5494
5782
  continue;
5495
5783
  }
5496
5784
  await mkdir4(dirname4(target), { recursive: true });
5497
- await writeFile5(target, content, { encoding: "utf8", flag: "wx" });
5785
+ await writeFile6(target, content, { encoding: "utf8", flag: "wx" });
5498
5786
  result.copied.push(target);
5499
5787
  }
5500
5788
  }
@@ -5515,7 +5803,7 @@ async function applyCodeBuddySetup(options) {
5515
5803
  codegraph: { command: "codegraph", args: ["serve", "--mcp"] }
5516
5804
  }
5517
5805
  };
5518
- await writeFile5(path, JSON.stringify(next, null, 2) + "\n", "utf8");
5806
+ await writeFile6(path, JSON.stringify(next, null, 2) + "\n", "utf8");
5519
5807
  result.mcpUpdated = true;
5520
5808
  }
5521
5809
  }
package/package.json CHANGED
@@ -1,36 +1,36 @@
1
- {
2
- "name": "hunter-harness",
3
- "version": "0.2.19",
4
- "description": "Local-first, server-governed agent harness",
5
- "license": "MIT",
6
- "type": "module",
7
- "engines": {
8
- "node": ">=22.12.0"
9
- },
10
- "bin": {
11
- "hunter-harness": "dist/bin.js"
12
- },
13
- "files": [
14
- "dist/bin.js"
15
- ],
16
- "scripts": {
17
- "build": "node ../../node_modules/typescript/bin/tsc -p tsconfig.json && npm run bundle",
18
- "bundle": "node ../../scripts/bundle-cli.mjs",
19
- "typecheck": "node ../../node_modules/typescript/bin/tsc -p tsconfig.json --noEmit",
20
- "prepack": "npm run build"
21
- },
22
- "dependencies": {
23
- "commander": "15.0.0",
24
- "yaml": "2.9.0",
25
- "zod": "4.4.3"
26
- },
27
- "optionalDependencies": {
28
- "pacote": "21.4.0"
29
- },
30
- "devDependencies": {
31
- "@hunter-harness/contracts": "*",
32
- "@hunter-harness/core": "*",
33
- "@types/pacote": "^11.1.8",
34
- "esbuild": "^0.25.12"
35
- }
36
- }
1
+ {
2
+ "name": "hunter-harness",
3
+ "version": "0.2.20",
4
+ "description": "Local-first, server-governed agent harness",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=22.12.0"
9
+ },
10
+ "bin": {
11
+ "hunter-harness": "dist/bin.js"
12
+ },
13
+ "files": [
14
+ "dist/bin.js"
15
+ ],
16
+ "scripts": {
17
+ "build": "node ../../node_modules/typescript/bin/tsc -p tsconfig.json && npm run bundle",
18
+ "bundle": "node ../../scripts/bundle-cli.mjs",
19
+ "typecheck": "node ../../node_modules/typescript/bin/tsc -p tsconfig.json --noEmit",
20
+ "prepack": "npm run build"
21
+ },
22
+ "dependencies": {
23
+ "commander": "15.0.0",
24
+ "yaml": "2.9.0",
25
+ "zod": "4.4.3"
26
+ },
27
+ "optionalDependencies": {
28
+ "pacote": "21.4.0"
29
+ },
30
+ "devDependencies": {
31
+ "@hunter-harness/contracts": "*",
32
+ "@hunter-harness/core": "*",
33
+ "@types/pacote": "^11.1.8",
34
+ "esbuild": "^0.25.12"
35
+ }
36
+ }