@hiveai/core 0.15.0 → 0.18.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
@@ -318,6 +318,16 @@ var STACK_PACK_TAG = "stack-pack";
318
318
  function isStackPackSeed(fm) {
319
319
  return Boolean(fm?.tags?.includes(STACK_PACK_TAG));
320
320
  }
321
+ var ENV_WORKAROUND_TAGS = /* @__PURE__ */ new Set([
322
+ "dev-workflow",
323
+ "dev-env",
324
+ "hotswap",
325
+ "local-setup",
326
+ "tooling-debt"
327
+ ]);
328
+ function isEnvWorkaroundMemory(fm) {
329
+ return Boolean(fm?.tags?.some((t) => ENV_WORKAROUND_TAGS.has(t)));
330
+ }
321
331
  var MODULE_PATTERNS = [
322
332
  /^packages\/([^/]+)\//,
323
333
  /^apps\/([^/]+)\//,
@@ -790,6 +800,45 @@ function summarizeImpact(scores) {
790
800
  }
791
801
  return summary;
792
802
  }
803
+ function recommendFeedbackAdjustment(fm, usage, options = {}) {
804
+ const rejectionThreshold = options.rejectionThreshold ?? 2;
805
+ const hasPositiveOutcome = usage.applied_count > 0 || usage.prevented_count > 0;
806
+ if (fm.sensor?.severity === "block" && usage.rejected_count >= 1) {
807
+ return {
808
+ action: "downgrade-block-sensor",
809
+ reason: "A human contested a blocking guardrail; downgrade it to warn so the gate stays helpful while the lesson is reviewed."
810
+ };
811
+ }
812
+ if (!hasPositiveOutcome && usage.rejected_count >= rejectionThreshold) {
813
+ return {
814
+ action: "deprecate-memory",
815
+ reason: `${usage.rejected_count} rejection(s) and no applied/prevented outcomes; deprecate until the lesson is refined.`
816
+ };
817
+ }
818
+ return { action: "none", reason: "No automatic adjustment needed." };
819
+ }
820
+ function applyFeedbackAdjustment(fm, adjustment, now = /* @__PURE__ */ new Date()) {
821
+ if (adjustment.action === "none") return fm;
822
+ const tags = [.../* @__PURE__ */ new Set([...fm.tags, "feedback-contested"])];
823
+ if (adjustment.action === "downgrade-block-sensor" && fm.sensor) {
824
+ return {
825
+ ...fm,
826
+ tags,
827
+ verified_at: now.toISOString(),
828
+ sensor: { ...fm.sensor, severity: "warn" }
829
+ };
830
+ }
831
+ if (adjustment.action === "deprecate-memory") {
832
+ return {
833
+ ...fm,
834
+ tags,
835
+ status: "deprecated",
836
+ stale_reason: adjustment.reason,
837
+ verified_at: now.toISOString()
838
+ };
839
+ }
840
+ return fm;
841
+ }
793
842
 
794
843
  // src/prevention.ts
795
844
  import { appendFile, mkdir as mkdir2, readFile as readFile4 } from "fs/promises";
@@ -855,6 +904,84 @@ function computeRecurrence(events) {
855
904
  rows.sort((a, b) => b.distinct_days - a.distinct_days || b.catches - a.catches);
856
905
  return { recurring_count: rows.length, top: rows };
857
906
  }
907
+ function briefingProofLine(events, options = {}) {
908
+ const now = options.now ?? /* @__PURE__ */ new Date();
909
+ const days = options.days ?? 30;
910
+ const since = now.getTime() - days * MS_PER_DAY2;
911
+ let count = 0;
912
+ for (const e of events) {
913
+ const t = Date.parse(e.at);
914
+ if (!Number.isFinite(t)) continue;
915
+ if (t >= since && t <= now.getTime()) count += 1;
916
+ }
917
+ if (count === 0) return null;
918
+ return `This harness prevented ${count} repeated mistake${count === 1 ? "" : "s"} in the last ${days} days.`;
919
+ }
920
+ function titleFromMemory(loaded) {
921
+ if (!loaded) return "";
922
+ for (const line of loaded.memory.body.split("\n")) {
923
+ const heading = /^#+\s*(.+)$/.exec(line.trim());
924
+ if (heading) return heading[1].trim().slice(0, 96);
925
+ }
926
+ for (const line of loaded.memory.body.split("\n")) {
927
+ const t = line.trim();
928
+ if (t) return t.replace(/^[-*]\s*/, "").slice(0, 96);
929
+ }
930
+ return "";
931
+ }
932
+ function sourceRank(source) {
933
+ return source === "anti-pattern" ? 0 : 1;
934
+ }
935
+ function summarizeCaughtForYou(events, memories, usage, options = {}) {
936
+ const until = options.now ?? /* @__PURE__ */ new Date();
937
+ const sinceMs = options.since === void 0 ? null : options.since instanceof Date ? options.since.getTime() : Date.parse(options.since);
938
+ const untilMs = until.getTime();
939
+ const byIdSource = /* @__PURE__ */ new Map();
940
+ for (const e of events) {
941
+ const t = Date.parse(e.at);
942
+ if (!Number.isFinite(t)) continue;
943
+ if (sinceMs !== null && Number.isFinite(sinceMs) && t < sinceMs) continue;
944
+ if (t > untilMs) continue;
945
+ const key = `${e.id}\0${e.source}`;
946
+ const current = byIdSource.get(key) ?? { id: e.id, source: e.source, catches: 0, last_at: e.at };
947
+ current.catches += 1;
948
+ if (e.at > current.last_at) current.last_at = e.at;
949
+ byIdSource.set(key, current);
950
+ }
951
+ const memoryById = new Map(memories.map((m) => [m.memory.frontmatter.id, m]));
952
+ const rows = [...byIdSource.values()].map((row) => {
953
+ const current = getUsage(usage, row.id).prevented_count;
954
+ const previous = Math.max(0, current - row.catches);
955
+ return {
956
+ id: row.id,
957
+ title: titleFromMemory(memoryById.get(row.id)) || row.id,
958
+ source: row.source,
959
+ catches: row.catches,
960
+ previous_count: previous,
961
+ current_count: current,
962
+ last_at: row.last_at
963
+ };
964
+ }).sort((a, b) => b.last_at.localeCompare(a.last_at) || sourceRank(a.source) - sourceRank(b.source)).slice(0, options.limit ?? 5);
965
+ return {
966
+ total_catches: [...byIdSource.values()].reduce((sum, row) => sum + row.catches, 0),
967
+ since: sinceMs !== null && Number.isFinite(sinceMs) ? new Date(sinceMs).toISOString() : null,
968
+ until: until.toISOString(),
969
+ rows
970
+ };
971
+ }
972
+ function renderCaughtForYou(summary) {
973
+ if (summary.total_catches === 0 || summary.rows.length === 0) return null;
974
+ const lines = [
975
+ `Caught for you: ${summary.total_catches} prevented repeat${summary.total_catches === 1 ? "" : "s"} this session.`
976
+ ];
977
+ for (const row of summary.rows) {
978
+ const gate = row.source === "anti-pattern" ? "Blocked" : "Caught";
979
+ lines.push(
980
+ `- ${gate}: ${row.title} (${row.id}). Prevention ${row.previous_count}->${row.current_count}.`
981
+ );
982
+ }
983
+ return lines.join("\n");
984
+ }
858
985
 
859
986
  // src/context-throttle.ts
860
987
  import { createHash } from "crypto";
@@ -1687,7 +1814,8 @@ var DEFAULT_CONFIG = {
1687
1814
  scoreThreshold: 80,
1688
1815
  cleanupGeneratedArtifacts: true,
1689
1816
  toolProfile: "enforcement",
1690
- policyPacks: ["architecture", "gotchas", "security", "domain", "release"]
1817
+ policyPacks: ["architecture", "gotchas", "security", "domain", "release"],
1818
+ releaseBranch: "main"
1691
1819
  }
1692
1820
  };
1693
1821
  var AUTOPILOT_DEFAULTS = {
@@ -1715,7 +1843,8 @@ var AUTOPILOT_DEFAULTS = {
1715
1843
  scoreThreshold: 85,
1716
1844
  cleanupGeneratedArtifacts: true,
1717
1845
  toolProfile: "enforcement",
1718
- policyPacks: ["architecture", "gotchas", "security", "domain", "release"]
1846
+ policyPacks: ["architecture", "gotchas", "security", "domain", "release"],
1847
+ releaseBranch: "main"
1719
1848
  }
1720
1849
  };
1721
1850
  function antiPatternGateParams(gate) {
@@ -2833,11 +2962,24 @@ function briefingMarkerPath(paths, sessionId) {
2833
2962
  return path16.join(briefingMarkersDir(paths), `${normalizeSessionId(sessionId)}.json`);
2834
2963
  }
2835
2964
  async function writeBriefingMarker(paths, input) {
2965
+ const sessionId = normalizeSessionId(input.sessionId);
2966
+ const accumulate = input.accumulate ?? true;
2967
+ let priorIds = [];
2968
+ let priorFiles = [];
2969
+ if (accumulate) {
2970
+ const existing = await readSessionBriefingMarker(paths, sessionId);
2971
+ if (existing) {
2972
+ priorIds = existing.memory_ids ?? [];
2973
+ priorFiles = existing.files ?? [];
2974
+ }
2975
+ }
2976
+ const mergedIds = [.../* @__PURE__ */ new Set([...priorIds, ...input.memoryIds ?? []])];
2977
+ const mergedFiles = [.../* @__PURE__ */ new Set([...priorFiles, ...input.files ?? []])];
2836
2978
  const marker = {
2837
- session_id: normalizeSessionId(input.sessionId),
2979
+ session_id: sessionId,
2838
2980
  ...input.task?.trim() ? { task: input.task.trim() } : {},
2839
- ...input.memoryIds && input.memoryIds.length > 0 ? { memory_ids: [...new Set(input.memoryIds)] } : {},
2840
- ...input.files && input.files.length > 0 ? { files: [...new Set(input.files)] } : {},
2981
+ ...mergedIds.length > 0 ? { memory_ids: mergedIds } : {},
2982
+ ...mergedFiles.length > 0 ? { files: mergedFiles } : {},
2841
2983
  source: input.source,
2842
2984
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
2843
2985
  root: paths.root
@@ -2850,6 +2992,18 @@ async function writeBriefingMarker(paths, input) {
2850
2992
  );
2851
2993
  return marker;
2852
2994
  }
2995
+ async function readSessionBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKER_TTL_MS) {
2996
+ const file = briefingMarkerPath(paths, sessionId);
2997
+ if (!existsSync14(file)) return null;
2998
+ try {
2999
+ const marker = JSON.parse(await readFile13(file, "utf8"));
3000
+ const created = Date.parse(marker.created_at);
3001
+ if (!Number.isFinite(created) || Date.now() - created > ttlMs) return null;
3002
+ return marker;
3003
+ } catch {
3004
+ return null;
3005
+ }
3006
+ }
2853
3007
  async function hasRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKER_TTL_MS) {
2854
3008
  const now = Date.now();
2855
3009
  const candidates = [];
@@ -3323,8 +3477,75 @@ function parseSonar(input) {
3323
3477
  }
3324
3478
  return findings;
3325
3479
  }
3326
- function parseFindings(format, input) {
3327
- return format === "sonar" ? parseSonar(input) : parseSarif(input);
3480
+ function parseEslintJson(input, opts = {}) {
3481
+ const docs = asArray(coerceJson(input));
3482
+ const findings = [];
3483
+ const cwd = opts.cwd ? opts.cwd.replace(/\/+$/, "") + "/" : "";
3484
+ for (const fileRaw of docs) {
3485
+ const file = asRecord(fileRaw);
3486
+ const rawPath = typeof file.filePath === "string" ? file.filePath : "";
3487
+ if (!rawPath) continue;
3488
+ const path18 = cwd && rawPath.startsWith(cwd) ? rawPath.slice(cwd.length) : rawPath;
3489
+ for (const msgRaw of asArray(file.messages)) {
3490
+ const msg = asRecord(msgRaw);
3491
+ const ruleId = typeof msg.ruleId === "string" && msg.ruleId ? msg.ruleId : "parse-error";
3492
+ const message = typeof msg.message === "string" ? msg.message.trim() : ruleId;
3493
+ const severity = normalizeFindingSeverity(msg.severity === 2 ? "error" : "warning");
3494
+ const line = typeof msg.line === "number" ? msg.line : void 0;
3495
+ findings.push({
3496
+ tool: "eslint",
3497
+ ruleId,
3498
+ message,
3499
+ severity,
3500
+ path: path18,
3501
+ ...line !== void 0 ? { line } : {},
3502
+ key: findingKey("eslint", ruleId, path18)
3503
+ });
3504
+ }
3505
+ }
3506
+ return findings;
3507
+ }
3508
+ var NPM_AUDIT_SEVERITY = {
3509
+ critical: "blocker",
3510
+ high: "critical",
3511
+ moderate: "major",
3512
+ low: "minor",
3513
+ info: "info"
3514
+ };
3515
+ function parseNpmAudit(input) {
3516
+ const doc = asRecord(coerceJson(input));
3517
+ const vulns = asRecord(doc.vulnerabilities);
3518
+ const findings = [];
3519
+ for (const [name, vulnRaw] of Object.entries(vulns)) {
3520
+ const vuln = asRecord(vulnRaw);
3521
+ const sev = typeof vuln.severity === "string" ? vuln.severity.toLowerCase() : "info";
3522
+ const severity = NPM_AUDIT_SEVERITY[sev] ?? "info";
3523
+ const via = asArray(vuln.via);
3524
+ const firstAdvisory = via.map(asRecord).find((v) => typeof v.title === "string");
3525
+ const title = firstAdvisory && typeof firstAdvisory.title === "string" ? firstAdvisory.title : `Vulnerable dependency: ${name}`;
3526
+ const range = typeof vuln.range === "string" ? ` (affected range: ${vuln.range})` : "";
3527
+ findings.push({
3528
+ tool: "npm-audit",
3529
+ ruleId: name,
3530
+ message: `${title}${range}`,
3531
+ severity,
3532
+ path: "package.json",
3533
+ key: findingKey("npm-audit", name, "package.json")
3534
+ });
3535
+ }
3536
+ return findings;
3537
+ }
3538
+ function parseFindings(format, input, opts = {}) {
3539
+ switch (format) {
3540
+ case "sonar":
3541
+ return parseSonar(input);
3542
+ case "eslint":
3543
+ return parseEslintJson(input, opts);
3544
+ case "npm-audit":
3545
+ return parseNpmAudit(input);
3546
+ default:
3547
+ return parseSarif(input);
3548
+ }
3328
3549
  }
3329
3550
  function normalizeUri(uri) {
3330
3551
  return uri.replace(/^file:\/\//, "").replace(/^\.\//, "");
@@ -3440,6 +3661,23 @@ function suggestGate(precision, rejections, currentGate) {
3440
3661
  }
3441
3662
  return null;
3442
3663
  }
3664
+ function nullableMetricDelta(baseline, current) {
3665
+ if (baseline === null || current === null) return { baseline, current, delta: null };
3666
+ return { baseline: round33(baseline), current: round33(current), delta: round33(current - baseline) };
3667
+ }
3668
+ function compareGatePrecision(baseline, current) {
3669
+ const precision = nullableMetricDelta(baseline.precision, current.precision);
3670
+ const rejections = nullableMetricDelta(baseline.rejections, current.rejections);
3671
+ const falsePositivesIncreased = current.rejections > baseline.rejections;
3672
+ const precisionRegressed = precision.delta !== null && precision.delta < 0;
3673
+ return {
3674
+ precision,
3675
+ rejections,
3676
+ false_positives_increased: falsePositivesIncreased,
3677
+ precision_regressed: precisionRegressed,
3678
+ regressed: falsePositivesIncreased || precisionRegressed
3679
+ };
3680
+ }
3443
3681
 
3444
3682
  // src/dashboard.ts
3445
3683
  var MS_PER_DAY4 = 24 * 60 * 60 * 1e3;
@@ -3548,9 +3786,26 @@ function buildDashboard(memories, usage, options = {}) {
3548
3786
  );
3549
3787
  sensorRows.sort((a, b) => b.last_fired.localeCompare(a.last_fired));
3550
3788
  dormantRows.sort((a, b) => b.age_days - a.age_days);
3789
+ const eventLog = options.preventionEvents ?? [];
3790
+ const recurrence = computeRecurrence(eventLog);
3551
3791
  return {
3552
3792
  generated_at: now.toISOString(),
3553
3793
  inventory,
3794
+ prevention: {
3795
+ total_events: preventionEvents,
3796
+ memories_with_catches: preventionRows.length,
3797
+ top: preventionRows.sort((a, b) => b.prevented_count - a.prevented_count).slice(0, top),
3798
+ trend: computePreventionTrend(eventLog, now),
3799
+ recurrence: {
3800
+ ...recurrence,
3801
+ top: recurrence.top.slice(0, top)
3802
+ }
3803
+ },
3804
+ gate_precision: computeGatePrecision(
3805
+ eventLog,
3806
+ usage,
3807
+ options.antiPatternGate ?? "anchored"
3808
+ ),
3554
3809
  impact: { ...summarizeImpact(impactScores), top: impactRows.slice(0, top) },
3555
3810
  sensors: {
3556
3811
  total: sensorTotal,
@@ -3572,21 +3827,6 @@ function buildDashboard(memories, usage, options = {}) {
3572
3827
  decaying,
3573
3828
  top_dormant: dormantRows.slice(0, top)
3574
3829
  },
3575
- prevention: {
3576
- total_events: preventionEvents,
3577
- memories_with_catches: preventionRows.length,
3578
- top: preventionRows.sort((a, b) => b.prevented_count - a.prevented_count).slice(0, top),
3579
- trend: computePreventionTrend(options.preventionEvents ?? [], now),
3580
- recurrence: {
3581
- ...computeRecurrence(options.preventionEvents ?? []),
3582
- top: computeRecurrence(options.preventionEvents ?? []).top.slice(0, top)
3583
- }
3584
- },
3585
- gate_precision: computeGatePrecision(
3586
- options.preventionEvents ?? [],
3587
- usage,
3588
- options.antiPatternGate ?? "anchored"
3589
- ),
3590
3830
  corpus: {
3591
3831
  memory_files: inventory.total,
3592
3832
  body_chars: bodyChars,
@@ -3759,6 +3999,7 @@ function planConflictResolution(a, b) {
3759
3999
  var REVERT_RE = /^Revert\s+"(.+)"\s*$/i;
3760
4000
  var FIXUP_RE = /^(?:fixup!|hotfix[:!]|fix[:!]\s*revert|revert\s+revert)/i;
3761
4001
  var URGENT_FIX_RE = /\b(hotfix|urgent fix|emergency fix|critical fix|broke production|broken build)\b/i;
4002
+ var WORKAROUND_RE = /\b(workaround|work around|hack(?:y|ish)?|band[- ]?aid|temporary fix|temp fix|quick fix|kludge|monkey[- ]?patch|stop[- ]?gap|FIXME|XXX)\b/i;
3762
4003
  function slugify(text) {
3763
4004
  return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "reverted-change";
3764
4005
  }
@@ -3776,6 +4017,9 @@ function proposeSeedsFromCommits(commits, limit = 20) {
3776
4017
  } else if (FIXUP_RE.test(subject) || URGENT_FIX_RE.test(subject)) {
3777
4018
  what = subject.replace(FIXUP_RE, "").trim() || subject;
3778
4019
  kind = "fixup";
4020
+ } else if (WORKAROUND_RE.test(subject)) {
4021
+ what = subject;
4022
+ kind = "workaround";
3779
4023
  }
3780
4024
  if (!what || !kind) continue;
3781
4025
  const slug = slugify(what);
@@ -3784,7 +4028,7 @@ function proposeSeedsFromCommits(commits, limit = 20) {
3784
4028
  out.push({
3785
4029
  slug,
3786
4030
  what,
3787
- why_failed: kind === "revert" ? `This change was reverted in commit ${commit.sha} \u2014 it caused a regression and was backed out. Verify the root cause before re-attempting.` : `This area required an urgent fix (commit ${commit.sha}: "${subject}") \u2014 it shipped broken once. Treat changes here with extra care.`,
4031
+ why_failed: kind === "revert" ? `This change was reverted in commit ${commit.sha} \u2014 it caused a regression and was backed out. Verify the root cause before re-attempting.` : kind === "workaround" ? `This area carries a known workaround/stop-gap (commit ${commit.sha}: "${subject}") \u2014 the proper fix is still owed. Understand why the workaround exists before changing it.` : `This area required an urgent fix (commit ${commit.sha}: "${subject}") \u2014 it shipped broken once. Treat changes here with extra care.`,
3788
4032
  paths: (commit.files ?? []).slice(0, 8),
3789
4033
  source_sha: commit.sha,
3790
4034
  kind
@@ -3794,6 +4038,75 @@ function proposeSeedsFromCommits(commits, limit = 20) {
3794
4038
  return out;
3795
4039
  }
3796
4040
 
4041
+ // src/seed.ts
4042
+ var JS_DETECTORS = [
4043
+ ["nestjs", ["@nestjs/core"]],
4044
+ ["nextjs", ["next"]],
4045
+ ["remix", ["@remix-run/react", "@remix-run/node"]],
4046
+ ["react", ["react"]],
4047
+ ["express", ["express"]],
4048
+ ["fastify", ["fastify"]],
4049
+ ["prisma", ["@prisma/client", "prisma"]],
4050
+ ["drizzle", ["drizzle-orm"]],
4051
+ ["zustand", ["zustand"]],
4052
+ ["redux", ["@reduxjs/toolkit", "redux"]],
4053
+ ["reactquery", ["@tanstack/react-query", "react-query"]],
4054
+ ["trpc", ["@trpc/server", "@trpc/client"]],
4055
+ ["mongoose", ["mongoose"]],
4056
+ ["graphql", ["@apollo/client", "@apollo/server", "apollo-server", "graphql"]],
4057
+ ["vue", ["vue", "@vue/core"]],
4058
+ ["tailwind", ["tailwindcss"]],
4059
+ ["vite", ["vite"]],
4060
+ ["sveltekit", ["@sveltejs/kit"]],
4061
+ ["astro", ["astro"]],
4062
+ ["typescript", ["typescript"]],
4063
+ ["monorepo", ["turbo", "nx", "@nrwl/workspace", "@nx/workspace"]]
4064
+ ];
4065
+ var PYTHON_DETECTORS = [
4066
+ ["fastapi", /\bfastapi\b/i],
4067
+ ["django", /\bdjango\b/i],
4068
+ ["flask", /\bflask\b/i]
4069
+ ];
4070
+ function detectFromPackageJson(deps) {
4071
+ const detected = [];
4072
+ for (const [stack, signals] of JS_DETECTORS) {
4073
+ if (signals.some((s) => s in deps)) detected.push(stack);
4074
+ }
4075
+ if (detected.includes("nextjs") || detected.includes("remix")) {
4076
+ return detected.filter((s) => s !== "react");
4077
+ }
4078
+ return detected;
4079
+ }
4080
+ function detectFromRequirementsTxt(content) {
4081
+ return PYTHON_DETECTORS.filter(([, re]) => re.test(content)).map(([s]) => s);
4082
+ }
4083
+ function detectFromGoMod(content) {
4084
+ return /^\s*module\s+\S/m.test(content) ? ["go"] : [];
4085
+ }
4086
+ function detectFromPomXml(content) {
4087
+ return /org\.springframework|spring-boot/.test(content) ? ["spring"] : [];
4088
+ }
4089
+ function detectFromComposerJson(content) {
4090
+ return /laravel\/framework|illuminate\//.test(content) ? ["laravel"] : [];
4091
+ }
4092
+ function detectFromGemfile(content) {
4093
+ return /^\s*gem\s+["']rails["']/m.test(content) || /\brails\b/.test(content) ? ["rails"] : [];
4094
+ }
4095
+ function detectStacksFromManifests(input) {
4096
+ const seen = /* @__PURE__ */ new Set();
4097
+ const add = (stacks) => stacks.forEach((s) => seen.add(s));
4098
+ if (input.packageJsonDeps) add(detectFromPackageJson(input.packageJsonDeps));
4099
+ if (input.requirementsTxt) add(detectFromRequirementsTxt(input.requirementsTxt));
4100
+ if (input.goMod) add(detectFromGoMod(input.goMod));
4101
+ if (input.pomXml) add(detectFromPomXml(input.pomXml));
4102
+ if (input.composerJson) add(detectFromComposerJson(input.composerJson));
4103
+ if (input.gemfile) add(detectFromGemfile(input.gemfile));
4104
+ if (input.hasCsproj) add(["dotnet"]);
4105
+ if (input.hasDockerfile) add(["docker"]);
4106
+ if (input.hasTurboJson || input.hasNxJson) add(["monorepo"]);
4107
+ return Array.from(seen);
4108
+ }
4109
+
3797
4110
  // src/merge-memory.ts
3798
4111
  function safeParse(raw) {
3799
4112
  try {
@@ -3823,10 +4136,207 @@ function mergeMemoryVersions(ours, theirs) {
3823
4136
  if (cmp < 0) return { content: ours, winner: "ours", reason: `newer created_at (${ca})` };
3824
4137
  return { content: ours, winner: "ours", reason: "tie \u2014 kept ours" };
3825
4138
  }
4139
+
4140
+ // src/recap.ts
4141
+ function isAutoRecap(body) {
4142
+ return /Auto-captured session/i.test(body) || /\bEdited \d+ files? across \d+ tool calls?/i.test(body) || /\b\d+ tool calls?\b/i.test(body);
4143
+ }
4144
+ function compactAutoRecapBody(body, maxChars = 600) {
4145
+ if (!isAutoRecap(body)) return body;
4146
+ const goalMatch = body.match(/##+\s*Goal[^\n]*\n+([^\n]+)/i);
4147
+ const callsMatch = body.match(/Auto-captured session \(([^)]+)\)/i);
4148
+ const header = goalMatch?.[1]?.trim() ? `_${goalMatch[1].trim()}_` : callsMatch ? `_Auto-captured session (${callsMatch[1]})._` : "_Auto-captured session._";
4149
+ const discMatch = body.match(/##+\s*Discoveries[^\n]*\n([\s\S]*?)(?=\n##+\s|\n*$)/i);
4150
+ const discovery = discMatch?.[1]?.trim() ?? "";
4151
+ const trivialDiscovery = discovery === "" || /^no (new memories|surprising)/i.test(discovery) || /No new memories saved this session\.?$/i.test(discovery);
4152
+ if (trivialDiscovery) {
4153
+ return `${header}
4154
+
4155
+ _No notable discoveries captured. Run post_task / \`mem_session_end\` for a richer recap._`;
4156
+ }
4157
+ const trimmed = discovery.length > maxChars ? discovery.slice(0, maxChars) + "\u2026" : discovery;
4158
+ return `${header}
4159
+
4160
+ **Discoveries:**
4161
+ ${trimmed}`;
4162
+ }
4163
+
4164
+ // src/priority.ts
4165
+ var DEFAULT_PRIORITY_SIGNALS = {
4166
+ type: "",
4167
+ tags: [],
4168
+ requiresHumanApproval: false,
4169
+ directAnchor: false,
4170
+ directSymbol: false,
4171
+ exactTaskMatch: false,
4172
+ strongSemantic: false,
4173
+ usefulSemantic: false,
4174
+ moduleOrDomainMatch: false,
4175
+ tagTaskMatch: false
4176
+ };
4177
+ function prioritySignals(partial) {
4178
+ return { ...DEFAULT_PRIORITY_SIGNALS, ...partial };
4179
+ }
4180
+ function classifyMemoryPriority(signals) {
4181
+ const isNegative = signals.type === "attempt";
4182
+ const isSkill2 = signals.type === "skill";
4183
+ if (signals.requiresHumanApproval || signals.directAnchor || signals.directSymbol || isNegative && (signals.exactTaskMatch || signals.strongSemantic) || isSkill2 && (signals.exactTaskMatch || signals.strongSemantic)) {
4184
+ return "must_read";
4185
+ }
4186
+ if (isStackPackSeed({ tags: signals.tags }) || isEnvWorkaroundMemory({ tags: signals.tags })) {
4187
+ return "background";
4188
+ }
4189
+ if (isSkill2 || signals.moduleOrDomainMatch || signals.exactTaskMatch || signals.usefulSemantic || signals.tagTaskMatch) {
4190
+ return "useful";
4191
+ }
4192
+ return "background";
4193
+ }
4194
+ function priorityRank(priority) {
4195
+ return priority === "must_read" ? 3 : priority === "useful" ? 2 : 1;
4196
+ }
4197
+
4198
+ // src/bridges.ts
4199
+ var BRIDGE_TARGET_PATH = {
4200
+ claude: "CLAUDE.md",
4201
+ cursor: ".cursor/rules/haive-memories.mdc",
4202
+ cline: ".clinerules",
4203
+ windsurf: ".windsurfrules",
4204
+ continue: ".continuerules",
4205
+ cody: ".sourcegraph/cody-rules.md",
4206
+ zed: ".rules",
4207
+ roo: ".roo/rules/haive.md",
4208
+ gemini: "GEMINI.md",
4209
+ aider: "CONVENTIONS.md",
4210
+ agents: "AGENTS.md",
4211
+ copilot: ".github/copilot-instructions.md"
4212
+ };
4213
+ var BRIDGE_TARGETS = Object.keys(BRIDGE_TARGET_PATH);
4214
+ var BRIDGE_MARKERS = {
4215
+ memoriesStart: "<!-- haive:memories-start -->",
4216
+ memoriesEnd: "<!-- haive:memories-end -->",
4217
+ sensorsStart: "<!-- haive:sensors-start -->",
4218
+ sensorsEnd: "<!-- haive:sensors-end -->"
4219
+ };
4220
+ function bridgeMemorySummary(body) {
4221
+ const firstLine = body.split("\n").map((l) => l.replace(/^#+\s*/, "").trim()).find((l) => l.length > 0) ?? "";
4222
+ const oneLine = firstLine.replace(/\s+/g, " ");
4223
+ return oneLine.length > 140 ? oneLine.slice(0, 137) + "\u2026" : oneLine;
4224
+ }
4225
+ function prepareBridgeData(memories, sensors, opts) {
4226
+ const max = opts?.maxMemories ?? 8;
4227
+ const topMemories = memories.filter((m) => {
4228
+ const s = m.frontmatter.status;
4229
+ if (m.frontmatter.type === "session_recap") return false;
4230
+ if (m.frontmatter.tags?.includes("stack-pack") || m.frontmatter.tags?.includes("seed")) return false;
4231
+ return s === "validated" || s === "proposed";
4232
+ }).sort((a, b) => {
4233
+ const score = (m) => m.frontmatter.status === "validated" ? 2 : 1;
4234
+ return score(b) - score(a);
4235
+ }).slice(0, max).map((m) => ({
4236
+ id: m.frontmatter.id,
4237
+ scope: m.frontmatter.scope,
4238
+ type: m.frontmatter.type,
4239
+ summary: bridgeMemorySummary(m.body),
4240
+ paths: m.frontmatter.anchor?.paths ?? []
4241
+ }));
4242
+ const blockSensors = sensors.filter((s) => s.severity === "block");
4243
+ return { topMemories, blockSensors };
4244
+ }
4245
+ function renderMemoriesBlock(topMemories) {
4246
+ const lines = [
4247
+ BRIDGE_MARKERS.memoriesStart,
4248
+ "<!-- AUTO-GENERATED by haive bridges sync \u2014 do not edit between these markers -->",
4249
+ "<!-- Top memories \u2014 call get_briefing / mem_get for the full body. -->",
4250
+ ""
4251
+ ];
4252
+ if (topMemories.length === 0) {
4253
+ lines.push("_(no validated memories yet \u2014 run `haive sync` to populate)_");
4254
+ } else {
4255
+ for (const m of topMemories) {
4256
+ const scopeNote = m.paths.length > 0 ? ` _(applies to: ${m.paths.slice(0, 4).join(", ")}${m.paths.length > 4 ? ", \u2026" : ""})_` : "";
4257
+ lines.push(`- \`${m.id}\` (${m.scope}/${m.type}) \u2014 ${m.summary}${scopeNote}`);
4258
+ }
4259
+ }
4260
+ lines.push("", BRIDGE_MARKERS.memoriesEnd);
4261
+ return lines.join("\n");
4262
+ }
4263
+ function renderSensorsBlock(blockSensors) {
4264
+ if (blockSensors.length === 0) return "";
4265
+ const lines = [
4266
+ BRIDGE_MARKERS.sensorsStart,
4267
+ "<!-- AUTO-GENERATED by haive bridges sync \u2014 do not edit between these markers -->",
4268
+ "",
4269
+ "## Hard rules \u2014 hAIve block sensors",
4270
+ "",
4271
+ "The patterns below are blocked by the repo enforcement gate.",
4272
+ "Introducing them will fail the pre-commit check (`haive enforce check`).",
4273
+ ""
4274
+ ];
4275
+ for (const s of blockSensors) {
4276
+ const pathNote = s.paths.length > 0 ? ` _(applies to: ${s.paths.join(", ")})_` : "";
4277
+ lines.push(`- **${s.id}**${pathNote}: ${s.message}`);
4278
+ if (s.pattern) lines.push(` - Pattern: \`${s.pattern}\``);
4279
+ }
4280
+ lines.push("", BRIDGE_MARKERS.sensorsEnd);
4281
+ return lines.join("\n");
4282
+ }
4283
+ var HAIVE_PREAMBLE = "This repo uses **[hAIve](https://github.com/Doucs91/hAIve)** for shared context and enforcement.\n\n**Before editing** for a goal: call `get_briefing` (task + files/symbols) to load ranked context.\n**When an approach fails**: call `mem_tried` right away.\n**Before closing**: run `haive enforce finish` and capture learnings via the `post_task` prompt.\n\nIf `get_briefing` returns `action_required`, surface each item to the developer and wait for\nexplicit confirmation before modifying any code.";
4284
+ function renderMarkdownBridge(topMemories, blockSensors, title) {
4285
+ const parts = [
4286
+ `# ${title}`,
4287
+ "",
4288
+ HAIVE_PREAMBLE,
4289
+ "",
4290
+ "## Memories",
4291
+ "",
4292
+ renderMemoriesBlock(topMemories)
4293
+ ];
4294
+ const sensorsBlock = renderSensorsBlock(blockSensors);
4295
+ if (sensorsBlock) {
4296
+ parts.push("", sensorsBlock);
4297
+ }
4298
+ return parts.join("\n") + "\n";
4299
+ }
4300
+ function renderCursorBridge(topMemories, blockSensors) {
4301
+ const frontmatter = [
4302
+ "---",
4303
+ "description: hAIve shared memories & block sensors (auto-generated)",
4304
+ "alwaysApply: true",
4305
+ "---",
4306
+ ""
4307
+ ].join("\n");
4308
+ return frontmatter + renderMarkdownBridge(topMemories, blockSensors, "hAIve rules (Cursor)");
4309
+ }
4310
+ var FORMATTERS = {
4311
+ claude: (m, s) => renderMarkdownBridge(m, s, "CLAUDE.md \u2014 hAIve context"),
4312
+ cursor: (m, s) => renderCursorBridge(m, s),
4313
+ cline: (m, s) => renderMarkdownBridge(m, s, "hAIve rules (Cline)"),
4314
+ windsurf: (m, s) => renderMarkdownBridge(m, s, "hAIve rules (Windsurf)"),
4315
+ continue: (m, s) => renderMarkdownBridge(m, s, "hAIve rules (Continue)"),
4316
+ cody: (m, s) => renderMarkdownBridge(m, s, "hAIve rules (Cody / Sourcegraph)"),
4317
+ zed: (m, s) => renderMarkdownBridge(m, s, "hAIve rules (Zed)"),
4318
+ roo: (m, s) => renderMarkdownBridge(m, s, "hAIve rules (Roo Code)"),
4319
+ gemini: (m, s) => renderMarkdownBridge(m, s, "GEMINI.md \u2014 hAIve context"),
4320
+ aider: (m, s) => renderMarkdownBridge(m, s, "CONVENTIONS.md \u2014 hAIve context (Aider)"),
4321
+ agents: (m, s) => renderMarkdownBridge(m, s, "AGENTS.md \u2014 hAIve context"),
4322
+ copilot: (m, s) => renderMarkdownBridge(m, s, "hAIve rules (GitHub Copilot)")
4323
+ };
4324
+ function generateBridges(memories, sensors, opts) {
4325
+ const { topMemories, blockSensors } = prepareBridgeData(memories, sensors, opts);
4326
+ const targets = opts?.targets ?? BRIDGE_TARGETS;
4327
+ return targets.map((target) => ({
4328
+ target,
4329
+ path: BRIDGE_TARGET_PATH[target],
4330
+ content: FORMATTERS[target](topMemories, blockSensors)
4331
+ }));
4332
+ }
3826
4333
  export {
3827
4334
  AUTOPILOT_DEFAULTS,
3828
4335
  ActivationSchema,
3829
4336
  AnchorSchema,
4337
+ BRIDGE_MARKERS,
4338
+ BRIDGE_TARGETS,
4339
+ BRIDGE_TARGET_PATH,
3830
4340
  BRIEFING_MARKER_TTL_MS,
3831
4341
  BRIEFING_PRESET_DEFAULTS,
3832
4342
  CHARS_PER_TOKEN,
@@ -3839,6 +4349,8 @@ export {
3839
4349
  DEFAULT_CONFIDENCE_THRESHOLDS,
3840
4350
  DEFAULT_CONFIG,
3841
4351
  DEFAULT_DORMANT_DAYS,
4352
+ DEFAULT_PRIORITY_SIGNALS,
4353
+ ENV_WORKAROUND_TAGS,
3842
4354
  GUESSABLE_THRESHOLD,
3843
4355
  HAIVE_DIR,
3844
4356
  MEMORIES_DIR,
@@ -3867,8 +4379,11 @@ export {
3867
4379
  appendPreventionEvent,
3868
4380
  appendRuntimeJournalEntry,
3869
4381
  appendUsageEvent,
4382
+ applyFeedbackAdjustment,
4383
+ bridgeMemorySummary,
3870
4384
  briefingMarkerPath,
3871
4385
  briefingMarkersDir,
4386
+ briefingProofLine,
3872
4387
  buildCodeMap,
3873
4388
  buildCoverageIndex,
3874
4389
  buildDashboard,
@@ -3876,9 +4391,12 @@ export {
3876
4391
  buildFrontmatter,
3877
4392
  buildReport,
3878
4393
  bumpRead,
4394
+ classifyMemoryPriority,
3879
4395
  codeMapPath,
3880
4396
  collectTimelineEntries,
4397
+ compactAutoRecapBody,
3881
4398
  compareEvalReports,
4399
+ compareGatePrecision,
3882
4400
  compareImpact,
3883
4401
  compileRegexSensor,
3884
4402
  computeEvalTrend,
@@ -3889,6 +4407,7 @@ export {
3889
4407
  configPath,
3890
4408
  contractLockPath,
3891
4409
  deriveConfidence,
4410
+ detectStacksFromManifests,
3892
4411
  diffContract,
3893
4412
  diffHasDistinctiveOverlap,
3894
4413
  distinctiveCap,
@@ -3910,15 +4429,18 @@ export {
3910
4429
  findingBody,
3911
4430
  findingToDraft,
3912
4431
  firstMemoryOneLine,
4432
+ generateBridges,
3913
4433
  getUsage,
3914
4434
  globToRegExp,
3915
4435
  hasRecentBriefingMarker,
3916
4436
  hashProjectContext,
3917
4437
  inferModulesFromPaths,
3918
4438
  isAutoPromoteEligible,
4439
+ isAutoRecap,
3919
4440
  isCovered,
3920
4441
  isDecaying,
3921
4442
  isDistinctiveToken,
4443
+ isEnvWorkaroundMemory,
3922
4444
  isFreshIsoDate,
3923
4445
  isGlobPath,
3924
4446
  isLikelyGuessable,
@@ -3944,15 +4466,20 @@ export {
3944
4466
  normalizeFindingSeverity,
3945
4467
  normalizeSessionId,
3946
4468
  overallScore,
4469
+ parseEslintJson,
3947
4470
  parseFindings,
3948
4471
  parseMemory,
4472
+ parseNpmAudit,
3949
4473
  parseSarif,
3950
4474
  parseSince,
3951
4475
  parseSonar,
3952
4476
  pathsOverlap,
3953
4477
  pickSnippetNeedle,
3954
4478
  planConflictResolution,
4479
+ prepareBridgeData,
3955
4480
  preventionLogPath,
4481
+ priorityRank,
4482
+ prioritySignals,
3956
4483
  projectContextRecentlyEmitted,
3957
4484
  proposeSeedsFromCommits,
3958
4485
  pullCrossRepoSources,
@@ -3961,11 +4488,13 @@ export {
3961
4488
  readRecentBriefingMarker,
3962
4489
  readRuntimeJournalTail,
3963
4490
  readUsageEvents,
4491
+ recommendFeedbackAdjustment,
3964
4492
  recordApplied,
3965
4493
  recordPrevention,
3966
4494
  recordProjectContextEmission,
3967
4495
  recordRejection,
3968
4496
  relPathFrom,
4497
+ renderCaughtForYou,
3969
4498
  resolveBriefingBudget,
3970
4499
  resolveHaivePaths,
3971
4500
  resolveManifestFiles,
@@ -3989,6 +4518,7 @@ export {
3989
4518
  suggestGate,
3990
4519
  suggestSensorFromMemory,
3991
4520
  suggestTopicKey,
4521
+ summarizeCaughtForYou,
3992
4522
  summarizeImpact,
3993
4523
  synthesizeSelfEvalCases,
3994
4524
  titleFromBody,