@hiveai/core 0.10.3 → 0.10.4

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.d.ts CHANGED
@@ -326,6 +326,7 @@ declare function buildFrontmatter(input: {
326
326
  topic?: string;
327
327
  status?: MemoryFrontmatter["status"];
328
328
  relatedIds?: string[];
329
+ sensor?: Sensor;
329
330
  }): MemoryFrontmatter;
330
331
 
331
332
  declare const HAIVE_DIR = ".ai";
@@ -1109,8 +1110,9 @@ interface SensorTarget {
1109
1110
  }
1110
1111
  /**
1111
1112
  * Does this sensor apply to `path`? A sensor with no explicit `paths` (and whose
1112
- * memory has no anchor paths) applies everywhere. Otherwise it applies when the path
1113
- * starts with, or contains, any configured prefix.
1113
+ * memory has no anchor paths) applies everywhere. Otherwise it applies only to the
1114
+ * exact file or directory prefix. Use an explicit directory path (`src/foo/`) when a
1115
+ * sensor should cover a whole subtree.
1114
1116
  */
1115
1117
  declare function sensorAppliesToPath(sensor: Sensor, anchorPaths: string[], path: string): boolean;
1116
1118
  /**
@@ -1130,6 +1132,8 @@ declare function runRegexSensor(memoryId: string, sensor: Sensor, target: Sensor
1130
1132
  * are the CLI's responsibility). At most one hit per (memory, file) pair is returned.
1131
1133
  */
1132
1134
  declare function runSensors(memories: Memory[], targets: SensorTarget[]): SensorHit[];
1135
+ /** Split a unified diff into per-file targets containing only added lines. */
1136
+ declare function sensorTargetsFromDiff(diff: string): SensorTarget[];
1133
1137
  /**
1134
1138
  * Extract the added lines from a unified diff (lines starting with a single `+`,
1135
1139
  * excluding the `+++` file header). Mirrors the diff-handling already used by the
@@ -1137,4 +1141,16 @@ declare function runSensors(memories: Memory[], targets: SensorTarget[]): Sensor
1137
1141
  */
1138
1142
  declare function addedLinesFromDiff(diff: string): string;
1139
1143
 
1140
- export { AUTOPILOT_DEFAULTS, type Anchor, AnchorSchema, type AntiPatternGate, type AutoPromoteRule, BRIEFING_MARKER_TTL_MS, BRIEFING_PRESET_DEFAULTS, type BreakingChange, type BriefingBudgetNumbers, type BriefingBudgetPreset, type BriefingMarker, type BudgetPart, type BudgetSlice, type BuildCodeMapOptions, CHARS_PER_TOKEN, CODE_MAP_FILE, CONFIG_FILE, type CodeExport, type CodeExportKind, type CodeFileEntry, type CodeMap, type CodeMapQueryOptions, type CollectTimelineOpts, type ConfidenceLevel, type ConfidenceThresholds, type ConflictCandidatePair, type ConflictCandidatesOpts, type ContractDiffResult, type ContractFile, type ContractSnapshot, CrossRepoProvenanceSchema, type CrossRepoReport, type CrossRepoSource, DECAY_DAYS, DEFAULT_AUTO_PROMOTE_RULE, DEFAULT_CONFIDENCE_THRESHOLDS, DEFAULT_CONFIG, type DepChange, type DepTrackResult, type DependencySnapshot, GUESSABLE_THRESHOLD, HAIVE_DIR, type HaiveConfig, type HaivePaths, type LexicalRankResult, type LoadedMemory, MEMORIES_DIR, type Memory, type MemoryFrontmatter, MemoryFrontmatterSchema, type MemoryScope, MemoryScopeSchema, type MemoryStatus, MemoryStatusSchema, type MemoryType, MemoryTypeSchema, type MemoryUsage, PROJECT_CONTEXT_FILE, RUNTIME_JOURNAL_FILENAME, type ResolveProjectInfo, type RetirementSignal, type RuntimeJournalEntry, SESSION_RECAP_TTL_MS, STACK_PACK_TAG, type Sensor, type SensorHit, SensorSchema, type SensorTarget, type TimelineEntry, type TopicStatusPair, type TruncateOptions, type TruncateResult, USAGE_FILE, USAGE_LOG_DIR, USAGE_LOG_FILE, type UsageAggregate, type UsageEvent, type UsageIndex, type VerifyOptions, type VerifyResult, addedLinesFromDiff, aggregateUsage, allocateBudget, antiPatternGateParams, appendRuntimeJournalEntry, appendUsageEvent, briefingMarkerPath, briefingMarkersDir, buildCodeMap, buildFrontmatter, bumpRead, codeMapPath, collectTimelineEntries, compileRegexSensor, configPath, contractLockPath, deriveConfidence, diffContract, emptyUsage, emptyUsageIndex, enforcementDir, estimateTokens, extractActionsBriefBody, extractSnippet, findLexicalConflictPairs, findProjectRoot, findTopicStatusConflictPairs, firstMemoryOneLine, getUsage, globToRegExp, hasRecentBriefingMarker, inferModulesFromPaths, isAutoPromoteEligible, isDecaying, isFreshIsoDate, isGlobPath, isLikelyGuessable, isRetiredMemory, isStackPackSeed, listMarkdownFilesRecursive, literalMatchesAllTokens, literalMatchesAnyToken, loadCodeMap, loadConfig, loadConfigSync, loadMemoriesFromDir, loadMemory, loadUsageIndex, memoryFilePath, memoryMatchesAnchorPaths, newMemoryId, normalizeSessionId, parseMemory, parseSince, pathsOverlap, pickSnippetNeedle, pullCrossRepoSources, queryCodeMap, rankMemoriesLexical, readRecentBriefingMarker, readRuntimeJournalTail, readUsageEvents, recordRejection, relPathFrom, resolveBriefingBudget, resolveHaivePaths, resolveManifestFiles, resolveProjectInfo, retirementSignal, runRegexSensor, runSensors, runtimeJournalPath, saveCodeMap, saveConfig, saveUsageIndex, sensorAppliesToPath, serializeMemory, snapshotContract, specificityScore, stripPrivate, suggestTopicKey, tokenizeQuery, trackDependencies, trackReads, truncateToTokens, usageLogPath, usageLogSize, usagePath, verifyAnchor, watchContracts, writeBriefingMarker };
1144
+ interface SensorSuggestionOptions {
1145
+ /** Extra paths to put on the sensor. Defaults to the memory anchor paths. */
1146
+ paths?: string[];
1147
+ }
1148
+ /**
1149
+ * Conservatively suggest a regex sensor from a gotcha/attempt body.
1150
+ *
1151
+ * This helper intentionally returns null more often than it guesses. Autogenerated
1152
+ * sensors start as `warn`, never `block`; humans can promote them after seeing them fire.
1153
+ */
1154
+ declare function suggestSensorFromMemory(body: string, anchorPaths: string[], options?: SensorSuggestionOptions): Sensor | null;
1155
+
1156
+ export { AUTOPILOT_DEFAULTS, type Anchor, AnchorSchema, type AntiPatternGate, type AutoPromoteRule, BRIEFING_MARKER_TTL_MS, BRIEFING_PRESET_DEFAULTS, type BreakingChange, type BriefingBudgetNumbers, type BriefingBudgetPreset, type BriefingMarker, type BudgetPart, type BudgetSlice, type BuildCodeMapOptions, CHARS_PER_TOKEN, CODE_MAP_FILE, CONFIG_FILE, type CodeExport, type CodeExportKind, type CodeFileEntry, type CodeMap, type CodeMapQueryOptions, type CollectTimelineOpts, type ConfidenceLevel, type ConfidenceThresholds, type ConflictCandidatePair, type ConflictCandidatesOpts, type ContractDiffResult, type ContractFile, type ContractSnapshot, CrossRepoProvenanceSchema, type CrossRepoReport, type CrossRepoSource, DECAY_DAYS, DEFAULT_AUTO_PROMOTE_RULE, DEFAULT_CONFIDENCE_THRESHOLDS, DEFAULT_CONFIG, type DepChange, type DepTrackResult, type DependencySnapshot, GUESSABLE_THRESHOLD, HAIVE_DIR, type HaiveConfig, type HaivePaths, type LexicalRankResult, type LoadedMemory, MEMORIES_DIR, type Memory, type MemoryFrontmatter, MemoryFrontmatterSchema, type MemoryScope, MemoryScopeSchema, type MemoryStatus, MemoryStatusSchema, type MemoryType, MemoryTypeSchema, type MemoryUsage, PROJECT_CONTEXT_FILE, RUNTIME_JOURNAL_FILENAME, type ResolveProjectInfo, type RetirementSignal, type RuntimeJournalEntry, SESSION_RECAP_TTL_MS, STACK_PACK_TAG, type Sensor, type SensorHit, SensorSchema, type SensorSuggestionOptions, type SensorTarget, type TimelineEntry, type TopicStatusPair, type TruncateOptions, type TruncateResult, USAGE_FILE, USAGE_LOG_DIR, USAGE_LOG_FILE, type UsageAggregate, type UsageEvent, type UsageIndex, type VerifyOptions, type VerifyResult, addedLinesFromDiff, aggregateUsage, allocateBudget, antiPatternGateParams, appendRuntimeJournalEntry, appendUsageEvent, briefingMarkerPath, briefingMarkersDir, buildCodeMap, buildFrontmatter, bumpRead, codeMapPath, collectTimelineEntries, compileRegexSensor, configPath, contractLockPath, deriveConfidence, diffContract, emptyUsage, emptyUsageIndex, enforcementDir, estimateTokens, extractActionsBriefBody, extractSnippet, findLexicalConflictPairs, findProjectRoot, findTopicStatusConflictPairs, firstMemoryOneLine, getUsage, globToRegExp, hasRecentBriefingMarker, inferModulesFromPaths, isAutoPromoteEligible, isDecaying, isFreshIsoDate, isGlobPath, isLikelyGuessable, isRetiredMemory, isStackPackSeed, listMarkdownFilesRecursive, literalMatchesAllTokens, literalMatchesAnyToken, loadCodeMap, loadConfig, loadConfigSync, loadMemoriesFromDir, loadMemory, loadUsageIndex, memoryFilePath, memoryMatchesAnchorPaths, newMemoryId, normalizeSessionId, parseMemory, parseSince, pathsOverlap, pickSnippetNeedle, pullCrossRepoSources, queryCodeMap, rankMemoriesLexical, readRecentBriefingMarker, readRuntimeJournalTail, readUsageEvents, recordRejection, relPathFrom, resolveBriefingBudget, resolveHaivePaths, resolveManifestFiles, resolveProjectInfo, retirementSignal, runRegexSensor, runSensors, runtimeJournalPath, saveCodeMap, saveConfig, saveUsageIndex, sensorAppliesToPath, sensorTargetsFromDiff, serializeMemory, snapshotContract, specificityScore, stripPrivate, suggestSensorFromMemory, suggestTopicKey, tokenizeQuery, trackDependencies, trackReads, truncateToTokens, usageLogPath, usageLogSize, usagePath, verifyAnchor, watchContracts, writeBriefingMarker };
package/dist/index.js CHANGED
@@ -148,6 +148,7 @@ function buildFrontmatter(input) {
148
148
  created_at: now.toISOString(),
149
149
  expires_when: null,
150
150
  topic: input.topic,
151
+ sensor: input.sensor,
151
152
  revision_count: 0,
152
153
  related_ids: input.relatedIds ?? []
153
154
  });
@@ -2465,10 +2466,18 @@ function isRetiredMemory(fm, body = "", now = /* @__PURE__ */ new Date()) {
2465
2466
  }
2466
2467
 
2467
2468
  // src/sensors.ts
2469
+ function normalizeProjectPath(value) {
2470
+ return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^[ab]\//, "").replace(/\/+$/g, "");
2471
+ }
2468
2472
  function sensorAppliesToPath(sensor, anchorPaths, path15) {
2469
2473
  const scopes = sensor.paths.length > 0 ? sensor.paths : anchorPaths;
2470
2474
  if (scopes.length === 0) return true;
2471
- return scopes.some((p) => path15 === p || path15.startsWith(p) || path15.includes(p));
2475
+ const target = normalizeProjectPath(path15);
2476
+ return scopes.some((rawScope) => {
2477
+ const scope = normalizeProjectPath(rawScope);
2478
+ if (!scope) return false;
2479
+ return target === scope || target.startsWith(`${scope}/`);
2480
+ });
2472
2481
  }
2473
2482
  function compileRegexSensor(sensor) {
2474
2483
  if (sensor.kind !== "regex" || !sensor.pattern) return null;
@@ -2511,9 +2520,147 @@ function runSensors(memories, targets) {
2511
2520
  }
2512
2521
  return hits;
2513
2522
  }
2523
+ function sensorTargetsFromDiff(diff) {
2524
+ const targets = [];
2525
+ let currentPath = null;
2526
+ let added = [];
2527
+ const flush = () => {
2528
+ if (!currentPath || added.length === 0) return;
2529
+ targets.push({ path: currentPath, content: added.join("\n") });
2530
+ added = [];
2531
+ };
2532
+ for (const line of diff.split("\n")) {
2533
+ if (line.startsWith("diff --git ")) {
2534
+ flush();
2535
+ currentPath = null;
2536
+ continue;
2537
+ }
2538
+ if (line.startsWith("+++ ")) {
2539
+ flush();
2540
+ const raw = line.slice(4).trim();
2541
+ currentPath = raw === "/dev/null" ? null : normalizeProjectPath(raw);
2542
+ continue;
2543
+ }
2544
+ if (line.startsWith("+") && !line.startsWith("+++")) {
2545
+ if (!currentPath) currentPath = "";
2546
+ added.push(line.slice(1));
2547
+ }
2548
+ }
2549
+ flush();
2550
+ return targets;
2551
+ }
2514
2552
  function addedLinesFromDiff(diff) {
2553
+ const targets = sensorTargetsFromDiff(diff);
2554
+ if (targets.length > 0) return targets.map((target) => target.content).join("\n");
2515
2555
  return diff.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).map((l) => l.slice(1)).join("\n");
2516
2556
  }
2557
+
2558
+ // src/sensor-suggest.ts
2559
+ var CODE_TOKEN_RE = /`([^`\n]{3,80})`|["']([A-Za-z0-9_.:-]{3,80})["']|\b([A-Za-z][A-Za-z0-9_.:-]{2,79})\b/g;
2560
+ var SENSOR_STOPWORDS = /* @__PURE__ */ new Set([
2561
+ "about",
2562
+ "after",
2563
+ "again",
2564
+ "agent",
2565
+ "always",
2566
+ "anchor",
2567
+ "approach",
2568
+ "because",
2569
+ "before",
2570
+ "break",
2571
+ "broken",
2572
+ "cannot",
2573
+ "change",
2574
+ "code",
2575
+ "commit",
2576
+ "correct",
2577
+ "could",
2578
+ "default",
2579
+ "detect",
2580
+ "direct",
2581
+ "directory",
2582
+ "does",
2583
+ "error",
2584
+ "failed",
2585
+ "fails",
2586
+ "file",
2587
+ "files",
2588
+ "future",
2589
+ "instead",
2590
+ "memory",
2591
+ "never",
2592
+ "project",
2593
+ "recorded",
2594
+ "repo",
2595
+ "return",
2596
+ "should",
2597
+ "source",
2598
+ "status",
2599
+ "string",
2600
+ "this",
2601
+ "tried",
2602
+ "true",
2603
+ "type",
2604
+ "undefined",
2605
+ "value",
2606
+ "when",
2607
+ "where",
2608
+ "which",
2609
+ "with",
2610
+ "without"
2611
+ ]);
2612
+ function suggestSensorFromMemory(body, anchorPaths, options = {}) {
2613
+ const paths = options.paths ?? anchorPaths;
2614
+ if (paths.length === 0) return null;
2615
+ const negativeText = body.split(/\*\*Instead,\s*use:\*\*|^##\s+Instead\b/im)[0] ?? body;
2616
+ const token = pickDistinctiveToken(negativeText);
2617
+ if (!token) return null;
2618
+ return {
2619
+ kind: "regex",
2620
+ pattern: escapeRegExp(token),
2621
+ paths,
2622
+ message: sensorMessageFromBody(body, token),
2623
+ severity: "warn",
2624
+ autogen: true,
2625
+ last_fired: null
2626
+ };
2627
+ }
2628
+ function pickDistinctiveToken(text) {
2629
+ const candidates = /* @__PURE__ */ new Map();
2630
+ for (const match of text.matchAll(CODE_TOKEN_RE)) {
2631
+ const raw = (match[1] ?? match[2] ?? match[3] ?? "").trim();
2632
+ const token = raw.replace(/^[^\w.-]+|[^\w.-]+$/g, "");
2633
+ const isCodeLike = Boolean(match[1] ?? match[2]);
2634
+ if (!isDistinctiveToken(token, isCodeLike)) continue;
2635
+ const key = token.toLowerCase();
2636
+ const codeSpanBonus = match[1] ? 20 : match[2] ? 8 : 0;
2637
+ const shapeBonus = /[-_.:]/.test(token) ? 3 : /[A-Z]/.test(token.slice(1)) ? 2 : /\d/.test(token) ? 1 : 0;
2638
+ const score = token.length + codeSpanBonus + shapeBonus;
2639
+ const existing = candidates.get(key);
2640
+ if (!existing || score > existing.score) candidates.set(key, { raw: token, score });
2641
+ }
2642
+ const best = [...candidates.values()].sort((a, b) => b.score - a.score)[0];
2643
+ return best?.raw ?? null;
2644
+ }
2645
+ function isDistinctiveToken(token, isCodeLike) {
2646
+ if (token.length < 4 || token.length > 80) return false;
2647
+ if (/^https?:\/\//i.test(token)) return false;
2648
+ if (/^\d+$/.test(token)) return false;
2649
+ const lower = token.toLowerCase();
2650
+ if (SENSOR_STOPWORDS.has(lower)) return false;
2651
+ if (!/[A-Za-z]/.test(token)) return false;
2652
+ const shaped = /[-_.:\d]/.test(token) || /[A-Z]/.test(token.slice(1));
2653
+ return shaped || isCodeLike;
2654
+ }
2655
+ function sensorMessageFromBody(body, token) {
2656
+ const instead = body.match(/\*\*Instead,\s*use:\*\*\s*([^\n]+)/i)?.[1]?.trim();
2657
+ if (instead) return `Avoid ${token}; ${instead}`;
2658
+ const firstGuidance = body.split("\n").map((line) => line.replace(/^#+\s*/, "").trim()).find((line) => line.length > 0 && !line.startsWith("---"));
2659
+ return firstGuidance?.slice(0, 180) || `Avoid ${token}; this matched an autogenerated hAIve sensor.`;
2660
+ }
2661
+ function escapeRegExp(value) {
2662
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2663
+ }
2517
2664
  export {
2518
2665
  AUTOPILOT_DEFAULTS,
2519
2666
  AnchorSchema,
@@ -2618,10 +2765,12 @@ export {
2618
2765
  saveConfig,
2619
2766
  saveUsageIndex,
2620
2767
  sensorAppliesToPath,
2768
+ sensorTargetsFromDiff,
2621
2769
  serializeMemory,
2622
2770
  snapshotContract,
2623
2771
  specificityScore,
2624
2772
  stripPrivate,
2773
+ suggestSensorFromMemory,
2625
2774
  suggestTopicKey,
2626
2775
  tokenizeQuery,
2627
2776
  trackDependencies,