hunter-harness 0.2.18 → 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 +412 -112
  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,20 @@ 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));
2906
+ let codebase = {
2907
+ map: ".harness/codebase/map",
2908
+ status: "missing"
2909
+ };
2910
+ try {
2911
+ const parsed = JSON.parse(existing);
2912
+ const current = parsed.codebase;
2913
+ if (typeof current?.map === "string" && (current.status === "missing" || current.status === "stale" || current.status === "fresh")) {
2914
+ codebase = { map: current.map, status: current.status };
2915
+ }
2916
+ } catch {
2917
+ }
2718
2918
  const record = {
2719
2919
  schema_version: 2,
2720
2920
  project: {
@@ -2728,18 +2928,28 @@ async function reconcileContextIndex(root, agents, profiles, manifests, codebudd
2728
2928
  ]))
2729
2929
  },
2730
2930
  knowledge: { index: ".harness/knowledge/index.json" },
2731
- codebase: { map: ".harness/codebase/map", status: "missing" },
2732
- skill_bundles: Object.fromEntries(manifests.map((manifest) => [
2733
- manifest.adapter,
2734
- { registry_version: manifest.bundle_version, bundle_hash: manifest.bundle_manifest_hash }
2735
- ]))
2931
+ codebase,
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
+ }))
2736
2946
  };
2737
2947
  const next = JSON.stringify(record, null, 2) + "\n";
2738
2948
  if (existing === next)
2739
2949
  return null;
2740
2950
  return {
2741
2951
  operation: existing === "" ? "add" : "modify",
2742
- path: CONTEXT_INDEX_PATH,
2952
+ path: CONTEXT_INDEX_PATH2,
2743
2953
  content: next
2744
2954
  };
2745
2955
  }
@@ -2903,7 +3113,7 @@ async function refreshProject(options) {
2903
3113
  const newStateFiles = installed.files.filter((entry) => entry.owner === "shared" || !selectedSet.has(entry.owner));
2904
3114
  for (const target of newManaged) {
2905
3115
  const incoming = target.sha256;
2906
- const current = await fileHex(join6(root, target.target_path));
3116
+ const current = await fileHex2(join6(root, target.target_path));
2907
3117
  if (current === null) {
2908
3118
  applied.push(item(target, "add", "MISSING_TARGET", null, incoming));
2909
3119
  ops.push({ operation: "add", path: target.target_path, content: target.bytes });
@@ -2931,7 +3141,7 @@ async function refreshProject(options) {
2931
3141
  }
2932
3142
  }
2933
3143
  for (const target of oldOnly) {
2934
- const current = await fileHex(join6(root, target.target_path));
3144
+ const current = await fileHex2(join6(root, target.target_path));
2935
3145
  if (current === null) {
2936
3146
  continue;
2937
3147
  }
@@ -2957,7 +3167,48 @@ async function refreshProject(options) {
2957
3167
  const projectOperation = await projectTransitionOperation(root, agents, profiles, codebuddySurface2);
2958
3168
  if (projectOperation !== null)
2959
3169
  ops.push(projectOperation);
2960
- 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);
2961
3212
  if (contextOperation !== null)
2962
3213
  ops.push(contextOperation);
2963
3214
  const managedBlocks = [
@@ -3028,6 +3279,14 @@ async function refreshProject(options) {
3028
3279
  function freshnessEntry(agent, profile, status, identity) {
3029
3280
  return { agent, profile, status, identity, driftedFiles: [], missingFiles: [] };
3030
3281
  }
3282
+ function emptyVerification() {
3283
+ return {
3284
+ installedContentHash: null,
3285
+ verifiedAt: null,
3286
+ verificationStatus: "unknown",
3287
+ mismatchDetails: []
3288
+ };
3289
+ }
3031
3290
  var BUILD_MARKER_BUNDLE_PATH = ".harness-build.json";
3032
3291
  function buildMarkerCoreHash(text) {
3033
3292
  if (text === null || text === "")
@@ -3057,7 +3316,8 @@ async function collectFreshness(options) {
3057
3316
  coreHash: null,
3058
3317
  installedCoreHash: null,
3059
3318
  adapterHash: null,
3060
- installedAdapterHash: null
3319
+ installedAdapterHash: null,
3320
+ ...emptyVerification()
3061
3321
  };
3062
3322
  let officialHash = null;
3063
3323
  let officialVersion = null;
@@ -3084,13 +3344,29 @@ async function collectFreshness(options) {
3084
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))));
3085
3345
  const installedProjection = await Promise.all(targets.map(async (target) => ({
3086
3346
  path: target.target_path.replace(/\\/g, "/"),
3087
- sha256: await fileHex(join6(root, target.target_path))
3347
+ sha256: await fileHex2(join6(root, target.target_path))
3088
3348
  })));
3089
3349
  identity.installedAdapterHash = sha256Bytes(canonicalJson(installedProjection.sort((a, b) => a.path.localeCompare(b.path))));
3090
3350
  const markerTarget = targets.find((target) => target.target_path.replace(/\\/g, "/").endsWith(`/${BUILD_MARKER_BUNDLE_PATH}`) || target.target_path === BUILD_MARKER_BUNDLE_PATH);
3091
3351
  if (markerTarget !== void 0) {
3092
3352
  identity.installedCoreHash = buildMarkerCoreHash(await readOptionalText(join6(root, markerTarget.target_path)));
3093
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;
3094
3370
  if (requestedProfile !== installedProfile) {
3095
3371
  agents.push(freshnessEntry(agent, installedProfile, "PROFILE_MISMATCH", identity));
3096
3372
  continue;
@@ -3102,7 +3378,7 @@ async function collectFreshness(options) {
3102
3378
  const drifted = [];
3103
3379
  const missing = [];
3104
3380
  for (const target of targets) {
3105
- const current = await fileHex(join6(root, target.target_path));
3381
+ const current = await fileHex2(join6(root, target.target_path));
3106
3382
  if (current === null) {
3107
3383
  missing.push(target.target_path);
3108
3384
  } else if (current !== target.sha256) {
@@ -3292,7 +3568,7 @@ function highEntropyCandidates(content) {
3292
3568
  }
3293
3569
 
3294
3570
  // ../core/dist/security/scanner.js
3295
- var SENSITIVE_SCANNER_VERSION = "1.0.0";
3571
+ var SENSITIVE_SCANNER_VERSION = "1.1.0";
3296
3572
  var RULES = [
3297
3573
  {
3298
3574
  id: "HH_PRIVATE_KEY",
@@ -3347,6 +3623,9 @@ function rawFindings(content) {
3347
3623
  for (const rule of RULES) {
3348
3624
  for (const match of content.matchAll(rule.pattern)) {
3349
3625
  const value = match[rule.valueGroup ?? 0] ?? match[0];
3626
+ if (rule.id === "HH_PASSWORD_VALUE" && isObviousPlaceholder(value)) {
3627
+ continue;
3628
+ }
3350
3629
  const relative3 = match[0].indexOf(value);
3351
3630
  findings.push({
3352
3631
  ruleId: rule.id,
@@ -3357,6 +3636,8 @@ function rawFindings(content) {
3357
3636
  }
3358
3637
  }
3359
3638
  for (const candidate of highEntropyCandidates(content)) {
3639
+ if (!hasSensitiveLexicalContext(content, candidate.offset))
3640
+ continue;
3360
3641
  findings.push({
3361
3642
  ruleId: "HH_HIGH_ENTROPY",
3362
3643
  severity: "high",
@@ -3366,6 +3647,17 @@ function rawFindings(content) {
3366
3647
  }
3367
3648
  return findings;
3368
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
+ }
3369
3661
  function scanSensitiveFiles(files, options = {}) {
3370
3662
  const findings = [];
3371
3663
  const evidence = [];
@@ -3424,6 +3716,8 @@ function scanSensitiveFiles(files, options = {}) {
3424
3716
  return {
3425
3717
  scanner_version: SENSITIVE_SCANNER_VERSION,
3426
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"),
3427
3721
  findings,
3428
3722
  override_evidence: Object.freeze(evidence)
3429
3723
  };
@@ -3448,7 +3742,7 @@ function generateProposalPreview(input, scanOptions = {}) {
3448
3742
  }
3449
3743
 
3450
3744
  // ../core/dist/push/credentials.js
3451
- import { readFile as readFile6, writeFile as writeFile3 } from "node:fs/promises";
3745
+ import { readFile as readFile6, writeFile as writeFile4 } from "node:fs/promises";
3452
3746
  import { join as join7 } from "node:path";
3453
3747
  import { parse as parseYaml4, stringify as stringifyYaml3 } from "yaml";
3454
3748
  var CREDENTIALS_LOCAL_RELATIVE = ".harness/credentials.local.yaml";
@@ -3530,7 +3824,7 @@ async function readLocalCredentials(projectRoot) {
3530
3824
  }
3531
3825
  async function writeLocalCredentials(projectRoot, credentials) {
3532
3826
  const normalized = validateLocalCredentials(credentials);
3533
- 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");
3534
3828
  }
3535
3829
  async function ensureCredentialsGitignore(projectRoot) {
3536
3830
  const gitignorePath = join7(projectRoot, ".gitignore");
@@ -3559,7 +3853,7 @@ async function ensureCredentialsGitignore(projectRoot) {
3559
3853
  return;
3560
3854
  }
3561
3855
  const suffix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
3562
- await writeFile3(gitignorePath, content + suffix + missing.join("\n") + "\n", "utf8");
3856
+ await writeFile4(gitignorePath, content + suffix + missing.join("\n") + "\n", "utf8");
3563
3857
  }
3564
3858
  function resolvePushAuth(input) {
3565
3859
  const tokenEnv = input.tokenEnv ?? input.projectTokenEnv;
@@ -3601,7 +3895,7 @@ async function readBaseline(projectRoot) {
3601
3895
 
3602
3896
  // ../core/dist/state/locks.js
3603
3897
  import { randomUUID as randomUUID3 } from "node:crypto";
3604
- 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";
3605
3899
  import { join as join9 } from "node:path";
3606
3900
  async function readLock(path) {
3607
3901
  return JSON.parse(await readFile8(path, "utf8"));
@@ -3621,7 +3915,7 @@ async function acquireProtocolLock(projectRoot, operation, options = {}) {
3621
3915
  };
3622
3916
  for (let attempt = 0; attempt < 2; attempt += 1) {
3623
3917
  try {
3624
- await writeFile4(lockPath, JSON.stringify(record, null, 2), { flag: "wx" });
3918
+ await writeFile5(lockPath, JSON.stringify(record, null, 2), { flag: "wx" });
3625
3919
  return {
3626
3920
  path: lockPath,
3627
3921
  operation,
@@ -4986,46 +5280,52 @@ async function cleanupProject(options) {
4986
5280
 
4987
5281
  // ../core/dist/skill/frontmatter.js
4988
5282
  import { parse as parseYaml6 } from "yaml";
4989
- import { z as z14 } from "zod";
5283
+ import { z as z15 } from "zod";
4990
5284
 
4991
5285
  // ../core/dist/skill/fixer.js
4992
5286
  import { parse as parseYaml7, stringify as stringifyYaml5 } from "yaml";
4993
5287
 
4994
- // ../core/dist/skill/agents.js
4995
- var AGENT_DESCRIPTORS = {
4996
- "claude-code": {
5288
+ // ../core/dist/skill/agent-surfaces.js
5289
+ var SURFACES = Object.freeze({
5290
+ "claude-code": Object.freeze({
4997
5291
  agent: "claude-code",
4998
- installable: true,
4999
- installTarget: (slug) => `.claude/skills/${slug}/`,
5000
- installMode: "folder"
5001
- },
5002
- 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({
5003
5299
  agent: "codex",
5004
- installable: true,
5005
- installTarget: () => "AGENTS.md",
5006
- installMode: "managed_block",
5007
- blockId: (slug) => `harness-skill-${slug}`
5008
- },
5009
- 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({
5010
5307
  agent: "cursor",
5011
- installable: true,
5012
- installTarget: (slug) => `.cursor/rules/${slug}.mdc`,
5013
- installMode: "file"
5014
- },
5015
- generic: {
5016
- agent: "generic",
5017
- installable: true,
5018
- installTarget: (slug) => `.agent-skills/${slug}.md`,
5019
- installMode: "file"
5020
- },
5021
- mcp: {
5022
- agent: "mcp",
5023
- installable: false,
5024
- installTarget: () => "",
5025
- installMode: "file"
5026
- }
5027
- };
5028
- 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
+ ]);
5029
5329
 
5030
5330
  // ../core/dist/transaction/recovery.js
5031
5331
  import { readFile as readFile12, readdir as readdir6, rm as rm7, stat as stat3 } from "node:fs/promises";
@@ -5408,7 +5708,7 @@ function serializeCliResult(result) {
5408
5708
  }
5409
5709
 
5410
5710
  // src/config/codebuddy-setup.ts
5411
- 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";
5412
5712
  import { basename as basename2, dirname as dirname4, extname, join as join16 } from "node:path";
5413
5713
  var MANAGED_RULE_NAMES = /* @__PURE__ */ new Set([
5414
5714
  "harness-general.md",
@@ -5482,7 +5782,7 @@ async function applyCodeBuddySetup(options) {
5482
5782
  continue;
5483
5783
  }
5484
5784
  await mkdir4(dirname4(target), { recursive: true });
5485
- await writeFile5(target, content, { encoding: "utf8", flag: "wx" });
5785
+ await writeFile6(target, content, { encoding: "utf8", flag: "wx" });
5486
5786
  result.copied.push(target);
5487
5787
  }
5488
5788
  }
@@ -5503,7 +5803,7 @@ async function applyCodeBuddySetup(options) {
5503
5803
  codegraph: { command: "codegraph", args: ["serve", "--mcp"] }
5504
5804
  }
5505
5805
  };
5506
- await writeFile5(path, JSON.stringify(next, null, 2) + "\n", "utf8");
5806
+ await writeFile6(path, JSON.stringify(next, null, 2) + "\n", "utf8");
5507
5807
  result.mcpUpdated = true;
5508
5808
  }
5509
5809
  }
package/package.json CHANGED
@@ -1,36 +1,36 @@
1
- {
2
- "name": "hunter-harness",
3
- "version": "0.2.18",
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
+ }