@hivelore/core 0.39.1 → 0.42.1

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.
package/dist/index.js CHANGED
@@ -28,8 +28,13 @@ var AnchorSchema = z.object({
28
28
  symbols: z.array(z.string()).default([])
29
29
  });
30
30
  var SensorSchema = z.object({
31
- kind: z.enum(["regex", "shell", "test"]).default("regex"),
32
- /** Regex source (for kind=regex), matched against added diff lines / file content. */
31
+ kind: z.enum(["regex", "ast", "shell", "test"]).default("regex"),
32
+ /**
33
+ * kind=regex: regex source, matched against added diff lines / file content.
34
+ * kind=ast: an ast-grep structural pattern (e.g. `stripe.paymentIntents.create($$$)`) — matched
35
+ * on the AST of changed files, so comments and string literals can never false-positive. Requires
36
+ * the optional `@ast-grep/napi` engine; without it the sensor is unrunnable (warn, never block).
37
+ */
33
38
  pattern: z.string().optional(),
34
39
  /**
35
40
  * Optional "correct-usage" regex (kind=regex). When `pattern` (the risky call) matches but this
@@ -56,6 +61,13 @@ var SensorSchema = z.object({
56
61
  * the incident the test exists to prevent". Surfaced in the block message and the prevention receipt.
57
62
  */
58
63
  incident: z.string().optional(),
64
+ /**
65
+ * kind=shell|test only: the oracle was PROVEN to fail (RED) on the incident state at arming time
66
+ * (`sensors propose --red-ref`): the command passed on the presumed-correct tree AND failed on
67
+ * the replayed incident tree. Distinguishes "a test is routed" from "the test demonstrably
68
+ * catches the incident". Surfaced in the prevention receipt.
69
+ */
70
+ red_proven: z.boolean().optional(),
59
71
  /** `warn` surfaces in review; `block` can hard-block the commit (only when the gate opts in). */
60
72
  severity: z.enum(["warn", "block"]).default("warn"),
61
73
  /** True when Hivelore generated this sensor automatically (vs. hand-authored). */
@@ -951,7 +963,8 @@ function buildPreventionReceipt(events, memories, usage, options) {
951
963
  stage: event.stage ?? null,
952
964
  exit_code: event.exit_code ?? null,
953
965
  message: sensor?.message ?? null,
954
- incident: sensor?.incident ?? null
966
+ incident: sensor?.incident ?? null,
967
+ red_proven: sensor?.red_proven === true
955
968
  };
956
969
  }).sort((a, b) => b.at.localeCompare(a.at));
957
970
  const preventedCountTotal = Object.values(usage.by_id).reduce((sum, item) => sum + item.prevented_count, 0);
@@ -976,7 +989,8 @@ function renderPreventionReceipt(receipt) {
976
989
  const exit = row.exit_code === null ? "" : `, exit ${row.exit_code}`;
977
990
  const stage = row.stage ? ` \u2014 caught at ${row.stage}` : "";
978
991
  const incident = row.incident ? ` \u21A9 incident: ${row.incident}` : "";
979
- lines.push(` \u2717\u2192\u2713 ${row.at.slice(0, 10)} ${row.id.padEnd(32)} (${kind}${exit}${stage})${incident}`);
992
+ const red = row.red_proven ? " \u2713 RED-proven" : "";
993
+ lines.push(` \u2717\u2192\u2713 ${row.at.slice(0, 10)} ${row.id.padEnd(32)} (${kind}${exit}${stage})${incident}${red}`);
980
994
  }
981
995
  lines.push(
982
996
  ` Trend: ${receipt.total} this window vs ${receipt.previous_total} previous window (${receipt.total <= receipt.previous_total ? "recurrences declining" : "recurrences rising"}).`
@@ -1175,6 +1189,43 @@ async function recordProjectContextEmission(paths, hash, now = Date.now()) {
1175
1189
  });
1176
1190
  }
1177
1191
 
1192
+ // src/priority.ts
1193
+ var DEFAULT_PRIORITY_SIGNALS = {
1194
+ type: "",
1195
+ tags: [],
1196
+ requiresHumanApproval: false,
1197
+ directAnchor: false,
1198
+ directSymbol: false,
1199
+ exactTaskMatch: false,
1200
+ strongSemantic: false,
1201
+ usefulSemantic: false,
1202
+ moduleOrDomainMatch: false,
1203
+ tagTaskMatch: false
1204
+ };
1205
+ function prioritySignals(partial) {
1206
+ return { ...DEFAULT_PRIORITY_SIGNALS, ...partial };
1207
+ }
1208
+ function classifyMemoryPriority(signals) {
1209
+ const isNegative = signals.type === "attempt";
1210
+ const isSkill2 = signals.type === "skill";
1211
+ if (signals.requiresHumanApproval || signals.directAnchor || signals.directSymbol || isNegative && (signals.exactTaskMatch || signals.strongSemantic) || isSkill2 && (signals.exactTaskMatch || signals.strongSemantic)) {
1212
+ return "must_read";
1213
+ }
1214
+ if (isStackPackSeed({ tags: signals.tags }) || isEnvWorkaroundMemory({ tags: signals.tags })) {
1215
+ if (isStackPackSeed({ tags: signals.tags }) && (signals.exactTaskMatch || signals.strongSemantic)) {
1216
+ return "useful";
1217
+ }
1218
+ return "background";
1219
+ }
1220
+ if (isSkill2 || signals.moduleOrDomainMatch || signals.exactTaskMatch || signals.usefulSemantic || signals.tagTaskMatch) {
1221
+ return "useful";
1222
+ }
1223
+ return "background";
1224
+ }
1225
+ function priorityRank(priority) {
1226
+ return priority === "must_read" ? 3 : priority === "useful" ? 2 : 1;
1227
+ }
1228
+
1178
1229
  // src/eval.ts
1179
1230
  function round32(n) {
1180
1231
  return Math.round(n * 1e3) / 1e3;
@@ -1302,6 +1353,69 @@ function synthesizeSelfEvalCases(memories, options = {}) {
1302
1353
  }
1303
1354
  return cases;
1304
1355
  }
1356
+ function appendProposedRetrievalCases(specRaw, cases) {
1357
+ let spec = {};
1358
+ if (specRaw?.trim()) {
1359
+ try {
1360
+ spec = JSON.parse(specRaw);
1361
+ } catch {
1362
+ spec = {};
1363
+ }
1364
+ }
1365
+ const existingNames = /* @__PURE__ */ new Set([
1366
+ ...(spec.retrieval ?? []).map((c) => c.name),
1367
+ ...(spec.proposed_retrieval ?? []).map((c) => c.name)
1368
+ ]);
1369
+ const fresh = cases.filter((c) => !existingNames.has(c.name));
1370
+ if (fresh.length > 0) spec.proposed_retrieval = [...spec.proposed_retrieval ?? [], ...fresh];
1371
+ return JSON.stringify(spec, null, 2) + "\n";
1372
+ }
1373
+ function approveProposedCases(specRaw) {
1374
+ let spec;
1375
+ try {
1376
+ spec = JSON.parse(specRaw);
1377
+ } catch {
1378
+ return { raw: specRaw, approved: 0 };
1379
+ }
1380
+ const proposed = spec.proposed_retrieval ?? [];
1381
+ if (proposed.length === 0) return { raw: specRaw, approved: 0 };
1382
+ spec.retrieval = [...spec.retrieval ?? [], ...proposed];
1383
+ delete spec.proposed_retrieval;
1384
+ return { raw: JSON.stringify(spec, null, 2) + "\n", approved: proposed.length };
1385
+ }
1386
+ function runTierContract() {
1387
+ const cases = [
1388
+ {
1389
+ name: "stack-pack seed + strong task evidence \u2192 useful (rescue path stays alive)",
1390
+ expected: "useful",
1391
+ signals: prioritySignals({ type: "convention", tags: ["stack-pack"], strongSemantic: true, usefulSemantic: true })
1392
+ },
1393
+ {
1394
+ name: "stack-pack seed + weak evidence \u2192 background (crowding guard stays)",
1395
+ expected: "background",
1396
+ signals: prioritySignals({ type: "convention", tags: ["stack-pack"], usefulSemantic: true, tagTaskMatch: true })
1397
+ },
1398
+ {
1399
+ name: "env workaround + strong evidence \u2192 background (hard cap stays)",
1400
+ expected: "background",
1401
+ signals: prioritySignals({ type: "gotcha", tags: ["dev-env"], strongSemantic: true, exactTaskMatch: true })
1402
+ },
1403
+ {
1404
+ name: "direct anchor \u2192 must_read (anchors always win)",
1405
+ expected: "must_read",
1406
+ signals: prioritySignals({ type: "convention", directAnchor: true })
1407
+ },
1408
+ {
1409
+ name: "attempt + exact task match \u2192 must_read (negative knowledge first)",
1410
+ expected: "must_read",
1411
+ signals: prioritySignals({ type: "attempt", exactTaskMatch: true })
1412
+ }
1413
+ ];
1414
+ return cases.map(({ name, expected, signals }) => {
1415
+ const actual = classifyMemoryPriority(signals);
1416
+ return { name, expected, actual, pass: actual === expected };
1417
+ });
1418
+ }
1305
1419
 
1306
1420
  // src/confidence.ts
1307
1421
  var DEFAULT_CONFIDENCE_THRESHOLDS = {
@@ -3958,6 +4072,34 @@ function runSensors(memories, targets) {
3958
4072
  }
3959
4073
  return hits;
3960
4074
  }
4075
+ var COMMAND_ENV_EXACT = /* @__PURE__ */ new Set([
4076
+ "PATH",
4077
+ "HOME",
4078
+ "LANG",
4079
+ "LANGUAGE",
4080
+ "TMPDIR",
4081
+ "TMP",
4082
+ "TEMP",
4083
+ "TERM",
4084
+ "SHELL",
4085
+ "USER",
4086
+ "LOGNAME",
4087
+ "PWD",
4088
+ "CI",
4089
+ "COLORTERM",
4090
+ "TZ"
4091
+ ]);
4092
+ var COMMAND_ENV_PREFIXES = ["LC_", "NODE_", "NVM_", "npm_", "HIVELORE_", "HAIVE_"];
4093
+ function scrubbedCommandEnv(env) {
4094
+ const out = {};
4095
+ for (const [key, value] of Object.entries(env)) {
4096
+ if (value === void 0) continue;
4097
+ if (COMMAND_ENV_EXACT.has(key) || COMMAND_ENV_PREFIXES.some((p) => key.startsWith(p))) {
4098
+ out[key] = value;
4099
+ }
4100
+ }
4101
+ return out;
4102
+ }
3961
4103
  function incidentSuffix(incident) {
3962
4104
  const ref = incident?.trim();
3963
4105
  return ref ? ` \u21A9 guards incident: ${ref}` : "";
@@ -3986,6 +4128,34 @@ function selectCommandSensors(memories, changedPaths) {
3986
4128
  }
3987
4129
  return specs;
3988
4130
  }
4131
+ function addedLineNumbersFromDiff(diff) {
4132
+ const result = /* @__PURE__ */ new Map();
4133
+ let currentPath = null;
4134
+ let newLine = 0;
4135
+ for (const line of diff.split("\n")) {
4136
+ if (line.startsWith("+++ ")) {
4137
+ const raw = line.slice(4).trim();
4138
+ currentPath = raw === "/dev/null" ? null : normalizeProjectPath(raw);
4139
+ continue;
4140
+ }
4141
+ const hunk = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
4142
+ if (hunk) {
4143
+ newLine = Number(hunk[1]);
4144
+ continue;
4145
+ }
4146
+ if (!currentPath) continue;
4147
+ if (line.startsWith("+") && !line.startsWith("+++")) {
4148
+ let set = result.get(currentPath);
4149
+ if (!set) result.set(currentPath, set = /* @__PURE__ */ new Set());
4150
+ set.add(newLine);
4151
+ newLine++;
4152
+ } else if (line.startsWith("-") && !line.startsWith("---")) {
4153
+ } else if (!line.startsWith("\\")) {
4154
+ newLine++;
4155
+ }
4156
+ }
4157
+ return result;
4158
+ }
3989
4159
  function sensorTargetsFromDiff(diff) {
3990
4160
  const targets = [];
3991
4161
  let currentPath = null;
@@ -5407,6 +5577,33 @@ function findUncapturedFailures(failures, captureTimes, options = {}) {
5407
5577
  out.sort((a, b) => a.ts.localeCompare(b.ts));
5408
5578
  return out;
5409
5579
  }
5580
+ var EXPLORATORY_RE = /^(ls|find|grep|rg|cat|head|tail|which|stat)\b/i;
5581
+ function distillFailureObservations(failures, options = {}) {
5582
+ const max = options.max ?? 3;
5583
+ const clusters = /* @__PURE__ */ new Map();
5584
+ for (const f of failures) {
5585
+ const summary = f.summary.trim();
5586
+ if (!summary || EXPLORATORY_RE.test(summary.replace(/^Bash:\s*/i, ""))) continue;
5587
+ const key = normalizeSummary(summary);
5588
+ const existing = clusters.get(key);
5589
+ if (existing) {
5590
+ existing.count++;
5591
+ for (const file of f.files ?? []) existing.files.add(file);
5592
+ } else {
5593
+ clusters.set(key, { first: f, count: 1, files: new Set(f.files ?? []) });
5594
+ }
5595
+ }
5596
+ return [...clusters.values()].sort((a, b) => b.count - a.count || b.first.ts.localeCompare(a.first.ts)).slice(0, max).map(({ first, count, files }) => {
5597
+ const summary = first.summary.trim();
5598
+ const firstLine = summary.split("\n")[0].slice(0, 120);
5599
+ return {
5600
+ what: `${first.tool} failed: ${firstLine}`,
5601
+ why_failed: summary.slice(0, 400),
5602
+ paths: [...files].slice(0, 6),
5603
+ occurrences: count
5604
+ };
5605
+ });
5606
+ }
5410
5607
 
5411
5608
  // src/coverage.ts
5412
5609
  var DEFAULT_COVERING_TYPES = ["decision", "convention", "gotcha", "architecture"];
@@ -5906,40 +6103,6 @@ async function handoffAgeMs(root, now = /* @__PURE__ */ new Date()) {
5906
6103
  }
5907
6104
  }
5908
6105
 
5909
- // src/priority.ts
5910
- var DEFAULT_PRIORITY_SIGNALS = {
5911
- type: "",
5912
- tags: [],
5913
- requiresHumanApproval: false,
5914
- directAnchor: false,
5915
- directSymbol: false,
5916
- exactTaskMatch: false,
5917
- strongSemantic: false,
5918
- usefulSemantic: false,
5919
- moduleOrDomainMatch: false,
5920
- tagTaskMatch: false
5921
- };
5922
- function prioritySignals(partial) {
5923
- return { ...DEFAULT_PRIORITY_SIGNALS, ...partial };
5924
- }
5925
- function classifyMemoryPriority(signals) {
5926
- const isNegative = signals.type === "attempt";
5927
- const isSkill2 = signals.type === "skill";
5928
- if (signals.requiresHumanApproval || signals.directAnchor || signals.directSymbol || isNegative && (signals.exactTaskMatch || signals.strongSemantic) || isSkill2 && (signals.exactTaskMatch || signals.strongSemantic)) {
5929
- return "must_read";
5930
- }
5931
- if (isStackPackSeed({ tags: signals.tags }) || isEnvWorkaroundMemory({ tags: signals.tags })) {
5932
- return "background";
5933
- }
5934
- if (isSkill2 || signals.moduleOrDomainMatch || signals.exactTaskMatch || signals.usefulSemantic || signals.tagTaskMatch) {
5935
- return "useful";
5936
- }
5937
- return "background";
5938
- }
5939
- function priorityRank(priority) {
5940
- return priority === "must_read" ? 3 : priority === "useful" ? 2 : 1;
5941
- }
5942
-
5943
6106
  // src/agent-context.ts
5944
6107
  var AGENT_ENV_SIGNALS = [
5945
6108
  { name: "HAIVE_SESSION_ID", label: "hivelore-run-wrapper" },
@@ -5957,6 +6120,92 @@ function detectAgentContext(env = typeof process !== "undefined" ? process.env :
5957
6120
  const signals = AGENT_ENV_SIGNALS.filter(({ name }) => (env[name] ?? "").trim().length > 0).map(({ name, label }) => `${label} (${name})`);
5958
6121
  return { agent: signals.length > 0, signals: [...new Set(signals)] };
5959
6122
  }
6123
+
6124
+ // src/pr-review-ingest.ts
6125
+ var REVIEW_LEARNING_MARKER = /(^|\s)\/?hivelore[:,]?\s+remember\b|(^|\s)hivelore:/i;
6126
+ var INSTRUCTION_RE = /\b(never|always|don'?t|do not|must(?: not)?|should(?: not|n'?t)?|avoid|prefer|instead of|use\s+\S+\s+instead)\b/i;
6127
+ var MAX_INSTRUCTION_CHARS = 500;
6128
+ var MIN_INSTRUCTION_CHARS = 12;
6129
+ function prNumberFrom(comment) {
6130
+ const m = comment.pull_request_url?.match(/\/pulls\/(\d+)$/);
6131
+ return m ? Number(m[1]) : void 0;
6132
+ }
6133
+ function extractReviewLearnings(payload) {
6134
+ if (!Array.isArray(payload)) return [];
6135
+ const comments = payload;
6136
+ const learnings = [];
6137
+ for (const comment of comments) {
6138
+ if (typeof comment?.id !== "number") continue;
6139
+ if ((comment.user?.type ?? "").toLowerCase() === "bot") continue;
6140
+ const body = (comment.body ?? "").trim();
6141
+ if (body.length < MIN_INSTRUCTION_CHARS) continue;
6142
+ const marked = REVIEW_LEARNING_MARKER.test(body);
6143
+ if (!marked && !INSTRUCTION_RE.test(body)) continue;
6144
+ const instruction = body.replace(REVIEW_LEARNING_MARKER, " ").replace(/\s+/g, " ").trim().slice(0, MAX_INSTRUCTION_CHARS);
6145
+ if (instruction.length < MIN_INSTRUCTION_CHARS) continue;
6146
+ learnings.push({
6147
+ thread_id: comment.in_reply_to_id ?? comment.id,
6148
+ comment_id: comment.id,
6149
+ ...comment.path ? { path: comment.path } : {},
6150
+ ...typeof (comment.line ?? comment.original_line) === "number" ? { line: comment.line ?? comment.original_line } : {},
6151
+ author: comment.user?.login ?? "reviewer",
6152
+ instruction,
6153
+ ...comment.html_url ? { url: comment.html_url } : {},
6154
+ ...prNumberFrom(comment) !== void 0 ? { pr_number: prNumberFrom(comment) } : {}
6155
+ });
6156
+ }
6157
+ return learnings;
6158
+ }
6159
+ function reviewLearningsToDrafts(learnings, options = {}) {
6160
+ const limit = options.limit ?? 20;
6161
+ const drafts = [];
6162
+ const seenThreads = /* @__PURE__ */ new Set();
6163
+ for (const learning of learnings) {
6164
+ if (drafts.length >= limit) break;
6165
+ if (seenThreads.has(learning.thread_id)) {
6166
+ const idx = drafts.findIndex((d) => d.key === `github-pr:${learning.thread_id}`);
6167
+ if (idx >= 0) drafts.splice(idx, 1);
6168
+ }
6169
+ seenThreads.add(learning.thread_id);
6170
+ const key = `github-pr:${learning.thread_id}`;
6171
+ const slugSource = learning.instruction.toLowerCase().replace(/[^a-z0-9\s]/g, " ");
6172
+ const slug = slugSource.trim().split(/\s+/).slice(0, 6).join("-") || "review-learning";
6173
+ const finding = {
6174
+ tool: "github-pr",
6175
+ ruleId: "review-learning",
6176
+ severity: "major",
6177
+ message: learning.instruction,
6178
+ path: learning.path ?? "",
6179
+ ...learning.line !== void 0 ? { line: learning.line } : {},
6180
+ key
6181
+ };
6182
+ const baseFm = buildFrontmatter({
6183
+ type: "convention",
6184
+ slug,
6185
+ scope: options.scope ?? "team",
6186
+ module: options.module,
6187
+ tags: ["review-learning"],
6188
+ paths: learning.path ? [learning.path] : [],
6189
+ author: options.author ?? learning.author
6190
+ });
6191
+ const frontmatter = { ...baseFm, status: "proposed", topic: `ingest:${key}` };
6192
+ const provenance = [
6193
+ learning.pr_number !== void 0 ? `PR #${learning.pr_number}` : null,
6194
+ `@${learning.author}`,
6195
+ learning.url ?? null
6196
+ ].filter(Boolean).join(" \xB7 ");
6197
+ const body = `# Review learning: ${learning.instruction.slice(0, 80)}
6198
+
6199
+ ${learning.instruction}
6200
+
6201
+ ` + (learning.path ? `Applies to: \`${learning.path}\`${learning.line !== void 0 ? ` (line ${learning.line} at review time)` : ""}
6202
+
6203
+ ` : "") + `_From a review thread (${provenance}). Review: approve, refine, or reject \u2014 then consider \`hivelore sensors propose\` to make it a deterministic gate._
6204
+ `;
6205
+ drafts.push({ key, topic: `ingest:${key}`, frontmatter, body, finding, has_sensor: false });
6206
+ }
6207
+ return drafts;
6208
+ }
5960
6209
  export {
5961
6210
  AUTOPILOT_DEFAULTS,
5962
6211
  ActivationSchema,
@@ -5995,6 +6244,7 @@ export {
5995
6244
  PREVENTION_DEBOUNCE_MS,
5996
6245
  PROJECT_CONTEXT_FILE,
5997
6246
  PROJECT_CONTEXT_THROTTLE_MS,
6247
+ REVIEW_LEARNING_MARKER,
5998
6248
  RUNTIME_JOURNAL_FILENAME,
5999
6249
  SCAFFOLD_MARKER_RE,
6000
6250
  SEED_QUALITY_FLOOR,
@@ -6007,6 +6257,7 @@ export {
6007
6257
  USAGE_FILE,
6008
6258
  USAGE_LOG_DIR,
6009
6259
  USAGE_LOG_FILE,
6260
+ addedLineNumbersFromDiff,
6010
6261
  addedLinesFromDiff,
6011
6262
  aggregateRetrieval,
6012
6263
  aggregateSensors,
@@ -6015,11 +6266,13 @@ export {
6015
6266
  antiPatternGateParams,
6016
6267
  appendEvalHistory,
6017
6268
  appendPreventionEvent,
6269
+ appendProposedRetrievalCases,
6018
6270
  appendRuntimeJournalEntry,
6019
6271
  appendSensorEvaluations,
6020
6272
  appendUsageEvent,
6021
6273
  applyConflictResolution,
6022
6274
  applyFeedbackAdjustment,
6275
+ approveProposedCases,
6023
6276
  assessBootstrapState,
6024
6277
  assessScaffoldLoop,
6025
6278
  assessSensorHealth,
@@ -6061,6 +6314,7 @@ export {
6061
6314
  detectStacksFromManifests,
6062
6315
  diffContract,
6063
6316
  diffHasDistinctiveOverlap,
6317
+ distillFailureObservations,
6064
6318
  distinctiveCap,
6065
6319
  draftsFromFindings,
6066
6320
  emptyUsage,
@@ -6072,6 +6326,7 @@ export {
6072
6326
  existingGateMissShas,
6073
6327
  extractActionsBriefBody,
6074
6328
  extractReferencedPaths,
6329
+ extractReviewLearnings,
6075
6330
  extractSensorExamples,
6076
6331
  extractSnippet,
6077
6332
  extractTestFilePathsFromCommand,
@@ -6186,8 +6441,10 @@ export {
6186
6441
  resolveProjectInfo,
6187
6442
  retirementSignal,
6188
6443
  revertedShaFromCommit,
6444
+ reviewLearningsToDrafts,
6189
6445
  runRegexSensor,
6190
6446
  runSensors,
6447
+ runTierContract,
6191
6448
  runtimeJournalPath,
6192
6449
  saveCodeMap,
6193
6450
  saveConfig,
@@ -6196,6 +6453,7 @@ export {
6196
6453
  scannableSensorTargets,
6197
6454
  scoreRetrievalCase,
6198
6455
  scoreSensorCase,
6456
+ scrubbedCommandEnv,
6199
6457
  selectCommandSensors,
6200
6458
  sensorAppliesToPath,
6201
6459
  sensorLedgerPath,