@hiveai/core 0.10.2 → 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
@@ -16,6 +16,56 @@ declare const AnchorSchema: z.ZodObject<{
16
16
  paths?: string[] | undefined;
17
17
  symbols?: string[] | undefined;
18
18
  }>;
19
+ /**
20
+ * An executable check derived from a memory — the "feedback computational" layer.
21
+ *
22
+ * A `gotcha`/`attempt` is normally feedforward (text the agent reads). A sensor turns
23
+ * that lesson into a deterministic check: when a touched file matches `pattern`, the
24
+ * memory's warning fires regardless of semantic ranking. This closes the harness loop —
25
+ * a documented mistake becomes a permanent guardrail.
26
+ *
27
+ * Phase 1 implements `kind: "regex"` only. `shell`/`test` are reserved for a later phase
28
+ * (they require I/O and must run from the CLI, not core).
29
+ */
30
+ declare const SensorSchema: z.ZodObject<{
31
+ kind: z.ZodDefault<z.ZodEnum<["regex", "shell", "test"]>>;
32
+ /** Regex source (for kind=regex), matched against added diff lines / file content. */
33
+ pattern: z.ZodOptional<z.ZodString>;
34
+ /** Regex flags (e.g. "i", "m"). Ignored for non-regex kinds. */
35
+ flags: z.ZodOptional<z.ZodString>;
36
+ /** Shell/test command to run (for kind=shell|test). Executed by the CLI, never by core. */
37
+ command: z.ZodOptional<z.ZodString>;
38
+ /** Glob-ish path prefixes the sensor applies to. Falls back to the memory's anchor paths when empty. */
39
+ paths: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
40
+ /** LLM-facing self-correction message: what was done wrong and what to do instead. */
41
+ message: z.ZodString;
42
+ /** `warn` surfaces in review; `block` can hard-block the commit (only when the gate opts in). */
43
+ severity: z.ZodDefault<z.ZodEnum<["warn", "block"]>>;
44
+ /** True when hAIve generated this sensor automatically (vs. hand-authored). */
45
+ autogen: z.ZodDefault<z.ZodBoolean>;
46
+ /** ISO timestamp of the last time this sensor matched a diff. */
47
+ last_fired: z.ZodDefault<z.ZodNullable<z.ZodString>>;
48
+ }, "strip", z.ZodTypeAny, {
49
+ message: string;
50
+ paths: string[];
51
+ kind: "regex" | "shell" | "test";
52
+ severity: "warn" | "block";
53
+ autogen: boolean;
54
+ last_fired: string | null;
55
+ pattern?: string | undefined;
56
+ flags?: string | undefined;
57
+ command?: string | undefined;
58
+ }, {
59
+ message: string;
60
+ paths?: string[] | undefined;
61
+ kind?: "regex" | "shell" | "test" | undefined;
62
+ pattern?: string | undefined;
63
+ flags?: string | undefined;
64
+ command?: string | undefined;
65
+ severity?: "warn" | "block" | undefined;
66
+ autogen?: boolean | undefined;
67
+ last_fired?: string | null | undefined;
68
+ }>;
19
69
  declare const MemoryFrontmatterSchema: z.ZodEffects<z.ZodObject<{
20
70
  id: z.ZodString;
21
71
  scope: z.ZodDefault<z.ZodEnum<["personal", "team", "module", "shared"]>>;
@@ -35,6 +85,46 @@ declare const MemoryFrontmatterSchema: z.ZodEffects<z.ZodObject<{
35
85
  paths?: string[] | undefined;
36
86
  symbols?: string[] | undefined;
37
87
  }>>;
88
+ /** Optional executable check derived from this memory (feedback computational layer). */
89
+ sensor: z.ZodOptional<z.ZodObject<{
90
+ kind: z.ZodDefault<z.ZodEnum<["regex", "shell", "test"]>>;
91
+ /** Regex source (for kind=regex), matched against added diff lines / file content. */
92
+ pattern: z.ZodOptional<z.ZodString>;
93
+ /** Regex flags (e.g. "i", "m"). Ignored for non-regex kinds. */
94
+ flags: z.ZodOptional<z.ZodString>;
95
+ /** Shell/test command to run (for kind=shell|test). Executed by the CLI, never by core. */
96
+ command: z.ZodOptional<z.ZodString>;
97
+ /** Glob-ish path prefixes the sensor applies to. Falls back to the memory's anchor paths when empty. */
98
+ paths: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
99
+ /** LLM-facing self-correction message: what was done wrong and what to do instead. */
100
+ message: z.ZodString;
101
+ /** `warn` surfaces in review; `block` can hard-block the commit (only when the gate opts in). */
102
+ severity: z.ZodDefault<z.ZodEnum<["warn", "block"]>>;
103
+ /** True when hAIve generated this sensor automatically (vs. hand-authored). */
104
+ autogen: z.ZodDefault<z.ZodBoolean>;
105
+ /** ISO timestamp of the last time this sensor matched a diff. */
106
+ last_fired: z.ZodDefault<z.ZodNullable<z.ZodString>>;
107
+ }, "strip", z.ZodTypeAny, {
108
+ message: string;
109
+ paths: string[];
110
+ kind: "regex" | "shell" | "test";
111
+ severity: "warn" | "block";
112
+ autogen: boolean;
113
+ last_fired: string | null;
114
+ pattern?: string | undefined;
115
+ flags?: string | undefined;
116
+ command?: string | undefined;
117
+ }, {
118
+ message: string;
119
+ paths?: string[] | undefined;
120
+ kind?: "regex" | "shell" | "test" | undefined;
121
+ pattern?: string | undefined;
122
+ flags?: string | undefined;
123
+ command?: string | undefined;
124
+ severity?: "warn" | "block" | undefined;
125
+ autogen?: boolean | undefined;
126
+ last_fired?: string | null | undefined;
127
+ }>>;
38
128
  tags: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
39
129
  domain: z.ZodOptional<z.ZodString>;
40
130
  author: z.ZodOptional<z.ZodString>;
@@ -73,6 +163,17 @@ declare const MemoryFrontmatterSchema: z.ZodEffects<z.ZodObject<{
73
163
  revision_count: number;
74
164
  requires_human_approval: boolean;
75
165
  module?: string | undefined;
166
+ sensor?: {
167
+ message: string;
168
+ paths: string[];
169
+ kind: "regex" | "shell" | "test";
170
+ severity: "warn" | "block";
171
+ autogen: boolean;
172
+ last_fired: string | null;
173
+ pattern?: string | undefined;
174
+ flags?: string | undefined;
175
+ command?: string | undefined;
176
+ } | undefined;
76
177
  domain?: string | undefined;
77
178
  author?: string | undefined;
78
179
  topic?: string | undefined;
@@ -88,6 +189,17 @@ declare const MemoryFrontmatterSchema: z.ZodEffects<z.ZodObject<{
88
189
  paths?: string[] | undefined;
89
190
  symbols?: string[] | undefined;
90
191
  } | undefined;
192
+ sensor?: {
193
+ message: string;
194
+ paths?: string[] | undefined;
195
+ kind?: "regex" | "shell" | "test" | undefined;
196
+ pattern?: string | undefined;
197
+ flags?: string | undefined;
198
+ command?: string | undefined;
199
+ severity?: "warn" | "block" | undefined;
200
+ autogen?: boolean | undefined;
201
+ last_fired?: string | null | undefined;
202
+ } | undefined;
91
203
  tags?: string[] | undefined;
92
204
  domain?: string | undefined;
93
205
  author?: string | undefined;
@@ -119,6 +231,17 @@ declare const MemoryFrontmatterSchema: z.ZodEffects<z.ZodObject<{
119
231
  revision_count: number;
120
232
  requires_human_approval: boolean;
121
233
  module?: string | undefined;
234
+ sensor?: {
235
+ message: string;
236
+ paths: string[];
237
+ kind: "regex" | "shell" | "test";
238
+ severity: "warn" | "block";
239
+ autogen: boolean;
240
+ last_fired: string | null;
241
+ pattern?: string | undefined;
242
+ flags?: string | undefined;
243
+ command?: string | undefined;
244
+ } | undefined;
122
245
  domain?: string | undefined;
123
246
  author?: string | undefined;
124
247
  topic?: string | undefined;
@@ -134,6 +257,17 @@ declare const MemoryFrontmatterSchema: z.ZodEffects<z.ZodObject<{
134
257
  paths?: string[] | undefined;
135
258
  symbols?: string[] | undefined;
136
259
  } | undefined;
260
+ sensor?: {
261
+ message: string;
262
+ paths?: string[] | undefined;
263
+ kind?: "regex" | "shell" | "test" | undefined;
264
+ pattern?: string | undefined;
265
+ flags?: string | undefined;
266
+ command?: string | undefined;
267
+ severity?: "warn" | "block" | undefined;
268
+ autogen?: boolean | undefined;
269
+ last_fired?: string | null | undefined;
270
+ } | undefined;
137
271
  tags?: string[] | undefined;
138
272
  domain?: string | undefined;
139
273
  author?: string | undefined;
@@ -167,6 +301,7 @@ type MemoryScope = z.infer<typeof MemoryScopeSchema>;
167
301
  type MemoryStatus = z.infer<typeof MemoryStatusSchema>;
168
302
  type MemoryType = z.infer<typeof MemoryTypeSchema>;
169
303
  type Anchor = z.infer<typeof AnchorSchema>;
304
+ type Sensor = z.infer<typeof SensorSchema>;
170
305
  type MemoryFrontmatter = z.infer<typeof MemoryFrontmatterSchema>;
171
306
  interface Memory {
172
307
  frontmatter: MemoryFrontmatter;
@@ -191,6 +326,7 @@ declare function buildFrontmatter(input: {
191
326
  topic?: string;
192
327
  status?: MemoryFrontmatter["status"];
193
328
  relatedIds?: string[];
329
+ sensor?: Sensor;
194
330
  }): MemoryFrontmatter;
195
331
 
196
332
  declare const HAIVE_DIR = ".ai";
@@ -939,4 +1075,82 @@ interface RetirementSignal {
939
1075
  declare function retirementSignal(fm: MemoryFrontmatter, body?: string, now?: Date): RetirementSignal;
940
1076
  declare function isRetiredMemory(fm: MemoryFrontmatter, body?: string, now?: Date): boolean;
941
1077
 
942
- 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 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, aggregateUsage, allocateBudget, antiPatternGateParams, appendRuntimeJournalEntry, appendUsageEvent, briefingMarkerPath, briefingMarkersDir, buildCodeMap, buildFrontmatter, bumpRead, codeMapPath, collectTimelineEntries, 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, runtimeJournalPath, saveCodeMap, saveConfig, saveUsageIndex, serializeMemory, snapshotContract, specificityScore, stripPrivate, suggestTopicKey, tokenizeQuery, trackDependencies, trackReads, truncateToTokens, usageLogPath, usageLogSize, usagePath, verifyAnchor, watchContracts, writeBriefingMarker };
1078
+ /**
1079
+ * Sensors — the feedback *computational* layer of the harness.
1080
+ *
1081
+ * A memory's `sensor` turns a documented lesson (gotcha/attempt) into a deterministic
1082
+ * check. Unlike semantic anti-pattern matching (probabilistic, warmup-sensitive), a
1083
+ * regex sensor fires the same way every time, so a known mistake becomes a permanent
1084
+ * guardrail. Phase 1 supports `kind: "regex"` only — pure, no I/O. `shell`/`test`
1085
+ * sensors are recognized but not executed here (they must run from the CLI).
1086
+ */
1087
+ interface SensorHit {
1088
+ /** The memory id whose sensor matched. */
1089
+ memory_id: string;
1090
+ /** The sensor that matched. */
1091
+ sensor: Sensor;
1092
+ /** Project-relative file the match was found in (when known). */
1093
+ file?: string;
1094
+ /** The matched line (trimmed, capped) — useful for review output. */
1095
+ matched_line?: string;
1096
+ /** LLM-facing self-correction message carried from the sensor. */
1097
+ message: string;
1098
+ severity: Sensor["severity"];
1099
+ }
1100
+ /** A unit of code to scan: a file path plus the text to match against. */
1101
+ interface SensorTarget {
1102
+ /** Project-relative path (used for path scoping and reporting). */
1103
+ path: string;
1104
+ /**
1105
+ * Text to scan. For a diff, pass only the added lines (callers should pre-filter)
1106
+ * so a sensor fires on "you introduced the bad pattern", not "you touched a file
1107
+ * that merely mentions it".
1108
+ */
1109
+ content: string;
1110
+ }
1111
+ /**
1112
+ * Does this sensor apply to `path`? A sensor with no explicit `paths` (and whose
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.
1116
+ */
1117
+ declare function sensorAppliesToPath(sensor: Sensor, anchorPaths: string[], path: string): boolean;
1118
+ /**
1119
+ * Compile a regex sensor. Returns null when the sensor is not a runnable regex
1120
+ * (wrong kind, missing/invalid pattern) so callers can skip it safely.
1121
+ */
1122
+ declare function compileRegexSensor(sensor: Sensor): RegExp | null;
1123
+ /**
1124
+ * Run a single regex sensor over one target. Returns the first matching line as a hit,
1125
+ * or null. Deterministic and side-effect-free.
1126
+ */
1127
+ declare function runRegexSensor(memoryId: string, sensor: Sensor, target: SensorTarget): SensorHit | null;
1128
+ /**
1129
+ * Run every memory's regex sensor against every applicable target.
1130
+ *
1131
+ * Memories without a sensor, or with a non-regex sensor, are skipped (non-regex kinds
1132
+ * are the CLI's responsibility). At most one hit per (memory, file) pair is returned.
1133
+ */
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[];
1137
+ /**
1138
+ * Extract the added lines from a unified diff (lines starting with a single `+`,
1139
+ * excluding the `+++` file header). Mirrors the diff-handling already used by the
1140
+ * anti-pattern tokenizer so sensors fire on introductions, not mere mentions.
1141
+ */
1142
+ declare function addedLinesFromDiff(diff: string): string;
1143
+
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
@@ -27,6 +27,25 @@ var AnchorSchema = z.object({
27
27
  paths: z.array(z.string()).default([]),
28
28
  symbols: z.array(z.string()).default([])
29
29
  });
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. */
33
+ pattern: z.string().optional(),
34
+ /** Regex flags (e.g. "i", "m"). Ignored for non-regex kinds. */
35
+ flags: z.string().optional(),
36
+ /** Shell/test command to run (for kind=shell|test). Executed by the CLI, never by core. */
37
+ command: z.string().optional(),
38
+ /** Glob-ish path prefixes the sensor applies to. Falls back to the memory's anchor paths when empty. */
39
+ paths: z.array(z.string()).default([]),
40
+ /** LLM-facing self-correction message: what was done wrong and what to do instead. */
41
+ message: z.string().min(1),
42
+ /** `warn` surfaces in review; `block` can hard-block the commit (only when the gate opts in). */
43
+ severity: z.enum(["warn", "block"]).default("warn"),
44
+ /** True when hAIve generated this sensor automatically (vs. hand-authored). */
45
+ autogen: z.boolean().default(false),
46
+ /** ISO timestamp of the last time this sensor matched a diff. */
47
+ last_fired: z.string().nullable().default(null)
48
+ });
30
49
  var IsoDateString = z.union([z.string(), z.date()]).transform((v) => v instanceof Date ? v.toISOString() : v).pipe(z.string().datetime());
31
50
  var MemoryFrontmatterSchema = z.object({
32
51
  id: z.string().min(1),
@@ -35,6 +54,8 @@ var MemoryFrontmatterSchema = z.object({
35
54
  type: MemoryTypeSchema,
36
55
  status: MemoryStatusSchema.default("draft"),
37
56
  anchor: AnchorSchema.default({ paths: [], symbols: [] }),
57
+ /** Optional executable check derived from this memory (feedback computational layer). */
58
+ sensor: SensorSchema.optional(),
38
59
  tags: z.array(z.string()).default([]),
39
60
  domain: z.string().optional(),
40
61
  author: z.string().optional(),
@@ -127,6 +148,7 @@ function buildFrontmatter(input) {
127
148
  created_at: now.toISOString(),
128
149
  expires_when: null,
129
150
  topic: input.topic,
151
+ sensor: input.sensor,
130
152
  revision_count: 0,
131
153
  related_ids: input.relatedIds ?? []
132
154
  });
@@ -2442,6 +2464,203 @@ function retirementSignal(fm, body = "", now = /* @__PURE__ */ new Date()) {
2442
2464
  function isRetiredMemory(fm, body = "", now = /* @__PURE__ */ new Date()) {
2443
2465
  return retirementSignal(fm, body, now).retired;
2444
2466
  }
2467
+
2468
+ // src/sensors.ts
2469
+ function normalizeProjectPath(value) {
2470
+ return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^[ab]\//, "").replace(/\/+$/g, "");
2471
+ }
2472
+ function sensorAppliesToPath(sensor, anchorPaths, path15) {
2473
+ const scopes = sensor.paths.length > 0 ? sensor.paths : anchorPaths;
2474
+ if (scopes.length === 0) return true;
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
+ });
2481
+ }
2482
+ function compileRegexSensor(sensor) {
2483
+ if (sensor.kind !== "regex" || !sensor.pattern) return null;
2484
+ try {
2485
+ const flags = new Set(["m", ...(sensor.flags ?? "").split("")].filter(Boolean));
2486
+ return new RegExp(sensor.pattern, [...flags].join(""));
2487
+ } catch {
2488
+ return null;
2489
+ }
2490
+ }
2491
+ function runRegexSensor(memoryId, sensor, target) {
2492
+ const re = compileRegexSensor(sensor);
2493
+ if (!re) return null;
2494
+ for (const rawLine of target.content.split("\n")) {
2495
+ re.lastIndex = 0;
2496
+ if (re.test(rawLine)) {
2497
+ return {
2498
+ memory_id: memoryId,
2499
+ sensor,
2500
+ file: target.path,
2501
+ matched_line: rawLine.trim().slice(0, 200),
2502
+ message: sensor.message,
2503
+ severity: sensor.severity
2504
+ };
2505
+ }
2506
+ }
2507
+ return null;
2508
+ }
2509
+ function runSensors(memories, targets) {
2510
+ const hits = [];
2511
+ for (const memory of memories) {
2512
+ const sensor = memory.frontmatter.sensor;
2513
+ if (!sensor || sensor.kind !== "regex") continue;
2514
+ const anchorPaths = memory.frontmatter.anchor.paths;
2515
+ for (const target of targets) {
2516
+ if (!sensorAppliesToPath(sensor, anchorPaths, target.path)) continue;
2517
+ const hit = runRegexSensor(memory.frontmatter.id, sensor, target);
2518
+ if (hit) hits.push(hit);
2519
+ }
2520
+ }
2521
+ return hits;
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
+ }
2552
+ function addedLinesFromDiff(diff) {
2553
+ const targets = sensorTargetsFromDiff(diff);
2554
+ if (targets.length > 0) return targets.map((target) => target.content).join("\n");
2555
+ return diff.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).map((l) => l.slice(1)).join("\n");
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
+ }
2445
2664
  export {
2446
2665
  AUTOPILOT_DEFAULTS,
2447
2666
  AnchorSchema,
@@ -2466,9 +2685,11 @@ export {
2466
2685
  RUNTIME_JOURNAL_FILENAME,
2467
2686
  SESSION_RECAP_TTL_MS,
2468
2687
  STACK_PACK_TAG,
2688
+ SensorSchema,
2469
2689
  USAGE_FILE,
2470
2690
  USAGE_LOG_DIR,
2471
2691
  USAGE_LOG_FILE,
2692
+ addedLinesFromDiff,
2472
2693
  aggregateUsage,
2473
2694
  allocateBudget,
2474
2695
  antiPatternGateParams,
@@ -2481,6 +2702,7 @@ export {
2481
2702
  bumpRead,
2482
2703
  codeMapPath,
2483
2704
  collectTimelineEntries,
2705
+ compileRegexSensor,
2484
2706
  configPath,
2485
2707
  contractLockPath,
2486
2708
  deriveConfidence,
@@ -2536,14 +2758,19 @@ export {
2536
2758
  resolveManifestFiles,
2537
2759
  resolveProjectInfo,
2538
2760
  retirementSignal,
2761
+ runRegexSensor,
2762
+ runSensors,
2539
2763
  runtimeJournalPath,
2540
2764
  saveCodeMap,
2541
2765
  saveConfig,
2542
2766
  saveUsageIndex,
2767
+ sensorAppliesToPath,
2768
+ sensorTargetsFromDiff,
2543
2769
  serializeMemory,
2544
2770
  snapshotContract,
2545
2771
  specificityScore,
2546
2772
  stripPrivate,
2773
+ suggestSensorFromMemory,
2547
2774
  suggestTopicKey,
2548
2775
  tokenizeQuery,
2549
2776
  trackDependencies,