@hiveai/core 0.10.8 → 0.11.0

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
@@ -46,6 +46,14 @@ var SensorSchema = z.object({
46
46
  /** ISO timestamp of the last time this sensor matched a diff. */
47
47
  last_fired: z.string().nullable().default(null)
48
48
  });
49
+ var ActivationSchema = z.object({
50
+ /** Case-insensitive substrings matched against the task text. */
51
+ keywords: z.array(z.string()).default([]),
52
+ /** Glob-ish path patterns matched against the files being edited. */
53
+ globs: z.array(z.string()).default([]),
54
+ /** Always activate (rare — for truly universal playbooks). */
55
+ always: z.boolean().default(false)
56
+ });
49
57
  var IsoDateString = z.union([z.string(), z.date()]).transform((v) => v instanceof Date ? v.toISOString() : v).pipe(z.string().datetime());
50
58
  var MemoryFrontmatterSchema = z.object({
51
59
  id: z.string().min(1),
@@ -56,6 +64,8 @@ var MemoryFrontmatterSchema = z.object({
56
64
  anchor: AnchorSchema.default({ paths: [], symbols: [] }),
57
65
  /** Optional executable check derived from this memory (feedback computational layer). */
58
66
  sensor: SensorSchema.optional(),
67
+ /** Optional progressive-disclosure triggers — only meaningful for `type: skill`. */
68
+ activation: ActivationSchema.optional(),
59
69
  tags: z.array(z.string()).default([]),
60
70
  domain: z.string().optional(),
61
71
  author: z.string().optional(),
@@ -149,6 +159,7 @@ function buildFrontmatter(input) {
149
159
  expires_when: null,
150
160
  topic: input.topic,
151
161
  sensor: input.sensor,
162
+ activation: input.activation,
152
163
  revision_count: 0,
153
164
  related_ids: input.relatedIds ?? []
154
165
  });
@@ -583,9 +594,14 @@ function emptyUsage() {
583
594
  last_read_at: null,
584
595
  rejected_count: 0,
585
596
  last_rejected_at: null,
586
- rejection_reason: null
597
+ rejection_reason: null,
598
+ applied_count: 0,
599
+ last_applied_at: null
587
600
  };
588
601
  }
602
+ function normalizeUsage(stored) {
603
+ return { ...emptyUsage(), ...stored ?? {} };
604
+ }
589
605
  function emptyUsageIndex() {
590
606
  return {
591
607
  version: 1,
@@ -615,13 +631,13 @@ async function saveUsageIndex(paths, index) {
615
631
  await writeFile(file, JSON.stringify(index, null, 2), "utf8");
616
632
  }
617
633
  function getUsage(index, id) {
618
- return index.by_id[id] ?? emptyUsage();
634
+ return normalizeUsage(index.by_id[id]);
619
635
  }
620
636
  function bumpRead(index, ids) {
621
637
  if (ids.length === 0) return index;
622
638
  const now = (/* @__PURE__ */ new Date()).toISOString();
623
639
  for (const id of ids) {
624
- const current = index.by_id[id] ?? emptyUsage();
640
+ const current = normalizeUsage(index.by_id[id]);
625
641
  index.by_id[id] = {
626
642
  ...current,
627
643
  read_count: current.read_count + 1,
@@ -631,7 +647,7 @@ function bumpRead(index, ids) {
631
647
  return index;
632
648
  }
633
649
  function recordRejection(index, id, reason) {
634
- const current = index.by_id[id] ?? emptyUsage();
650
+ const current = normalizeUsage(index.by_id[id]);
635
651
  const now = (/* @__PURE__ */ new Date()).toISOString();
636
652
  index.by_id[id] = {
637
653
  ...current,
@@ -641,6 +657,16 @@ function recordRejection(index, id, reason) {
641
657
  };
642
658
  return index;
643
659
  }
660
+ function recordApplied(index, id) {
661
+ const current = normalizeUsage(index.by_id[id]);
662
+ const now = (/* @__PURE__ */ new Date()).toISOString();
663
+ index.by_id[id] = {
664
+ ...current,
665
+ applied_count: current.applied_count + 1,
666
+ last_applied_at: now
667
+ };
668
+ return index;
669
+ }
644
670
  var DECAY_DAYS = 90;
645
671
  function isDecaying(usage, createdAt) {
646
672
  const threshold = Date.now() - DECAY_DAYS * 24 * 60 * 60 * 1e3;
@@ -657,6 +683,206 @@ async function trackReads(paths, ids) {
657
683
  return index;
658
684
  }
659
685
 
686
+ // src/impact.ts
687
+ var MS_PER_DAY = 24 * 60 * 60 * 1e3;
688
+ var DEFAULT_DORMANT_DAYS = 120;
689
+ var READ_SATURATION = 32;
690
+ function clamp01(n) {
691
+ if (Number.isNaN(n)) return 0;
692
+ return Math.max(0, Math.min(1, n));
693
+ }
694
+ function hasSensorFired(fm) {
695
+ return Boolean(fm.sensor?.last_fired);
696
+ }
697
+ function isDeadStatus(fm) {
698
+ return fm.status === "stale" || fm.status === "deprecated" || fm.status === "rejected";
699
+ }
700
+ function computeImpact(fm, usage, options = {}) {
701
+ const now = options.now ?? /* @__PURE__ */ new Date();
702
+ const dormantDays = options.dormantDays ?? DEFAULT_DORMANT_DAYS;
703
+ const signals = [];
704
+ let raw = 0;
705
+ if (usage.read_count > 0) {
706
+ raw += Math.min(1, Math.log2(usage.read_count + 1) / Math.log2(READ_SATURATION + 1)) * 0.35;
707
+ signals.push(`read ${usage.read_count}\xD7`);
708
+ }
709
+ if (usage.applied_count > 0) {
710
+ raw += Math.min(1, usage.applied_count / 4) * 0.6;
711
+ signals.push(`applied ${usage.applied_count}\xD7`);
712
+ }
713
+ if (hasSensorFired(fm)) {
714
+ raw += 0.25;
715
+ signals.push("sensor fired");
716
+ }
717
+ if (usage.rejected_count > 0) {
718
+ raw -= Math.min(0.6, usage.rejected_count * 0.25);
719
+ signals.push(`rejected ${usage.rejected_count}\xD7`);
720
+ }
721
+ let score = clamp01(raw);
722
+ if (isDeadStatus(fm)) {
723
+ score *= 0.2;
724
+ signals.push(`status=${fm.status}`);
725
+ }
726
+ const anchor = usage.last_applied_at ?? usage.last_read_at ?? fm.created_at;
727
+ const ageDays = (now.getTime() - new Date(anchor).getTime()) / MS_PER_DAY;
728
+ const dormant = Number.isFinite(ageDays) && ageDays >= dormantDays && usage.applied_count === 0;
729
+ if (dormant) {
730
+ score *= 0.5;
731
+ signals.push(`dormant ${Math.floor(ageDays)}d`);
732
+ }
733
+ const tier = deriveTier(score, dormant, usage);
734
+ const pruneCandidate = isPruneCandidate(fm, usage, tier);
735
+ return { score: round3(score), tier, signals, pruneCandidate };
736
+ }
737
+ function deriveTier(score, dormant, usage) {
738
+ if (dormant && usage.read_count <= 1 && usage.applied_count === 0) return "dormant";
739
+ if (score >= 0.55) return "high";
740
+ if (score >= 0.2) return "medium";
741
+ return "low";
742
+ }
743
+ function isPruneCandidate(fm, usage, tier) {
744
+ if (fm.sensor || usage.applied_count > 0) return false;
745
+ if (isDeadStatus(fm)) return true;
746
+ if (usage.rejected_count > 0 && usage.rejected_count >= usage.read_count) return true;
747
+ if (tier === "dormant" && usage.read_count === 0) return true;
748
+ return false;
749
+ }
750
+ function round3(n) {
751
+ return Math.round(n * 1e3) / 1e3;
752
+ }
753
+ function compareImpact(a, b) {
754
+ if (b.score !== a.score) return b.score - a.score;
755
+ if (a.pruneCandidate !== b.pruneCandidate) return a.pruneCandidate ? 1 : -1;
756
+ return 0;
757
+ }
758
+ function summarizeImpact(scores) {
759
+ const summary = {
760
+ total: scores.length,
761
+ high: 0,
762
+ medium: 0,
763
+ low: 0,
764
+ dormant: 0,
765
+ prune_candidates: 0
766
+ };
767
+ for (const s of scores) {
768
+ summary[s.tier] += 1;
769
+ if (s.pruneCandidate) summary.prune_candidates += 1;
770
+ }
771
+ return summary;
772
+ }
773
+
774
+ // src/eval.ts
775
+ function round32(n) {
776
+ return Math.round(n * 1e3) / 1e3;
777
+ }
778
+ function uniq(ids) {
779
+ return [...new Set(ids)];
780
+ }
781
+ function scoreRetrievalCase(name, expectIds, surfacedRanked) {
782
+ const expect = uniq(expectIds);
783
+ const surfaced = uniq(surfacedRanked);
784
+ const surfacedSet = new Set(surfaced);
785
+ const hits = expect.filter((id) => surfacedSet.has(id));
786
+ const misses = expect.filter((id) => !surfacedSet.has(id));
787
+ let bestRank = null;
788
+ for (let i = 0; i < surfaced.length; i++) {
789
+ if (expect.includes(surfaced[i])) {
790
+ bestRank = i + 1;
791
+ break;
792
+ }
793
+ }
794
+ return {
795
+ name,
796
+ expect_ids: expect,
797
+ surfaced_ids: surfaced,
798
+ hits,
799
+ misses,
800
+ precision: surfaced.length === 0 ? 0 : round32(hits.length / surfaced.length),
801
+ recall: expect.length === 0 ? 1 : round32(hits.length / expect.length),
802
+ best_rank: bestRank
803
+ };
804
+ }
805
+ function aggregateRetrieval(cases) {
806
+ const n = cases.length;
807
+ const mean = (sel) => n === 0 ? 0 : round32(cases.reduce((s, c) => s + sel(c), 0) / n);
808
+ return {
809
+ cases,
810
+ mean_precision: mean((c) => c.precision),
811
+ mean_recall: mean((c) => c.recall),
812
+ mrr: mean((c) => c.best_rank ? 1 / c.best_rank : 0)
813
+ };
814
+ }
815
+ function scoreSensorCase(name, expectFireIds, firedIds) {
816
+ const expect = uniq(expectFireIds);
817
+ const fired = uniq(firedIds);
818
+ const firedSet = new Set(fired);
819
+ const hits = expect.filter((id) => firedSet.has(id));
820
+ const misses = expect.filter((id) => !firedSet.has(id));
821
+ return {
822
+ name,
823
+ expect_fire_ids: expect,
824
+ fired_ids: fired,
825
+ hits,
826
+ misses,
827
+ recall: expect.length === 0 ? 1 : round32(hits.length / expect.length)
828
+ };
829
+ }
830
+ function aggregateSensors(cases) {
831
+ const totalExpected = cases.reduce((s, c) => s + c.expect_fire_ids.length, 0);
832
+ const totalHits = cases.reduce((s, c) => s + c.hits.length, 0);
833
+ return {
834
+ cases,
835
+ catch_rate: totalExpected === 0 ? 1 : round32(totalHits / totalExpected)
836
+ };
837
+ }
838
+ function overallScore(retrieval, sensors) {
839
+ if (retrieval && sensors) {
840
+ return Math.round((0.5 * retrieval.mean_recall + 0.2 * retrieval.mrr + 0.3 * sensors.catch_rate) * 100);
841
+ }
842
+ if (retrieval) {
843
+ return Math.round((0.7 * retrieval.mean_recall + 0.3 * retrieval.mrr) * 100);
844
+ }
845
+ if (sensors) {
846
+ return Math.round(sensors.catch_rate * 100);
847
+ }
848
+ return 0;
849
+ }
850
+ function buildReport(retrieval, sensors) {
851
+ return { retrieval, sensors, score: overallScore(retrieval, sensors) };
852
+ }
853
+ function titleFromBody(body) {
854
+ const lines = body.split("\n");
855
+ for (const line of lines) {
856
+ const heading = /^#+\s*(.+)$/.exec(line.trim());
857
+ if (heading) return heading[1].trim().slice(0, 120);
858
+ }
859
+ for (const line of lines) {
860
+ const t = line.trim();
861
+ if (t) return t.replace(/^[-*]\s*/, "").slice(0, 120);
862
+ }
863
+ return "";
864
+ }
865
+ function synthesizeSelfEvalCases(memories, options = {}) {
866
+ const includeFiles = options.includeFiles ?? true;
867
+ const skip = new Set(options.skipStatuses ?? ["stale", "deprecated", "rejected"]);
868
+ const cases = [];
869
+ for (const { memory } of memories) {
870
+ const fm = memory.frontmatter;
871
+ if (fm.type === "session_recap") continue;
872
+ if (skip.has(fm.status)) continue;
873
+ const paths = fm.anchor.paths;
874
+ if (paths.length === 0) continue;
875
+ const task = titleFromBody(memory.body) || fm.id;
876
+ cases.push({
877
+ name: fm.id,
878
+ task,
879
+ ...includeFiles ? { files: paths } : {},
880
+ expect_ids: [fm.id]
881
+ });
882
+ }
883
+ return cases;
884
+ }
885
+
660
886
  // src/confidence.ts
661
887
  var DEFAULT_CONFIDENCE_THRESHOLDS = {
662
888
  trustedReads: 3,
@@ -664,13 +890,13 @@ var DEFAULT_CONFIDENCE_THRESHOLDS = {
664
890
  decayDays: 180,
665
891
  hardDecayDays: 365
666
892
  };
667
- var MS_PER_DAY = 24 * 60 * 60 * 1e3;
893
+ var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
668
894
  function deriveConfidence(fm, usage, thresholds = DEFAULT_CONFIDENCE_THRESHOLDS, now = /* @__PURE__ */ new Date()) {
669
895
  if (fm.status === "stale" || fm.status === "deprecated" || fm.status === "rejected") return "stale";
670
896
  const baseLevel = baseConfidence(fm, usage, thresholds);
671
897
  if (baseLevel !== "authoritative" && baseLevel !== "trusted") return baseLevel;
672
898
  const anchor = usage.last_read_at ?? fm.created_at;
673
- const ageDays = (now.getTime() - new Date(anchor).getTime()) / MS_PER_DAY;
899
+ const ageDays = (now.getTime() - new Date(anchor).getTime()) / MS_PER_DAY2;
674
900
  if (Number.isNaN(ageDays) || ageDays <= 0) return baseLevel;
675
901
  if (ageDays >= thresholds.hardDecayDays) {
676
902
  return "low";
@@ -700,6 +926,43 @@ function isAutoPromoteEligible(fm, usage, rule = DEFAULT_AUTO_PROMOTE_RULE) {
700
926
  return usage.read_count >= rule.minReads;
701
927
  }
702
928
 
929
+ // src/skill-activation.ts
930
+ function isSkill(fm) {
931
+ return fm.type === "skill";
932
+ }
933
+ function evaluateSkillActivation(fm, ctx) {
934
+ if (!isSkill(fm)) return { applicable: false, activated: true, reasons: [] };
935
+ const act = fm.activation;
936
+ if (!act) return { applicable: false, activated: true, reasons: ["no-activation"] };
937
+ const reasons = [];
938
+ if (act.always) reasons.push("always");
939
+ const task = (ctx.task ?? "").toLowerCase();
940
+ if (task) {
941
+ for (const kw of act.keywords) {
942
+ if (kw && task.includes(kw.toLowerCase())) {
943
+ reasons.push(`keyword:${kw}`);
944
+ break;
945
+ }
946
+ }
947
+ }
948
+ const files = ctx.files ?? [];
949
+ if (files.length > 0) {
950
+ outer: for (const glob of act.globs) {
951
+ for (const f of files) {
952
+ if (pathsOverlap(glob, f)) {
953
+ reasons.push(`glob:${glob}`);
954
+ break outer;
955
+ }
956
+ }
957
+ }
958
+ }
959
+ return { applicable: true, activated: reasons.length > 0, reasons };
960
+ }
961
+ function isSkillSuppressed(fm, ctx) {
962
+ const result = evaluateSkillActivation(fm, ctx);
963
+ return result.applicable && !result.activated;
964
+ }
965
+
703
966
  // src/specificity.ts
704
967
  var GENERIC_PHRASES = [
705
968
  "validate input",
@@ -2613,11 +2876,12 @@ function suggestSensorFromMemory(body, anchorPaths, options = {}) {
2613
2876
  if (paths.length === 0) return null;
2614
2877
  const negativeText = body.split(/\*\*Instead,\s*use:\*\*|^##\s+Instead\b/im)[0] ?? body;
2615
2878
  const assignment = pickAssignmentPattern(negativeText);
2616
- const token = assignment?.label ?? pickDistinctiveToken(negativeText);
2879
+ const lowercaseValue = assignment ? null : pickLowercaseValuePattern(negativeText);
2880
+ const token = assignment?.label ?? lowercaseValue?.label ?? pickDistinctiveToken(negativeText);
2617
2881
  if (!token) return null;
2618
2882
  return {
2619
2883
  kind: "regex",
2620
- pattern: assignment?.pattern ?? escapeRegExp(token),
2884
+ pattern: assignment?.pattern ?? lowercaseValue?.pattern ?? escapeRegExp(token),
2621
2885
  paths,
2622
2886
  message: sensorMessageFromBody(body, token),
2623
2887
  severity: "warn",
@@ -2625,6 +2889,20 @@ function suggestSensorFromMemory(body, anchorPaths, options = {}) {
2625
2889
  last_fired: null
2626
2890
  };
2627
2891
  }
2892
+ function pickLowercaseValuePattern(text) {
2893
+ const candidates = [];
2894
+ for (const match of text.matchAll(/\blowercase\s+([A-Za-z][A-Za-z0-9_.:-]{2,79})\s+([a-z][a-z0-9_.:-]{1,40})\b/g)) {
2895
+ const key = match[1] ?? "";
2896
+ const value = match[2] ?? "";
2897
+ if (!isDistinctiveToken(key, true) || isBoringValue(value)) continue;
2898
+ candidates.push({
2899
+ label: `${key}=${value}`,
2900
+ pattern: `${escapeRegExp(key)}\\s*[:=]\\s*["']?${escapeRegExp(value)}["']?`,
2901
+ score: key.length + value.length + 35
2902
+ });
2903
+ }
2904
+ return candidates.sort((a, b) => b.score - a.score)[0] ?? null;
2905
+ }
2628
2906
  function pickAssignmentPattern(text) {
2629
2907
  const candidates = [];
2630
2908
  for (const source of assignmentSources(text)) {
@@ -2701,6 +2979,7 @@ function escapeRegExp(value) {
2701
2979
  }
2702
2980
  export {
2703
2981
  AUTOPILOT_DEFAULTS,
2982
+ ActivationSchema,
2704
2983
  AnchorSchema,
2705
2984
  BRIEFING_MARKER_TTL_MS,
2706
2985
  BRIEFING_PRESET_DEFAULTS,
@@ -2712,6 +2991,7 @@ export {
2712
2991
  DEFAULT_AUTO_PROMOTE_RULE,
2713
2992
  DEFAULT_CONFIDENCE_THRESHOLDS,
2714
2993
  DEFAULT_CONFIG,
2994
+ DEFAULT_DORMANT_DAYS,
2715
2995
  GUESSABLE_THRESHOLD,
2716
2996
  HAIVE_DIR,
2717
2997
  MEMORIES_DIR,
@@ -2728,6 +3008,8 @@ export {
2728
3008
  USAGE_LOG_DIR,
2729
3009
  USAGE_LOG_FILE,
2730
3010
  addedLinesFromDiff,
3011
+ aggregateRetrieval,
3012
+ aggregateSensors,
2731
3013
  aggregateUsage,
2732
3014
  allocateBudget,
2733
3015
  antiPatternGateParams,
@@ -2737,10 +3019,13 @@ export {
2737
3019
  briefingMarkersDir,
2738
3020
  buildCodeMap,
2739
3021
  buildFrontmatter,
3022
+ buildReport,
2740
3023
  bumpRead,
2741
3024
  codeMapPath,
2742
3025
  collectTimelineEntries,
3026
+ compareImpact,
2743
3027
  compileRegexSensor,
3028
+ computeImpact,
2744
3029
  configPath,
2745
3030
  contractLockPath,
2746
3031
  deriveConfidence,
@@ -2749,6 +3034,7 @@ export {
2749
3034
  emptyUsageIndex,
2750
3035
  enforcementDir,
2751
3036
  estimateTokens,
3037
+ evaluateSkillActivation,
2752
3038
  extractActionsBriefBody,
2753
3039
  extractSnippet,
2754
3040
  findLexicalConflictPairs,
@@ -2765,6 +3051,8 @@ export {
2765
3051
  isGlobPath,
2766
3052
  isLikelyGuessable,
2767
3053
  isRetiredMemory,
3054
+ isSkill,
3055
+ isSkillSuppressed,
2768
3056
  isStackPackSeed,
2769
3057
  listMarkdownFilesRecursive,
2770
3058
  literalMatchesAllTokens,
@@ -2779,6 +3067,7 @@ export {
2779
3067
  memoryMatchesAnchorPaths,
2780
3068
  newMemoryId,
2781
3069
  normalizeSessionId,
3070
+ overallScore,
2782
3071
  parseMemory,
2783
3072
  parseSince,
2784
3073
  pathsOverlap,
@@ -2789,6 +3078,7 @@ export {
2789
3078
  readRecentBriefingMarker,
2790
3079
  readRuntimeJournalTail,
2791
3080
  readUsageEvents,
3081
+ recordApplied,
2792
3082
  recordRejection,
2793
3083
  relPathFrom,
2794
3084
  resolveBriefingBudget,
@@ -2802,6 +3092,8 @@ export {
2802
3092
  saveCodeMap,
2803
3093
  saveConfig,
2804
3094
  saveUsageIndex,
3095
+ scoreRetrievalCase,
3096
+ scoreSensorCase,
2805
3097
  sensorAppliesToPath,
2806
3098
  sensorTargetsFromDiff,
2807
3099
  serializeMemory,
@@ -2810,6 +3102,9 @@ export {
2810
3102
  stripPrivate,
2811
3103
  suggestSensorFromMemory,
2812
3104
  suggestTopicKey,
3105
+ summarizeImpact,
3106
+ synthesizeSelfEvalCases,
3107
+ titleFromBody,
2813
3108
  tokenizeQuery,
2814
3109
  trackDependencies,
2815
3110
  trackReads,