@hiveai/core 0.10.9 → 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",
@@ -2716,6 +2979,7 @@ function escapeRegExp(value) {
2716
2979
  }
2717
2980
  export {
2718
2981
  AUTOPILOT_DEFAULTS,
2982
+ ActivationSchema,
2719
2983
  AnchorSchema,
2720
2984
  BRIEFING_MARKER_TTL_MS,
2721
2985
  BRIEFING_PRESET_DEFAULTS,
@@ -2727,6 +2991,7 @@ export {
2727
2991
  DEFAULT_AUTO_PROMOTE_RULE,
2728
2992
  DEFAULT_CONFIDENCE_THRESHOLDS,
2729
2993
  DEFAULT_CONFIG,
2994
+ DEFAULT_DORMANT_DAYS,
2730
2995
  GUESSABLE_THRESHOLD,
2731
2996
  HAIVE_DIR,
2732
2997
  MEMORIES_DIR,
@@ -2743,6 +3008,8 @@ export {
2743
3008
  USAGE_LOG_DIR,
2744
3009
  USAGE_LOG_FILE,
2745
3010
  addedLinesFromDiff,
3011
+ aggregateRetrieval,
3012
+ aggregateSensors,
2746
3013
  aggregateUsage,
2747
3014
  allocateBudget,
2748
3015
  antiPatternGateParams,
@@ -2752,10 +3019,13 @@ export {
2752
3019
  briefingMarkersDir,
2753
3020
  buildCodeMap,
2754
3021
  buildFrontmatter,
3022
+ buildReport,
2755
3023
  bumpRead,
2756
3024
  codeMapPath,
2757
3025
  collectTimelineEntries,
3026
+ compareImpact,
2758
3027
  compileRegexSensor,
3028
+ computeImpact,
2759
3029
  configPath,
2760
3030
  contractLockPath,
2761
3031
  deriveConfidence,
@@ -2764,6 +3034,7 @@ export {
2764
3034
  emptyUsageIndex,
2765
3035
  enforcementDir,
2766
3036
  estimateTokens,
3037
+ evaluateSkillActivation,
2767
3038
  extractActionsBriefBody,
2768
3039
  extractSnippet,
2769
3040
  findLexicalConflictPairs,
@@ -2780,6 +3051,8 @@ export {
2780
3051
  isGlobPath,
2781
3052
  isLikelyGuessable,
2782
3053
  isRetiredMemory,
3054
+ isSkill,
3055
+ isSkillSuppressed,
2783
3056
  isStackPackSeed,
2784
3057
  listMarkdownFilesRecursive,
2785
3058
  literalMatchesAllTokens,
@@ -2794,6 +3067,7 @@ export {
2794
3067
  memoryMatchesAnchorPaths,
2795
3068
  newMemoryId,
2796
3069
  normalizeSessionId,
3070
+ overallScore,
2797
3071
  parseMemory,
2798
3072
  parseSince,
2799
3073
  pathsOverlap,
@@ -2804,6 +3078,7 @@ export {
2804
3078
  readRecentBriefingMarker,
2805
3079
  readRuntimeJournalTail,
2806
3080
  readUsageEvents,
3081
+ recordApplied,
2807
3082
  recordRejection,
2808
3083
  relPathFrom,
2809
3084
  resolveBriefingBudget,
@@ -2817,6 +3092,8 @@ export {
2817
3092
  saveCodeMap,
2818
3093
  saveConfig,
2819
3094
  saveUsageIndex,
3095
+ scoreRetrievalCase,
3096
+ scoreSensorCase,
2820
3097
  sensorAppliesToPath,
2821
3098
  sensorTargetsFromDiff,
2822
3099
  serializeMemory,
@@ -2825,6 +3102,9 @@ export {
2825
3102
  stripPrivate,
2826
3103
  suggestSensorFromMemory,
2827
3104
  suggestTopicKey,
3105
+ summarizeImpact,
3106
+ synthesizeSelfEvalCases,
3107
+ titleFromBody,
2828
3108
  tokenizeQuery,
2829
3109
  trackDependencies,
2830
3110
  trackReads,