@hivelore/core 0.30.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/LICENSE +21 -0
- package/README.md +189 -0
- package/dist/index.d.ts +2752 -0
- package/dist/index.js +5586 -0
- package/dist/index.js.map +1 -0
- package/grammars/NOTICE.md +18 -0
- package/grammars/tree-sitter-c_sharp.wasm +0 -0
- package/grammars/tree-sitter-go.wasm +0 -0
- package/grammars/tree-sitter-java.wasm +0 -0
- package/grammars/tree-sitter-javascript.wasm +0 -0
- package/grammars/tree-sitter-php.wasm +0 -0
- package/grammars/tree-sitter-python.wasm +0 -0
- package/grammars/tree-sitter-ruby.wasm +0 -0
- package/grammars/tree-sitter-rust.wasm +0 -0
- package/grammars/tree-sitter-tsx.wasm +0 -0
- package/grammars/tree-sitter-typescript.wasm +0 -0
- package/package.json +53 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2752 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
declare const MemoryScopeSchema: z.ZodEnum<["personal", "team", "module", "shared"]>;
|
|
4
|
+
declare const MemoryStatusSchema: z.ZodEnum<["draft", "proposed", "validated", "deprecated", "stale", "rejected"]>;
|
|
5
|
+
declare const MemoryTypeSchema: z.ZodEnum<["convention", "decision", "gotcha", "architecture", "glossary", "skill", "attempt", "session_recap"]>;
|
|
6
|
+
declare const AnchorSchema: z.ZodObject<{
|
|
7
|
+
commit: z.ZodOptional<z.ZodString>;
|
|
8
|
+
paths: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
9
|
+
symbols: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
10
|
+
}, "strip", z.ZodTypeAny, {
|
|
11
|
+
paths: string[];
|
|
12
|
+
symbols: string[];
|
|
13
|
+
commit?: string | undefined;
|
|
14
|
+
}, {
|
|
15
|
+
commit?: string | undefined;
|
|
16
|
+
paths?: string[] | undefined;
|
|
17
|
+
symbols?: string[] | undefined;
|
|
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
|
+
/**
|
|
35
|
+
* Optional "correct-usage" regex (kind=regex). When `pattern` (the risky call) matches but this
|
|
36
|
+
* regex ALSO appears within a small window around the match, the catch is SUPPRESSED — the diff
|
|
37
|
+
* already includes the required companion. Encodes "X without Y": `pattern`=X (e.g. the API call),
|
|
38
|
+
* `absent`=Y (e.g. the required option). This is what lets an autogenerated sensor discriminate the
|
|
39
|
+
* faulty usage from the correct one instead of firing on every call — and makes it safe to promote.
|
|
40
|
+
*/
|
|
41
|
+
absent: z.ZodOptional<z.ZodString>;
|
|
42
|
+
/** Regex flags (e.g. "i", "m"). Ignored for non-regex kinds. */
|
|
43
|
+
flags: z.ZodOptional<z.ZodString>;
|
|
44
|
+
/** Shell/test command to run (for kind=shell|test). Executed by the CLI, never by core. */
|
|
45
|
+
command: z.ZodOptional<z.ZodString>;
|
|
46
|
+
/** Glob-ish path prefixes the sensor applies to. Falls back to the memory's anchor paths when empty. */
|
|
47
|
+
paths: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
48
|
+
/** LLM-facing self-correction message: what was done wrong and what to do instead. */
|
|
49
|
+
message: z.ZodString;
|
|
50
|
+
/** `warn` surfaces in review; `block` can hard-block the commit (only when the gate opts in). */
|
|
51
|
+
severity: z.ZodDefault<z.ZodEnum<["warn", "block"]>>;
|
|
52
|
+
/** True when Hivelore generated this sensor automatically (vs. hand-authored). */
|
|
53
|
+
autogen: z.ZodDefault<z.ZodBoolean>;
|
|
54
|
+
/** ISO timestamp of the last time this sensor matched a diff. */
|
|
55
|
+
last_fired: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
56
|
+
}, "strip", z.ZodTypeAny, {
|
|
57
|
+
message: string;
|
|
58
|
+
paths: string[];
|
|
59
|
+
kind: "regex" | "shell" | "test";
|
|
60
|
+
severity: "warn" | "block";
|
|
61
|
+
autogen: boolean;
|
|
62
|
+
last_fired: string | null;
|
|
63
|
+
pattern?: string | undefined;
|
|
64
|
+
absent?: string | undefined;
|
|
65
|
+
flags?: string | undefined;
|
|
66
|
+
command?: string | undefined;
|
|
67
|
+
}, {
|
|
68
|
+
message: string;
|
|
69
|
+
paths?: string[] | undefined;
|
|
70
|
+
kind?: "regex" | "shell" | "test" | undefined;
|
|
71
|
+
pattern?: string | undefined;
|
|
72
|
+
absent?: string | undefined;
|
|
73
|
+
flags?: string | undefined;
|
|
74
|
+
command?: string | undefined;
|
|
75
|
+
severity?: "warn" | "block" | undefined;
|
|
76
|
+
autogen?: boolean | undefined;
|
|
77
|
+
last_fired?: string | null | undefined;
|
|
78
|
+
}>;
|
|
79
|
+
/**
|
|
80
|
+
* Progressive-disclosure activation triggers for a `skill` memory.
|
|
81
|
+
*
|
|
82
|
+
* A skill is a reusable playbook (feedforward harness guide). Injecting every skill
|
|
83
|
+
* on every briefing bloats the context and dilutes signal (the "instruction budget"
|
|
84
|
+
* problem). An `activation` block makes a skill surface ONLY when it is relevant:
|
|
85
|
+
* its keywords match the task, or its globs match the files being edited. A skill
|
|
86
|
+
* that defines `activation` and matches none of it is suppressed from the briefing;
|
|
87
|
+
* a skill with no `activation` block keeps the legacy always-eligible behavior.
|
|
88
|
+
*/
|
|
89
|
+
declare const ActivationSchema: z.ZodObject<{
|
|
90
|
+
/** Case-insensitive substrings matched against the task text. */
|
|
91
|
+
keywords: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
92
|
+
/** Glob-ish path patterns matched against the files being edited. */
|
|
93
|
+
globs: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
94
|
+
/** Always activate (rare — for truly universal playbooks). */
|
|
95
|
+
always: z.ZodDefault<z.ZodBoolean>;
|
|
96
|
+
}, "strip", z.ZodTypeAny, {
|
|
97
|
+
keywords: string[];
|
|
98
|
+
globs: string[];
|
|
99
|
+
always: boolean;
|
|
100
|
+
}, {
|
|
101
|
+
keywords?: string[] | undefined;
|
|
102
|
+
globs?: string[] | undefined;
|
|
103
|
+
always?: boolean | undefined;
|
|
104
|
+
}>;
|
|
105
|
+
declare const MemoryFrontmatterSchema: z.ZodEffects<z.ZodObject<{
|
|
106
|
+
id: z.ZodString;
|
|
107
|
+
scope: z.ZodDefault<z.ZodEnum<["personal", "team", "module", "shared"]>>;
|
|
108
|
+
module: z.ZodOptional<z.ZodString>;
|
|
109
|
+
type: z.ZodEnum<["convention", "decision", "gotcha", "architecture", "glossary", "skill", "attempt", "session_recap"]>;
|
|
110
|
+
status: z.ZodDefault<z.ZodEnum<["draft", "proposed", "validated", "deprecated", "stale", "rejected"]>>;
|
|
111
|
+
anchor: z.ZodDefault<z.ZodObject<{
|
|
112
|
+
commit: z.ZodOptional<z.ZodString>;
|
|
113
|
+
paths: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
114
|
+
symbols: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
115
|
+
}, "strip", z.ZodTypeAny, {
|
|
116
|
+
paths: string[];
|
|
117
|
+
symbols: string[];
|
|
118
|
+
commit?: string | undefined;
|
|
119
|
+
}, {
|
|
120
|
+
commit?: string | undefined;
|
|
121
|
+
paths?: string[] | undefined;
|
|
122
|
+
symbols?: string[] | undefined;
|
|
123
|
+
}>>;
|
|
124
|
+
/** Optional executable check derived from this memory (feedback computational layer). */
|
|
125
|
+
sensor: z.ZodOptional<z.ZodObject<{
|
|
126
|
+
kind: z.ZodDefault<z.ZodEnum<["regex", "shell", "test"]>>;
|
|
127
|
+
/** Regex source (for kind=regex), matched against added diff lines / file content. */
|
|
128
|
+
pattern: z.ZodOptional<z.ZodString>;
|
|
129
|
+
/**
|
|
130
|
+
* Optional "correct-usage" regex (kind=regex). When `pattern` (the risky call) matches but this
|
|
131
|
+
* regex ALSO appears within a small window around the match, the catch is SUPPRESSED — the diff
|
|
132
|
+
* already includes the required companion. Encodes "X without Y": `pattern`=X (e.g. the API call),
|
|
133
|
+
* `absent`=Y (e.g. the required option). This is what lets an autogenerated sensor discriminate the
|
|
134
|
+
* faulty usage from the correct one instead of firing on every call — and makes it safe to promote.
|
|
135
|
+
*/
|
|
136
|
+
absent: z.ZodOptional<z.ZodString>;
|
|
137
|
+
/** Regex flags (e.g. "i", "m"). Ignored for non-regex kinds. */
|
|
138
|
+
flags: z.ZodOptional<z.ZodString>;
|
|
139
|
+
/** Shell/test command to run (for kind=shell|test). Executed by the CLI, never by core. */
|
|
140
|
+
command: z.ZodOptional<z.ZodString>;
|
|
141
|
+
/** Glob-ish path prefixes the sensor applies to. Falls back to the memory's anchor paths when empty. */
|
|
142
|
+
paths: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
143
|
+
/** LLM-facing self-correction message: what was done wrong and what to do instead. */
|
|
144
|
+
message: z.ZodString;
|
|
145
|
+
/** `warn` surfaces in review; `block` can hard-block the commit (only when the gate opts in). */
|
|
146
|
+
severity: z.ZodDefault<z.ZodEnum<["warn", "block"]>>;
|
|
147
|
+
/** True when Hivelore generated this sensor automatically (vs. hand-authored). */
|
|
148
|
+
autogen: z.ZodDefault<z.ZodBoolean>;
|
|
149
|
+
/** ISO timestamp of the last time this sensor matched a diff. */
|
|
150
|
+
last_fired: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
151
|
+
}, "strip", z.ZodTypeAny, {
|
|
152
|
+
message: string;
|
|
153
|
+
paths: string[];
|
|
154
|
+
kind: "regex" | "shell" | "test";
|
|
155
|
+
severity: "warn" | "block";
|
|
156
|
+
autogen: boolean;
|
|
157
|
+
last_fired: string | null;
|
|
158
|
+
pattern?: string | undefined;
|
|
159
|
+
absent?: string | undefined;
|
|
160
|
+
flags?: string | undefined;
|
|
161
|
+
command?: string | undefined;
|
|
162
|
+
}, {
|
|
163
|
+
message: string;
|
|
164
|
+
paths?: string[] | undefined;
|
|
165
|
+
kind?: "regex" | "shell" | "test" | undefined;
|
|
166
|
+
pattern?: string | undefined;
|
|
167
|
+
absent?: string | undefined;
|
|
168
|
+
flags?: string | undefined;
|
|
169
|
+
command?: string | undefined;
|
|
170
|
+
severity?: "warn" | "block" | undefined;
|
|
171
|
+
autogen?: boolean | undefined;
|
|
172
|
+
last_fired?: string | null | undefined;
|
|
173
|
+
}>>;
|
|
174
|
+
/** Optional progressive-disclosure triggers — only meaningful for `type: skill`. */
|
|
175
|
+
activation: z.ZodOptional<z.ZodObject<{
|
|
176
|
+
/** Case-insensitive substrings matched against the task text. */
|
|
177
|
+
keywords: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
178
|
+
/** Glob-ish path patterns matched against the files being edited. */
|
|
179
|
+
globs: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
180
|
+
/** Always activate (rare — for truly universal playbooks). */
|
|
181
|
+
always: z.ZodDefault<z.ZodBoolean>;
|
|
182
|
+
}, "strip", z.ZodTypeAny, {
|
|
183
|
+
keywords: string[];
|
|
184
|
+
globs: string[];
|
|
185
|
+
always: boolean;
|
|
186
|
+
}, {
|
|
187
|
+
keywords?: string[] | undefined;
|
|
188
|
+
globs?: string[] | undefined;
|
|
189
|
+
always?: boolean | undefined;
|
|
190
|
+
}>>;
|
|
191
|
+
tags: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
192
|
+
domain: z.ZodOptional<z.ZodString>;
|
|
193
|
+
author: z.ZodOptional<z.ZodString>;
|
|
194
|
+
created_at: z.ZodPipeline<z.ZodEffects<z.ZodUnion<[z.ZodString, z.ZodDate]>, string, string | Date>, z.ZodString>;
|
|
195
|
+
expires_when: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
196
|
+
verified_at: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
197
|
+
stale_reason: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
198
|
+
related_ids: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
199
|
+
last_read_at: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
200
|
+
topic: z.ZodOptional<z.ZodString>;
|
|
201
|
+
revision_count: z.ZodDefault<z.ZodNumber>;
|
|
202
|
+
/**
|
|
203
|
+
* When true, the AI MUST NOT act on this memory autonomously.
|
|
204
|
+
* It must surface the information to the human developer and wait
|
|
205
|
+
* for explicit confirmation before modifying any code.
|
|
206
|
+
* Used for cross-repo breaking changes, dependency bumps, contract diffs.
|
|
207
|
+
*/
|
|
208
|
+
requires_human_approval: z.ZodDefault<z.ZodBoolean>;
|
|
209
|
+
/**
|
|
210
|
+
* Provenance of the memory's `validated` status — who/what last trusted it:
|
|
211
|
+
* "human" — a person explicitly approved it (CLI `memory approve`)
|
|
212
|
+
* "agent" — an AI agent explicitly approved it (MCP `mem_approve`)
|
|
213
|
+
* "auto" — an automatic rule trusted it without review (auto-promotion by read
|
|
214
|
+
* count/time, or `defaultStatus: validated` on creation)
|
|
215
|
+
* null when the memory is not yet validated, or on legacy memories written before this field.
|
|
216
|
+
* Lets a human distinguish reviewed knowledge from AI/auto-trusted knowledge.
|
|
217
|
+
*/
|
|
218
|
+
validated_by: z.ZodDefault<z.ZodNullable<z.ZodEnum<["human", "agent", "auto"]>>>;
|
|
219
|
+
}, "strip", z.ZodTypeAny, {
|
|
220
|
+
type: "convention" | "decision" | "gotcha" | "architecture" | "glossary" | "skill" | "attempt" | "session_recap";
|
|
221
|
+
status: "draft" | "proposed" | "validated" | "deprecated" | "stale" | "rejected";
|
|
222
|
+
id: string;
|
|
223
|
+
scope: "personal" | "team" | "module" | "shared";
|
|
224
|
+
anchor: {
|
|
225
|
+
paths: string[];
|
|
226
|
+
symbols: string[];
|
|
227
|
+
commit?: string | undefined;
|
|
228
|
+
};
|
|
229
|
+
tags: string[];
|
|
230
|
+
created_at: string;
|
|
231
|
+
expires_when: string | null;
|
|
232
|
+
verified_at: string | null;
|
|
233
|
+
stale_reason: string | null;
|
|
234
|
+
related_ids: string[];
|
|
235
|
+
last_read_at: string | null;
|
|
236
|
+
revision_count: number;
|
|
237
|
+
requires_human_approval: boolean;
|
|
238
|
+
validated_by: "human" | "agent" | "auto" | null;
|
|
239
|
+
module?: string | undefined;
|
|
240
|
+
sensor?: {
|
|
241
|
+
message: string;
|
|
242
|
+
paths: string[];
|
|
243
|
+
kind: "regex" | "shell" | "test";
|
|
244
|
+
severity: "warn" | "block";
|
|
245
|
+
autogen: boolean;
|
|
246
|
+
last_fired: string | null;
|
|
247
|
+
pattern?: string | undefined;
|
|
248
|
+
absent?: string | undefined;
|
|
249
|
+
flags?: string | undefined;
|
|
250
|
+
command?: string | undefined;
|
|
251
|
+
} | undefined;
|
|
252
|
+
activation?: {
|
|
253
|
+
keywords: string[];
|
|
254
|
+
globs: string[];
|
|
255
|
+
always: boolean;
|
|
256
|
+
} | undefined;
|
|
257
|
+
domain?: string | undefined;
|
|
258
|
+
author?: string | undefined;
|
|
259
|
+
topic?: string | undefined;
|
|
260
|
+
}, {
|
|
261
|
+
type: "convention" | "decision" | "gotcha" | "architecture" | "glossary" | "skill" | "attempt" | "session_recap";
|
|
262
|
+
id: string;
|
|
263
|
+
created_at: string | Date;
|
|
264
|
+
module?: string | undefined;
|
|
265
|
+
status?: "draft" | "proposed" | "validated" | "deprecated" | "stale" | "rejected" | undefined;
|
|
266
|
+
scope?: "personal" | "team" | "module" | "shared" | undefined;
|
|
267
|
+
anchor?: {
|
|
268
|
+
commit?: string | undefined;
|
|
269
|
+
paths?: string[] | undefined;
|
|
270
|
+
symbols?: string[] | undefined;
|
|
271
|
+
} | undefined;
|
|
272
|
+
sensor?: {
|
|
273
|
+
message: string;
|
|
274
|
+
paths?: string[] | undefined;
|
|
275
|
+
kind?: "regex" | "shell" | "test" | undefined;
|
|
276
|
+
pattern?: string | undefined;
|
|
277
|
+
absent?: string | undefined;
|
|
278
|
+
flags?: string | undefined;
|
|
279
|
+
command?: string | undefined;
|
|
280
|
+
severity?: "warn" | "block" | undefined;
|
|
281
|
+
autogen?: boolean | undefined;
|
|
282
|
+
last_fired?: string | null | undefined;
|
|
283
|
+
} | undefined;
|
|
284
|
+
activation?: {
|
|
285
|
+
keywords?: string[] | undefined;
|
|
286
|
+
globs?: string[] | undefined;
|
|
287
|
+
always?: boolean | undefined;
|
|
288
|
+
} | undefined;
|
|
289
|
+
tags?: string[] | undefined;
|
|
290
|
+
domain?: string | undefined;
|
|
291
|
+
author?: string | undefined;
|
|
292
|
+
expires_when?: string | null | undefined;
|
|
293
|
+
verified_at?: string | null | undefined;
|
|
294
|
+
stale_reason?: string | null | undefined;
|
|
295
|
+
related_ids?: string[] | undefined;
|
|
296
|
+
last_read_at?: string | null | undefined;
|
|
297
|
+
topic?: string | undefined;
|
|
298
|
+
revision_count?: number | undefined;
|
|
299
|
+
requires_human_approval?: boolean | undefined;
|
|
300
|
+
validated_by?: "human" | "agent" | "auto" | null | undefined;
|
|
301
|
+
}>, {
|
|
302
|
+
type: "convention" | "decision" | "gotcha" | "architecture" | "glossary" | "skill" | "attempt" | "session_recap";
|
|
303
|
+
status: "draft" | "proposed" | "validated" | "deprecated" | "stale" | "rejected";
|
|
304
|
+
id: string;
|
|
305
|
+
scope: "personal" | "team" | "module" | "shared";
|
|
306
|
+
anchor: {
|
|
307
|
+
paths: string[];
|
|
308
|
+
symbols: string[];
|
|
309
|
+
commit?: string | undefined;
|
|
310
|
+
};
|
|
311
|
+
tags: string[];
|
|
312
|
+
created_at: string;
|
|
313
|
+
expires_when: string | null;
|
|
314
|
+
verified_at: string | null;
|
|
315
|
+
stale_reason: string | null;
|
|
316
|
+
related_ids: string[];
|
|
317
|
+
last_read_at: string | null;
|
|
318
|
+
revision_count: number;
|
|
319
|
+
requires_human_approval: boolean;
|
|
320
|
+
validated_by: "human" | "agent" | "auto" | null;
|
|
321
|
+
module?: string | undefined;
|
|
322
|
+
sensor?: {
|
|
323
|
+
message: string;
|
|
324
|
+
paths: string[];
|
|
325
|
+
kind: "regex" | "shell" | "test";
|
|
326
|
+
severity: "warn" | "block";
|
|
327
|
+
autogen: boolean;
|
|
328
|
+
last_fired: string | null;
|
|
329
|
+
pattern?: string | undefined;
|
|
330
|
+
absent?: string | undefined;
|
|
331
|
+
flags?: string | undefined;
|
|
332
|
+
command?: string | undefined;
|
|
333
|
+
} | undefined;
|
|
334
|
+
activation?: {
|
|
335
|
+
keywords: string[];
|
|
336
|
+
globs: string[];
|
|
337
|
+
always: boolean;
|
|
338
|
+
} | undefined;
|
|
339
|
+
domain?: string | undefined;
|
|
340
|
+
author?: string | undefined;
|
|
341
|
+
topic?: string | undefined;
|
|
342
|
+
}, {
|
|
343
|
+
type: "convention" | "decision" | "gotcha" | "architecture" | "glossary" | "skill" | "attempt" | "session_recap";
|
|
344
|
+
id: string;
|
|
345
|
+
created_at: string | Date;
|
|
346
|
+
module?: string | undefined;
|
|
347
|
+
status?: "draft" | "proposed" | "validated" | "deprecated" | "stale" | "rejected" | undefined;
|
|
348
|
+
scope?: "personal" | "team" | "module" | "shared" | undefined;
|
|
349
|
+
anchor?: {
|
|
350
|
+
commit?: string | undefined;
|
|
351
|
+
paths?: string[] | undefined;
|
|
352
|
+
symbols?: string[] | undefined;
|
|
353
|
+
} | undefined;
|
|
354
|
+
sensor?: {
|
|
355
|
+
message: string;
|
|
356
|
+
paths?: string[] | undefined;
|
|
357
|
+
kind?: "regex" | "shell" | "test" | undefined;
|
|
358
|
+
pattern?: string | undefined;
|
|
359
|
+
absent?: string | undefined;
|
|
360
|
+
flags?: string | undefined;
|
|
361
|
+
command?: string | undefined;
|
|
362
|
+
severity?: "warn" | "block" | undefined;
|
|
363
|
+
autogen?: boolean | undefined;
|
|
364
|
+
last_fired?: string | null | undefined;
|
|
365
|
+
} | undefined;
|
|
366
|
+
activation?: {
|
|
367
|
+
keywords?: string[] | undefined;
|
|
368
|
+
globs?: string[] | undefined;
|
|
369
|
+
always?: boolean | undefined;
|
|
370
|
+
} | undefined;
|
|
371
|
+
tags?: string[] | undefined;
|
|
372
|
+
domain?: string | undefined;
|
|
373
|
+
author?: string | undefined;
|
|
374
|
+
expires_when?: string | null | undefined;
|
|
375
|
+
verified_at?: string | null | undefined;
|
|
376
|
+
stale_reason?: string | null | undefined;
|
|
377
|
+
related_ids?: string[] | undefined;
|
|
378
|
+
last_read_at?: string | null | undefined;
|
|
379
|
+
topic?: string | undefined;
|
|
380
|
+
revision_count?: number | undefined;
|
|
381
|
+
requires_human_approval?: boolean | undefined;
|
|
382
|
+
validated_by?: "human" | "agent" | "auto" | null | undefined;
|
|
383
|
+
}>;
|
|
384
|
+
declare const CrossRepoProvenanceSchema: z.ZodOptional<z.ZodObject<{
|
|
385
|
+
source_name: z.ZodString;
|
|
386
|
+
source_path: z.ZodString;
|
|
387
|
+
source_id: z.ZodString;
|
|
388
|
+
imported_at: z.ZodString;
|
|
389
|
+
}, "strip", z.ZodTypeAny, {
|
|
390
|
+
source_name: string;
|
|
391
|
+
source_path: string;
|
|
392
|
+
source_id: string;
|
|
393
|
+
imported_at: string;
|
|
394
|
+
}, {
|
|
395
|
+
source_name: string;
|
|
396
|
+
source_path: string;
|
|
397
|
+
source_id: string;
|
|
398
|
+
imported_at: string;
|
|
399
|
+
}>>;
|
|
400
|
+
|
|
401
|
+
type MemoryScope = z.infer<typeof MemoryScopeSchema>;
|
|
402
|
+
type MemoryStatus = z.infer<typeof MemoryStatusSchema>;
|
|
403
|
+
type MemoryType = z.infer<typeof MemoryTypeSchema>;
|
|
404
|
+
type Anchor = z.infer<typeof AnchorSchema>;
|
|
405
|
+
type Sensor = z.infer<typeof SensorSchema>;
|
|
406
|
+
type Activation = z.infer<typeof ActivationSchema>;
|
|
407
|
+
type MemoryFrontmatter = z.infer<typeof MemoryFrontmatterSchema>;
|
|
408
|
+
interface Memory {
|
|
409
|
+
frontmatter: MemoryFrontmatter;
|
|
410
|
+
body: string;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
declare function stripPrivate(body: string): string;
|
|
414
|
+
declare function parseMemory(raw: string): Memory;
|
|
415
|
+
declare function serializeMemory(memory: Memory): string;
|
|
416
|
+
declare function newMemoryId(type: string, slug: string, date?: Date): string;
|
|
417
|
+
declare function buildFrontmatter(input: {
|
|
418
|
+
type: MemoryFrontmatter["type"];
|
|
419
|
+
slug: string;
|
|
420
|
+
scope?: MemoryFrontmatter["scope"];
|
|
421
|
+
module?: string;
|
|
422
|
+
tags?: string[];
|
|
423
|
+
domain?: string;
|
|
424
|
+
author?: string;
|
|
425
|
+
paths?: string[];
|
|
426
|
+
symbols?: string[];
|
|
427
|
+
commit?: string;
|
|
428
|
+
topic?: string;
|
|
429
|
+
status?: MemoryFrontmatter["status"];
|
|
430
|
+
relatedIds?: string[];
|
|
431
|
+
sensor?: Sensor;
|
|
432
|
+
activation?: Activation;
|
|
433
|
+
}): MemoryFrontmatter;
|
|
434
|
+
|
|
435
|
+
declare const HAIVE_DIR = ".ai";
|
|
436
|
+
declare function findProjectRoot(startDir?: string): string;
|
|
437
|
+
declare const PROJECT_CONTEXT_FILE = "project-context.md";
|
|
438
|
+
declare const MEMORIES_DIR = "memories";
|
|
439
|
+
interface HaivePaths {
|
|
440
|
+
root: string;
|
|
441
|
+
haiveDir: string;
|
|
442
|
+
/** Disposable local layer (session journal, caches). Not team truth — see `.ai/.runtime/` layout. */
|
|
443
|
+
runtimeDir: string;
|
|
444
|
+
projectContext: string;
|
|
445
|
+
memoriesDir: string;
|
|
446
|
+
personalDir: string;
|
|
447
|
+
teamDir: string;
|
|
448
|
+
sharedDir: string;
|
|
449
|
+
moduleDir: string;
|
|
450
|
+
modulesContextDir: string;
|
|
451
|
+
}
|
|
452
|
+
declare function resolveHaivePaths(projectRoot: string): HaivePaths;
|
|
453
|
+
declare function memoryFilePath(paths: HaivePaths, scope: "personal" | "team" | "module" | "shared", id: string, module?: string): string;
|
|
454
|
+
|
|
455
|
+
interface LoadedMemory {
|
|
456
|
+
memory: Memory;
|
|
457
|
+
filePath: string;
|
|
458
|
+
}
|
|
459
|
+
declare function listMarkdownFilesRecursive(dir: string): Promise<string[]>;
|
|
460
|
+
declare function loadMemory(filePath: string): Promise<LoadedMemory>;
|
|
461
|
+
declare function loadMemoriesFromDir(dir: string): Promise<LoadedMemory[]>;
|
|
462
|
+
|
|
463
|
+
declare function tokenizeQuery(query: string): string[];
|
|
464
|
+
declare function literalMatchesAllTokens(memory: Memory, tokens: string[]): boolean;
|
|
465
|
+
/**
|
|
466
|
+
* OR-based fallback: returns true if the memory matches at least one token.
|
|
467
|
+
* Used when all-tokens (AND) search returns 0 results.
|
|
468
|
+
*/
|
|
469
|
+
declare function literalMatchesAnyToken(memory: Memory, tokens: string[]): boolean;
|
|
470
|
+
declare function pickSnippetNeedle(query: string): string;
|
|
471
|
+
declare function extractSnippet(body: string, needle: string, radius?: number): string;
|
|
472
|
+
|
|
473
|
+
interface VerifyResult {
|
|
474
|
+
stale: boolean;
|
|
475
|
+
reason: string | null;
|
|
476
|
+
checkedPaths: string[];
|
|
477
|
+
checkedSymbols: string[];
|
|
478
|
+
possibleRenames: string[];
|
|
479
|
+
}
|
|
480
|
+
interface VerifyOptions {
|
|
481
|
+
/** Project root used to resolve relative anchor paths. */
|
|
482
|
+
projectRoot: string;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Verify that a memory's anchor still matches the current code.
|
|
486
|
+
* - Every anchor.paths entry must exist on disk
|
|
487
|
+
* - Every anchor.symbols entry must appear at least once across the anchor.paths
|
|
488
|
+
* files (or any tracked file if no paths are recorded)
|
|
489
|
+
*
|
|
490
|
+
* Anchorless memories (no paths and no symbols) are always considered fresh —
|
|
491
|
+
* staleness only applies to memories that opted into anchoring.
|
|
492
|
+
*/
|
|
493
|
+
declare function verifyAnchor(memory: Memory, options: VerifyOptions): Promise<VerifyResult>;
|
|
494
|
+
|
|
495
|
+
interface MemoryUsage {
|
|
496
|
+
read_count: number;
|
|
497
|
+
last_read_at: string | null;
|
|
498
|
+
rejected_count: number;
|
|
499
|
+
last_rejected_at: string | null;
|
|
500
|
+
rejection_reason: string | null;
|
|
501
|
+
/**
|
|
502
|
+
* Number of times the memory was explicitly confirmed *useful* — i.e. an agent
|
|
503
|
+
* or human recorded that it changed what they did (the closed-loop "applied"
|
|
504
|
+
* outcome, recorded via `mem_feedback`). A far stronger utility signal than a
|
|
505
|
+
* read: a memory can be surfaced many times and ignored, but `applied` means it
|
|
506
|
+
* demonstrably steered work. Drives impact scoring in {@link ./impact.js}.
|
|
507
|
+
*/
|
|
508
|
+
applied_count: number;
|
|
509
|
+
last_applied_at: string | null;
|
|
510
|
+
/**
|
|
511
|
+
* Number of *prevention* events — times this memory's sensor actually fired on a scanned diff,
|
|
512
|
+
* intercepting a known mistake before it landed. This is an OUTCOME signal (defect prevented),
|
|
513
|
+
* the closest proxy Hivelore has to "did the knowledge stop a real problem?", distinct from
|
|
514
|
+
* retrieval (read) and self-reported usefulness (applied). Recorded by `hivelore sensors check`.
|
|
515
|
+
*/
|
|
516
|
+
prevented_count: number;
|
|
517
|
+
last_prevented_at: string | null;
|
|
518
|
+
}
|
|
519
|
+
interface UsageIndex {
|
|
520
|
+
version: 1;
|
|
521
|
+
updated_at: string;
|
|
522
|
+
by_id: Record<string, MemoryUsage>;
|
|
523
|
+
}
|
|
524
|
+
declare const USAGE_FILE = "usage.json";
|
|
525
|
+
declare function emptyUsage(): MemoryUsage;
|
|
526
|
+
declare function emptyUsageIndex(): UsageIndex;
|
|
527
|
+
declare function usagePath(paths: HaivePaths): string;
|
|
528
|
+
declare function loadUsageIndex(paths: HaivePaths): Promise<UsageIndex>;
|
|
529
|
+
declare function saveUsageIndex(paths: HaivePaths, index: UsageIndex): Promise<void>;
|
|
530
|
+
declare function getUsage(index: UsageIndex, id: string): MemoryUsage;
|
|
531
|
+
declare function bumpRead(index: UsageIndex, ids: string[]): UsageIndex;
|
|
532
|
+
declare function recordRejection(index: UsageIndex, id: string, reason: string | null): UsageIndex;
|
|
533
|
+
/**
|
|
534
|
+
* Record that a memory was *applied* — explicitly confirmed to have changed what
|
|
535
|
+
* the agent/human did. This is the closed-loop utility signal that distinguishes
|
|
536
|
+
* a memory that merely got surfaced from one that demonstrably steered work.
|
|
537
|
+
*/
|
|
538
|
+
declare function recordApplied(index: UsageIndex, id: string): UsageIndex;
|
|
539
|
+
/** Debounce window so re-scanning the same diff within a few minutes doesn't inflate prevention
|
|
540
|
+
* counts (a pre-commit hook can run the check several times for one commit). */
|
|
541
|
+
declare const PREVENTION_DEBOUNCE_MS: number;
|
|
542
|
+
/**
|
|
543
|
+
* Record a *prevention* event: a memory's sensor fired on a scanned diff, intercepting a known
|
|
544
|
+
* mistake before it landed. Outcome signal (defect prevented), stronger than a read. Debounced by
|
|
545
|
+
* {@link PREVENTION_DEBOUNCE_MS}. Returns true if a NEW event was recorded (false if debounced).
|
|
546
|
+
*/
|
|
547
|
+
declare function recordPrevention(index: UsageIndex, id: string, now?: number): boolean;
|
|
548
|
+
declare const DECAY_DAYS = 90;
|
|
549
|
+
declare function isDecaying(usage: MemoryUsage, createdAt: string): boolean;
|
|
550
|
+
declare function trackReads(paths: HaivePaths, ids: string[]): Promise<UsageIndex>;
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Closed-loop memory-utility scoring — the "did this memory actually help?" layer.
|
|
554
|
+
*
|
|
555
|
+
* Hivelore already tracks reads ({@link ./usage.js}) and derives a trust level from
|
|
556
|
+
* status + read_count ({@link ./confidence.js}). But a read only means a memory was
|
|
557
|
+
* *surfaced*, not that it *helped* — a memory can be injected on every briefing and
|
|
558
|
+
* silently ignored. Harness engineering's core loop (Fowler / LangChain) is to
|
|
559
|
+
* measure what demonstrably steers work and let that feed back into recall.
|
|
560
|
+
*
|
|
561
|
+
* `computeImpact` combines the signals Hivelore already records but never correlated:
|
|
562
|
+
* POSITIVE reads · applied outcomes (mem_feedback) · a sensor that actually fired
|
|
563
|
+
* NEGATIVE rejections · stale/deprecated/rejected status · dormancy
|
|
564
|
+
* into a single 0..1 utility score, a tier, and a prune-candidate flag. It is a pure
|
|
565
|
+
* function (no I/O), unit-tested in `packages/core/test/impact.test.ts`.
|
|
566
|
+
*/
|
|
567
|
+
type ImpactTier = "high" | "medium" | "low" | "dormant";
|
|
568
|
+
interface ImpactScore {
|
|
569
|
+
/** Normalized utility score in [0, 1]. */
|
|
570
|
+
score: number;
|
|
571
|
+
tier: ImpactTier;
|
|
572
|
+
/** Human-readable breakdown of the signals that produced the score. */
|
|
573
|
+
signals: string[];
|
|
574
|
+
/**
|
|
575
|
+
* True when the memory looks like dead weight worth reviewing/pruning:
|
|
576
|
+
* more rejections than reads, or never used with no guardrail, or already
|
|
577
|
+
* stale/deprecated/rejected. A memory carrying a `sensor` or with `applied`
|
|
578
|
+
* outcomes is never a prune candidate — it earns its keep as a guardrail.
|
|
579
|
+
*/
|
|
580
|
+
pruneCandidate: boolean;
|
|
581
|
+
}
|
|
582
|
+
interface ImpactOptions {
|
|
583
|
+
/** Days with no read AND no applied outcome after which a memory is "dormant". */
|
|
584
|
+
dormantDays?: number;
|
|
585
|
+
now?: Date;
|
|
586
|
+
}
|
|
587
|
+
/** Default dormancy window — half the confidence hard-decay (365d), so impact reacts sooner. */
|
|
588
|
+
declare const DEFAULT_DORMANT_DAYS = 120;
|
|
589
|
+
/**
|
|
590
|
+
* Compute the demonstrated utility of a single memory from its frontmatter + usage.
|
|
591
|
+
* Pure and deterministic given `now`.
|
|
592
|
+
*/
|
|
593
|
+
declare function computeImpact(fm: MemoryFrontmatter, usage: MemoryUsage, options?: ImpactOptions): ImpactScore;
|
|
594
|
+
/** Sort comparator: highest impact first, prune candidates last on ties. */
|
|
595
|
+
declare function compareImpact(a: ImpactScore, b: ImpactScore): number;
|
|
596
|
+
interface ImpactSummary {
|
|
597
|
+
total: number;
|
|
598
|
+
high: number;
|
|
599
|
+
medium: number;
|
|
600
|
+
low: number;
|
|
601
|
+
dormant: number;
|
|
602
|
+
prune_candidates: number;
|
|
603
|
+
}
|
|
604
|
+
/** Roll up a set of impact scores into tier counts. */
|
|
605
|
+
declare function summarizeImpact(scores: ImpactScore[]): ImpactSummary;
|
|
606
|
+
type FeedbackAdjustmentAction = "none" | "downgrade-block-sensor" | "deprecate-memory";
|
|
607
|
+
interface FeedbackAdjustment {
|
|
608
|
+
action: FeedbackAdjustmentAction;
|
|
609
|
+
reason: string;
|
|
610
|
+
}
|
|
611
|
+
interface FeedbackAdjustmentOptions {
|
|
612
|
+
/** Rejections needed before deprecating a memory with no positive outcomes. Defaults to 2. */
|
|
613
|
+
rejectionThreshold?: number;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Turn explicit human rejection (`mem_feedback outcome=rejected`) into a deterministic
|
|
617
|
+
* noise-reduction action. Pure: callers decide whether to persist the returned change.
|
|
618
|
+
*/
|
|
619
|
+
declare function recommendFeedbackAdjustment(fm: MemoryFrontmatter, usage: MemoryUsage, options?: FeedbackAdjustmentOptions): FeedbackAdjustment;
|
|
620
|
+
declare function applyFeedbackAdjustment(fm: MemoryFrontmatter, adjustment: FeedbackAdjustment, now?: Date): MemoryFrontmatter;
|
|
621
|
+
|
|
622
|
+
type PreventionSource = "sensor" | "anti-pattern";
|
|
623
|
+
interface PreventionEvent {
|
|
624
|
+
/** ISO timestamp of the catch. */
|
|
625
|
+
at: string;
|
|
626
|
+
/** Memory id whose lesson fired. */
|
|
627
|
+
id: string;
|
|
628
|
+
/** Which gate path recorded it. */
|
|
629
|
+
source: PreventionSource;
|
|
630
|
+
}
|
|
631
|
+
declare function preventionLogPath(paths: HaivePaths): string;
|
|
632
|
+
/** Append one catch to the log. Best-effort, creates the dir on demand. */
|
|
633
|
+
declare function appendPreventionEvent(paths: HaivePaths, event: PreventionEvent): Promise<void>;
|
|
634
|
+
/**
|
|
635
|
+
* THE single recorder for "a documented lesson intercepted a real mistake". Every gate path —
|
|
636
|
+
* the installed git-hook gate (`enforce check`), the standalone `hivelore sensors check`, and the
|
|
637
|
+
* `anti_patterns_check` MCP tool — funnels its fired memory ids through here so prevention is
|
|
638
|
+
* recorded once and identically, not bolted onto each entry point (it used to leak: the git-hook
|
|
639
|
+
* gate blocked but never recorded — see the harness-positioning gotcha).
|
|
640
|
+
*
|
|
641
|
+
* Bumps `prevented_count` in usage.json (debounced per memory via {@link recordPrevention}) AND
|
|
642
|
+
* appends one timestamped event per NEW catch to the prevention log. Best-effort: a telemetry
|
|
643
|
+
* write must never break a commit, so failures are swallowed. Returns the ids actually recorded
|
|
644
|
+
* (i.e. not debounced), so callers can report "caught for you" without re-counting.
|
|
645
|
+
*/
|
|
646
|
+
declare function recordPreventionHits(paths: HaivePaths, firedIds: string[], source: PreventionSource, now?: Date): Promise<string[]>;
|
|
647
|
+
/** Read all catch events (skips malformed lines). */
|
|
648
|
+
declare function loadPreventionEvents(paths: HaivePaths): Promise<PreventionEvent[]>;
|
|
649
|
+
interface PreventionTrend {
|
|
650
|
+
/** Catches in the last 7 days. */
|
|
651
|
+
last_7d: number;
|
|
652
|
+
/** Catches in the last 30 days. */
|
|
653
|
+
last_30d: number;
|
|
654
|
+
/** Catch counts per ISO week, oldest → newest, for the last N weeks (default 6). */
|
|
655
|
+
weekly: number[];
|
|
656
|
+
}
|
|
657
|
+
declare function computePreventionTrend(events: PreventionEvent[], now?: Date, weeks?: number): PreventionTrend;
|
|
658
|
+
interface RecurrenceRow {
|
|
659
|
+
id: string;
|
|
660
|
+
/** Total catches for this memory. */
|
|
661
|
+
catches: number;
|
|
662
|
+
/** Number of distinct UTC days the lesson fired — the recurrence signal. */
|
|
663
|
+
distinct_days: number;
|
|
664
|
+
last_at: string;
|
|
665
|
+
}
|
|
666
|
+
interface RecurrenceReport {
|
|
667
|
+
/**
|
|
668
|
+
* Memories whose lesson was caught on >= 2 distinct days — i.e. the mistake was RE-INTRODUCED
|
|
669
|
+
* after it had already been captured and caught once. A high count means a recurring problem the
|
|
670
|
+
* team keeps reintroducing (the guardrail is earning its keep, and the root cause may need a
|
|
671
|
+
* stronger fix than a memory).
|
|
672
|
+
*/
|
|
673
|
+
recurring_count: number;
|
|
674
|
+
top: RecurrenceRow[];
|
|
675
|
+
}
|
|
676
|
+
declare function computeRecurrence(events: PreventionEvent[]): RecurrenceReport;
|
|
677
|
+
interface BriefingProofLineOptions {
|
|
678
|
+
/** End of the reporting window. Defaults to now. */
|
|
679
|
+
now?: Date;
|
|
680
|
+
/** Window size in days. Defaults to 30 ("this month" in product copy). */
|
|
681
|
+
days?: number;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Coordination point for Lot C: turn prevention events into one compact proof line
|
|
685
|
+
* suitable for get_briefing, without coupling this lot to the MCP tool.
|
|
686
|
+
*/
|
|
687
|
+
declare function briefingProofLine(events: PreventionEvent[], options?: BriefingProofLineOptions): string | null;
|
|
688
|
+
interface CaughtForYouOptions {
|
|
689
|
+
/** Only include events at or after this instant. */
|
|
690
|
+
since?: string | Date;
|
|
691
|
+
/** Only include events at or before this instant. Defaults to now. */
|
|
692
|
+
now?: Date;
|
|
693
|
+
/** Max rows in the summary. Defaults to 5. */
|
|
694
|
+
limit?: number;
|
|
695
|
+
}
|
|
696
|
+
interface CaughtForYouRow {
|
|
697
|
+
id: string;
|
|
698
|
+
title: string;
|
|
699
|
+
source: PreventionSource;
|
|
700
|
+
catches: number;
|
|
701
|
+
previous_count: number;
|
|
702
|
+
current_count: number;
|
|
703
|
+
last_at: string;
|
|
704
|
+
}
|
|
705
|
+
interface CaughtForYouSummary {
|
|
706
|
+
total_catches: number;
|
|
707
|
+
since: string | null;
|
|
708
|
+
until: string;
|
|
709
|
+
rows: CaughtForYouRow[];
|
|
710
|
+
}
|
|
711
|
+
/** Build the end-of-session "caught for you" scene from prevention events. Pure. */
|
|
712
|
+
declare function summarizeCaughtForYou(events: PreventionEvent[], memories: LoadedMemory[], usage: UsageIndex, options?: CaughtForYouOptions): CaughtForYouSummary;
|
|
713
|
+
/** Render a compact human-readable block for CLI/session recaps. */
|
|
714
|
+
declare function renderCaughtForYou(summary: CaughtForYouSummary): string | null;
|
|
715
|
+
|
|
716
|
+
/** How long an emitted project context is considered "still fresh in the agent's context". */
|
|
717
|
+
declare const PROJECT_CONTEXT_THROTTLE_MS: number;
|
|
718
|
+
declare function hashProjectContext(content: string): string;
|
|
719
|
+
/** True if an identical project-context body was already emitted within the throttle window. */
|
|
720
|
+
declare function projectContextRecentlyEmitted(paths: HaivePaths, hash: string, now?: number): Promise<boolean>;
|
|
721
|
+
/** Record that this exact project-context body was just emitted. Best-effort. */
|
|
722
|
+
declare function recordProjectContextEmission(paths: HaivePaths, hash: string, now?: number): Promise<void>;
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* A rigorous, model-free, repeatable evaluation of Hivelore's core promise: surfacing
|
|
726
|
+
* the right knowledge and guardrails at the right moment. Unlike the agent benchmark
|
|
727
|
+
* (which parses human-written reports), this is deterministic and CI-runnable — it
|
|
728
|
+
* produces a numeric quality score from labeled cases, so a regression in ranking or
|
|
729
|
+
* sensor coverage fails the build instead of silently degrading every agent session.
|
|
730
|
+
*
|
|
731
|
+
* Two case families:
|
|
732
|
+
* - RETRIEVAL — given a task (+optional files/symbols), do the expected memories
|
|
733
|
+
* surface in the briefing top-k? Measured by recall and mean reciprocal rank.
|
|
734
|
+
* - SENSORS — given a known-bad diff, does the expected memory's sensor fire?
|
|
735
|
+
* Measured by catch-rate.
|
|
736
|
+
*
|
|
737
|
+
* This module is pure: it defines the case/result types, the scoring math, and case
|
|
738
|
+
* synthesis from a repo's own anchored memories. Orchestration (calling get_briefing
|
|
739
|
+
* / anti_patterns_check) lives in the CLI, since core cannot depend on the MCP layer.
|
|
740
|
+
*/
|
|
741
|
+
interface RetrievalCase {
|
|
742
|
+
name: string;
|
|
743
|
+
task: string;
|
|
744
|
+
files?: string[];
|
|
745
|
+
symbols?: string[];
|
|
746
|
+
/** Memory ids that SHOULD surface in the briefing for this case. */
|
|
747
|
+
expect_ids: string[];
|
|
748
|
+
}
|
|
749
|
+
interface SensorCase {
|
|
750
|
+
name: string;
|
|
751
|
+
/** Unified diff (or added-line text) the sensors run against. */
|
|
752
|
+
diff: string;
|
|
753
|
+
paths?: string[];
|
|
754
|
+
/** Memory ids whose sensor SHOULD fire on this diff. */
|
|
755
|
+
expect_fire_ids: string[];
|
|
756
|
+
}
|
|
757
|
+
interface EvalSpec {
|
|
758
|
+
retrieval?: RetrievalCase[];
|
|
759
|
+
sensors?: SensorCase[];
|
|
760
|
+
}
|
|
761
|
+
interface RetrievalCaseResult {
|
|
762
|
+
name: string;
|
|
763
|
+
expect_ids: string[];
|
|
764
|
+
/** Surfaced memory ids, in ranked order, capped at k. */
|
|
765
|
+
surfaced_ids: string[];
|
|
766
|
+
hits: string[];
|
|
767
|
+
misses: string[];
|
|
768
|
+
precision: number;
|
|
769
|
+
recall: number;
|
|
770
|
+
/** 1-based rank of the first expected id among surfaced ids; null if none surfaced. */
|
|
771
|
+
best_rank: number | null;
|
|
772
|
+
}
|
|
773
|
+
interface SensorCaseResult {
|
|
774
|
+
name: string;
|
|
775
|
+
expect_fire_ids: string[];
|
|
776
|
+
fired_ids: string[];
|
|
777
|
+
hits: string[];
|
|
778
|
+
misses: string[];
|
|
779
|
+
recall: number;
|
|
780
|
+
}
|
|
781
|
+
interface RetrievalAggregate {
|
|
782
|
+
cases: RetrievalCaseResult[];
|
|
783
|
+
/**
|
|
784
|
+
* Top-k precision = expected hits ÷ surfaced results. Inherently LOW when only ~1 memory is
|
|
785
|
+
* expected per case but k results are surfaced (e.g. 1/8 ≈ 0.12) — this is a top-k artifact, not
|
|
786
|
+
* a quality defect. Retrieval quality is judged by `mean_recall` and `mrr`; precision is NOT part
|
|
787
|
+
* of the headline eval score (see `scoreEval`). Reported for completeness only.
|
|
788
|
+
*/
|
|
789
|
+
mean_precision: number;
|
|
790
|
+
mean_recall: number;
|
|
791
|
+
/** Mean reciprocal rank of the first expected hit — rewards ranking the right memory high. */
|
|
792
|
+
mrr: number;
|
|
793
|
+
}
|
|
794
|
+
interface SensorAggregate {
|
|
795
|
+
cases: SensorCaseResult[];
|
|
796
|
+
catch_rate: number;
|
|
797
|
+
}
|
|
798
|
+
interface EvalReport {
|
|
799
|
+
retrieval: RetrievalAggregate | null;
|
|
800
|
+
sensors: SensorAggregate | null;
|
|
801
|
+
/** Overall quality score 0..100. */
|
|
802
|
+
score: number;
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Score one retrieval case from the (ranked) ids the briefing surfaced.
|
|
806
|
+
* `surfacedRanked` should already be capped to the top-k the caller cares about.
|
|
807
|
+
*/
|
|
808
|
+
declare function scoreRetrievalCase(name: string, expectIds: string[], surfacedRanked: string[]): RetrievalCaseResult;
|
|
809
|
+
declare function aggregateRetrieval(cases: RetrievalCaseResult[]): RetrievalAggregate;
|
|
810
|
+
declare function scoreSensorCase(name: string, expectFireIds: string[], firedIds: string[]): SensorCaseResult;
|
|
811
|
+
declare function aggregateSensors(cases: SensorCaseResult[]): SensorAggregate;
|
|
812
|
+
/** Combine retrieval + sensor aggregates into a single 0..100 quality score. */
|
|
813
|
+
declare function overallScore(retrieval: RetrievalAggregate | null, sensors: SensorAggregate | null): number;
|
|
814
|
+
declare function buildReport(retrieval: RetrievalAggregate | null, sensors: SensorAggregate | null): EvalReport;
|
|
815
|
+
/**
|
|
816
|
+
* Baseline / compare — makes the "Hivelore improves retrieval by N%" claim reproducible.
|
|
817
|
+
*
|
|
818
|
+
* `hivelore eval --baseline` snapshots a report; `--compare` re-runs and diffs against it,
|
|
819
|
+
* so a ranking/sensor regression is a number, not a vibe. Pure math here; the CLI does I/O.
|
|
820
|
+
*/
|
|
821
|
+
interface MetricDelta {
|
|
822
|
+
baseline: number;
|
|
823
|
+
current: number;
|
|
824
|
+
/** current − baseline (positive = improvement for all these metrics). */
|
|
825
|
+
delta: number;
|
|
826
|
+
}
|
|
827
|
+
interface EvalDelta {
|
|
828
|
+
score: MetricDelta;
|
|
829
|
+
mean_recall: MetricDelta | null;
|
|
830
|
+
mrr: MetricDelta | null;
|
|
831
|
+
catch_rate: MetricDelta | null;
|
|
832
|
+
/** True when the overall score dropped vs the baseline. */
|
|
833
|
+
regressed: boolean;
|
|
834
|
+
/** True when the overall score rose vs the baseline. */
|
|
835
|
+
improved: boolean;
|
|
836
|
+
}
|
|
837
|
+
/** Diff a current report against a baseline. Pure. */
|
|
838
|
+
declare function compareEvalReports(baseline: EvalReport, current: EvalReport): EvalDelta;
|
|
839
|
+
/** Extract a short task-like title from a memory body (first heading or first line). */
|
|
840
|
+
declare function titleFromBody(body: string): string;
|
|
841
|
+
interface SelfEvalOptions {
|
|
842
|
+
/** Include each memory's anchor paths as the case files (tests anchored retrieval). */
|
|
843
|
+
includeFiles?: boolean;
|
|
844
|
+
/** Skip memories with these statuses (default: stale/deprecated/rejected). */
|
|
845
|
+
skipStatuses?: string[];
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Synthesize retrieval cases from a repo's own anchored memories — zero-setup eval.
|
|
849
|
+
* Each anchored, non-recap, non-dead memory becomes a case: "when working on the
|
|
850
|
+
* file(s) this memory anchors, with its title as the task, does Hivelore surface it?".
|
|
851
|
+
* With `includeFiles: false` it becomes a harder semantic-only probe (title alone).
|
|
852
|
+
*/
|
|
853
|
+
declare function synthesizeSelfEvalCases(memories: LoadedMemory[], options?: SelfEvalOptions): RetrievalCase[];
|
|
854
|
+
|
|
855
|
+
type ConfidenceLevel = "unverified" | "low" | "trusted" | "authoritative" | "stale";
|
|
856
|
+
interface ConfidenceThresholds {
|
|
857
|
+
trustedReads: number;
|
|
858
|
+
authoritativeReads: number;
|
|
859
|
+
/** Days without a read after which confidence drops one tier (authoritative → trusted). */
|
|
860
|
+
decayDays: number;
|
|
861
|
+
/** Days without a read after which confidence drops two tiers (e.g. authoritative → low). */
|
|
862
|
+
hardDecayDays: number;
|
|
863
|
+
}
|
|
864
|
+
declare const DEFAULT_CONFIDENCE_THRESHOLDS: ConfidenceThresholds;
|
|
865
|
+
/**
|
|
866
|
+
* Compute the trust level of a memory.
|
|
867
|
+
*
|
|
868
|
+
* Base tier is derived from `status + read_count`:
|
|
869
|
+
* - draft → unverified
|
|
870
|
+
* - proposed (low reads) → low
|
|
871
|
+
* - proposed (3+ reads) → trusted
|
|
872
|
+
* - validated (low reads) → trusted
|
|
873
|
+
* - validated (10+ reads) → authoritative
|
|
874
|
+
* - stale / deprecated / rejected → stale
|
|
875
|
+
*
|
|
876
|
+
* On top of the base tier, a TIME DECAY is applied: a memory that has not been
|
|
877
|
+
* read in `decayDays` (default 180) drops one tier, and one not read in
|
|
878
|
+
* `hardDecayDays` (default 365) drops two tiers. The clock starts at
|
|
879
|
+
* `last_read_at` if any, otherwise `created_at` from the frontmatter.
|
|
880
|
+
*
|
|
881
|
+
* The decay never crosses into `stale` (we keep that signal reserved for the
|
|
882
|
+
* verifier). The intent is to surface "this used to be authoritative but
|
|
883
|
+
* nobody has touched it in a year — verify before quoting it" without
|
|
884
|
+
* pretending the memory is wrong.
|
|
885
|
+
*/
|
|
886
|
+
declare function deriveConfidence(fm: MemoryFrontmatter, usage: MemoryUsage, thresholds?: ConfidenceThresholds, now?: Date): ConfidenceLevel;
|
|
887
|
+
interface AutoPromoteRule {
|
|
888
|
+
/** Minimum read_count to promote proposed → validated. */
|
|
889
|
+
minReads: number;
|
|
890
|
+
/** Maximum rejected_count tolerated (memories with more rejections never auto-promote). */
|
|
891
|
+
maxRejections: number;
|
|
892
|
+
}
|
|
893
|
+
declare const DEFAULT_AUTO_PROMOTE_RULE: AutoPromoteRule;
|
|
894
|
+
declare function isAutoPromoteEligible(fm: MemoryFrontmatter, usage: MemoryUsage, rule?: AutoPromoteRule): boolean;
|
|
895
|
+
|
|
896
|
+
/**
|
|
897
|
+
* Tag stamped on memories that were pre-seeded from a stack pack at `hivelore init`
|
|
898
|
+
* (generic framework knowledge the model already largely knows, not repo-specific
|
|
899
|
+
* institutional knowledge). Briefing ranking caps these at `background` priority so
|
|
900
|
+
* a generic seed never displaces a repo-specific memory — unless the seed has been
|
|
901
|
+
* anchored to a file the agent is actually editing.
|
|
902
|
+
*/
|
|
903
|
+
declare const STACK_PACK_TAG = "stack-pack";
|
|
904
|
+
/** True when a memory was pre-seeded from a stack pack (carries {@link STACK_PACK_TAG}). */
|
|
905
|
+
declare function isStackPackSeed(fm: {
|
|
906
|
+
tags?: string[];
|
|
907
|
+
} | null | undefined): boolean;
|
|
908
|
+
/**
|
|
909
|
+
* Tags that mark a memory as a *local dev-environment workaround* (hot-swap, nested node_modules,
|
|
910
|
+
* global-install quirks) rather than repo-specific team policy. These are real, but they describe
|
|
911
|
+
* tooling debt, not unguessable team knowledge — and because they get read on almost every session
|
|
912
|
+
* their read_count inflates and they crowd the briefing. Ranking caps them at `background` UNLESS
|
|
913
|
+
* they directly anchor a file being edited, so they stop displacing actual policy. The fix for a
|
|
914
|
+
* recurring one is to repair the environment, not to keep surfacing the note.
|
|
915
|
+
*/
|
|
916
|
+
declare const ENV_WORKAROUND_TAGS: Set<string>;
|
|
917
|
+
/** True when a memory is tagged as a local dev-environment workaround (see {@link ENV_WORKAROUND_TAGS}). */
|
|
918
|
+
declare function isEnvWorkaroundMemory(fm: {
|
|
919
|
+
tags?: string[];
|
|
920
|
+
} | null | undefined): boolean;
|
|
921
|
+
declare function extractReferencedPaths(text: string): string[];
|
|
922
|
+
/**
|
|
923
|
+
* True when a memory carries any tag in `excludeTags` (case-insensitive) — used to keep
|
|
924
|
+
* strategy/positioning memories OUT of automatic briefing surfacing while leaving them searchable.
|
|
925
|
+
* See `HaiveConfig.briefingExcludeTags`. Empty/undefined list ⇒ never excluded.
|
|
926
|
+
*/
|
|
927
|
+
declare function memoryHasExcludedTag(fm: {
|
|
928
|
+
tags?: string[];
|
|
929
|
+
} | null | undefined, excludeTags: string[] | null | undefined): boolean;
|
|
930
|
+
/**
|
|
931
|
+
* Best-effort inference: given a list of file paths, infer module names from
|
|
932
|
+
* conventional layouts (packages/X/, apps/X/, modules/X/, src/X/).
|
|
933
|
+
*/
|
|
934
|
+
declare function inferModulesFromPaths(filePaths: string[]): string[];
|
|
935
|
+
/**
|
|
936
|
+
* Path overlap: returns true if `a` and `b` refer to the same path or one is a
|
|
937
|
+
* parent of the other. Both inputs are treated as POSIX-style relative paths.
|
|
938
|
+
*/
|
|
939
|
+
declare function pathsOverlap(a: string, b: string): boolean;
|
|
940
|
+
declare function memoryMatchesAnchorPaths(memory: LoadedMemory["memory"], inputPaths: string[]): boolean;
|
|
941
|
+
declare function isGlobPath(p: string): boolean;
|
|
942
|
+
declare function globToRegExp(pattern: string): RegExp;
|
|
943
|
+
declare function relPathFrom(root: string, abs: string): string;
|
|
944
|
+
|
|
945
|
+
/**
|
|
946
|
+
* Distinctive-token corroboration for the anti-pattern gate.
|
|
947
|
+
*
|
|
948
|
+
* The pre-commit gate used to hard-block whenever a diff shared ANY ≥4-char token
|
|
949
|
+
* with an anchored gotcha's body. That fires on ubiquitous domain words ("memory",
|
|
950
|
+
* "sensor", "scope", "input", "version") and on version-bump diffs — blocking agents
|
|
951
|
+
* for nothing. The fix: a `literal` overlap only corroborates a BLOCK when at least
|
|
952
|
+
* one shared token is *distinctive* to that gotcha — i.e. rare across the gotcha
|
|
953
|
+
* corpus (low document frequency), like `BigInt`, `open-in-view`, `rec_7`. Common
|
|
954
|
+
* words can still surface the warning for review; they just can't hard-block.
|
|
955
|
+
*
|
|
956
|
+
* Pure module (no I/O), TF-IDF-style. Unit-tested in `test/distinctive.test.ts`.
|
|
957
|
+
*/
|
|
958
|
+
/**
|
|
959
|
+
* Language keywords + ubiquitous code words that would match almost any memory body
|
|
960
|
+
* and so carry no distinguishing signal. Shared by the diff tokenizer and the
|
|
961
|
+
* distinctiveness check so "literal" stays meaningful.
|
|
962
|
+
*/
|
|
963
|
+
declare const CODE_STOPWORDS: Set<string>;
|
|
964
|
+
/** Minimum token length kept for word-level matching (shorter tokens are too noisy). */
|
|
965
|
+
declare const MIN_WORD_LEN = 4;
|
|
966
|
+
/** Split text into lowercase word tokens (>= MIN_WORD_LEN, excluding code stopwords). */
|
|
967
|
+
declare function tokenizeWords(text: string): string[];
|
|
968
|
+
interface DocFrequency {
|
|
969
|
+
/** token -> number of documents (memory bodies) it appears in */
|
|
970
|
+
df: Map<string, number>;
|
|
971
|
+
/** total number of documents */
|
|
972
|
+
total: number;
|
|
973
|
+
}
|
|
974
|
+
/** Build per-token document frequency across a corpus of memory bodies. */
|
|
975
|
+
declare function buildDocFrequency(bodies: string[]): DocFrequency;
|
|
976
|
+
/**
|
|
977
|
+
* Document-frequency cap at/below which a token counts as distinctive. Deliberately
|
|
978
|
+
* strict — "distinctive" means *rare* (≈ the bottom 10% of the corpus), with a floor
|
|
979
|
+
* of 1 so a token appearing in a single memory is always distinctive. Strictness is
|
|
980
|
+
* intentional: blocking is the aggressive action, so we under-block rather than fire
|
|
981
|
+
* on a word that several gotchas happen to share.
|
|
982
|
+
*/
|
|
983
|
+
declare function distinctiveCap(total: number): number;
|
|
984
|
+
/** True when `token` is distinctive (rare) within the corpus. */
|
|
985
|
+
declare function isDistinctiveToken(token: string, freq: DocFrequency): boolean;
|
|
986
|
+
/**
|
|
987
|
+
* True when the added diff text shares at least one *distinctive* word token with the
|
|
988
|
+
* memory body. This is the precise corroboration the block decision should require:
|
|
989
|
+
* "the change actually contains the specific thing this gotcha warns about", not
|
|
990
|
+
* "the change happens to mention a common domain word".
|
|
991
|
+
*/
|
|
992
|
+
declare function diffHasDistinctiveOverlap(addedDiffText: string, memoryBody: string, freq: DocFrequency): boolean;
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* Progressive disclosure for `skill` memories.
|
|
996
|
+
*
|
|
997
|
+
* Skills are reusable playbooks. Surfacing all of them on every briefing wastes the
|
|
998
|
+
* instruction budget and buries the relevant one. A skill with an `activation` block
|
|
999
|
+
* is disclosed ONLY when it is relevant to the current task/files; a skill without
|
|
1000
|
+
* one keeps the legacy always-eligible behavior. This module is pure (no I/O).
|
|
1001
|
+
*/
|
|
1002
|
+
interface ActivationContext {
|
|
1003
|
+
task?: string;
|
|
1004
|
+
files?: string[];
|
|
1005
|
+
}
|
|
1006
|
+
interface SkillActivation {
|
|
1007
|
+
applicable: boolean;
|
|
1008
|
+
activated: boolean;
|
|
1009
|
+
reasons: string[];
|
|
1010
|
+
}
|
|
1011
|
+
declare function isSkill(fm: Pick<MemoryFrontmatter, "type">): boolean;
|
|
1012
|
+
/**
|
|
1013
|
+
* Decide whether a skill should be disclosed for the given context.
|
|
1014
|
+
* For non-skills, or skills with no `activation` block, returns `activated: true`
|
|
1015
|
+
* (never suppress them). Otherwise activates on `always`, a keyword substring match
|
|
1016
|
+
* against the task, or a glob match against the edited files.
|
|
1017
|
+
*/
|
|
1018
|
+
declare function evaluateSkillActivation(fm: Pick<MemoryFrontmatter, "type" | "activation">, ctx: ActivationContext): SkillActivation;
|
|
1019
|
+
/** Convenience: true when a skill defines activation triggers that the context does NOT satisfy. */
|
|
1020
|
+
declare function isSkillSuppressed(fm: Pick<MemoryFrontmatter, "type" | "activation">, ctx: ActivationContext): boolean;
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* Specificity ("surprise") scoring for memories.
|
|
1024
|
+
*
|
|
1025
|
+
* Hivelore's measured value is preventing agents from reinventing — wrongly — the team's
|
|
1026
|
+
* NON-OBVIOUS, arbitrary decisions (e.g. "public ids are internal id + 100000 prefixed AC-",
|
|
1027
|
+
* "the status field must be 'OK'/'KO'"). A capable model already knows generic best practice
|
|
1028
|
+
* ("use Decimal for money", "validate input"), so surfacing that is pure token overhead.
|
|
1029
|
+
*
|
|
1030
|
+
* `specificityScore` is a cheap, deterministic heuristic that estimates how *unguessable* a
|
|
1031
|
+
* memory is: high when it contains concrete, arbitrary signal (string literals, code
|
|
1032
|
+
* identifiers, magic numbers, paths, ALLCAPS constants), low when it is generic prose.
|
|
1033
|
+
*
|
|
1034
|
+
* It is intentionally a heuristic, not a model call — used to (1) bias briefing ranking toward
|
|
1035
|
+
* unguessable knowledge, (2) keep a near-empty briefing cheap when nothing team-specific
|
|
1036
|
+
* matches, (3) lint low-value memories, and (4) filter auto-capture candidates.
|
|
1037
|
+
*/
|
|
1038
|
+
/**
|
|
1039
|
+
* Estimate how unguessable / team-specific a memory body is, in [0, 1].
|
|
1040
|
+
* ~0 = generic advice a model already follows by default.
|
|
1041
|
+
* ~1 = arbitrary, repo-specific knowledge no model can infer.
|
|
1042
|
+
*/
|
|
1043
|
+
declare function specificityScore(body: string): number;
|
|
1044
|
+
/**
|
|
1045
|
+
* True when a body uses generic best-practice phrasing a capable model already follows.
|
|
1046
|
+
* Used to SCOPE the LOW_VALUE lint: a low-density body is only "guessable noise" when it also
|
|
1047
|
+
* reads like generic advice. An arbitrary team *policy* can be prose-y yet unguessable (e.g.
|
|
1048
|
+
* "UI text in English, user content in any language") — that must NOT be flagged. Positive
|
|
1049
|
+
* evidence (a generic phrase) keeps the lint high-precision and kills that false positive.
|
|
1050
|
+
*/
|
|
1051
|
+
declare function looksLikeGenericAdvice(body: string): boolean;
|
|
1052
|
+
/** Default threshold below which a memory is considered likely-guessable (low marginal value). */
|
|
1053
|
+
declare const GUESSABLE_THRESHOLD = 0.3;
|
|
1054
|
+
/** True when a memory body looks like generic knowledge a capable model already has. */
|
|
1055
|
+
declare function isLikelyGuessable(body: string, threshold?: number): boolean;
|
|
1056
|
+
/**
|
|
1057
|
+
* Quality floor for SEEDED memories (stack packs, ingested findings) — the guard against shipping
|
|
1058
|
+
* low-value "use const not var" starter content. A seed earns its place only if it is either:
|
|
1059
|
+
* - ENFORCEABLE: it carries a hand-authored sensor (its value is the gate, not its prose), or
|
|
1060
|
+
* - SPECIFIC: it reads as a concrete framework/repo trap (specificity >= floor) and is NOT
|
|
1061
|
+
* generic best-practice prose a capable model already follows.
|
|
1062
|
+
* Used both at seed time (skip a memory that fails) and as a CI guard over the shipped pack library.
|
|
1063
|
+
*
|
|
1064
|
+
* The floor (0.2) is intentionally LOWER than {@link GUESSABLE_THRESHOLD} (0.3, used to lint claimed
|
|
1065
|
+
* team knowledge): a seed is explicitly background-priority framework REFERENCE, not a claim of
|
|
1066
|
+
* non-guessable team policy, so a concrete framework gotcha with a code example clears it — while
|
|
1067
|
+
* genuine garbage ("use const", "write tests") still scores ~0 and/or trips looksLikeGenericAdvice.
|
|
1068
|
+
*/
|
|
1069
|
+
declare const SEED_QUALITY_FLOOR = 0.2;
|
|
1070
|
+
declare function meetsSeedQualityFloor(body: string, hasSensor: boolean, floor?: number): boolean;
|
|
1071
|
+
|
|
1072
|
+
/**
|
|
1073
|
+
* Token budgeting helpers. We use the standard heuristic of ~4 chars per token,
|
|
1074
|
+
* which is conservative for English/code/markdown. Callers that need exact
|
|
1075
|
+
* counts should plug in a real tokenizer; this module only ever over-estimates,
|
|
1076
|
+
* so the user's hard limits are respected.
|
|
1077
|
+
*/
|
|
1078
|
+
declare const CHARS_PER_TOKEN = 4;
|
|
1079
|
+
declare function estimateTokens(text: string): number;
|
|
1080
|
+
interface TruncateOptions {
|
|
1081
|
+
/** Maximum tokens allowed in the result (inclusive). */
|
|
1082
|
+
maxTokens: number;
|
|
1083
|
+
/** Marker inserted where content was dropped. */
|
|
1084
|
+
marker?: string;
|
|
1085
|
+
/** Where to keep characters from when truncating. Default: head. */
|
|
1086
|
+
mode?: "head" | "tail" | "middle";
|
|
1087
|
+
}
|
|
1088
|
+
interface TruncateResult {
|
|
1089
|
+
text: string;
|
|
1090
|
+
truncated: boolean;
|
|
1091
|
+
estimatedTokens: number;
|
|
1092
|
+
originalTokens: number;
|
|
1093
|
+
}
|
|
1094
|
+
declare function truncateToTokens(input: string, options: TruncateOptions): TruncateResult;
|
|
1095
|
+
/**
|
|
1096
|
+
* Allocate a global token budget across N parts with relative weights, then
|
|
1097
|
+
* truncate each part to its share. Returns parts in input order, paired with
|
|
1098
|
+
* truncate metadata.
|
|
1099
|
+
*/
|
|
1100
|
+
interface BudgetPart {
|
|
1101
|
+
key: string;
|
|
1102
|
+
text: string;
|
|
1103
|
+
weight: number;
|
|
1104
|
+
mode?: TruncateOptions["mode"];
|
|
1105
|
+
}
|
|
1106
|
+
interface BudgetSlice {
|
|
1107
|
+
key: string;
|
|
1108
|
+
text: string;
|
|
1109
|
+
truncated: boolean;
|
|
1110
|
+
estimatedTokens: number;
|
|
1111
|
+
originalTokens: number;
|
|
1112
|
+
allocatedTokens: number;
|
|
1113
|
+
}
|
|
1114
|
+
declare function allocateBudget(parts: BudgetPart[], maxTokens: number): BudgetSlice[];
|
|
1115
|
+
|
|
1116
|
+
declare const CODE_MAP_FILE = "code-map.json";
|
|
1117
|
+
type CodeExportKind = "function" | "class" | "interface" | "type" | "const" | "enum" | "default";
|
|
1118
|
+
interface CodeExport {
|
|
1119
|
+
name: string;
|
|
1120
|
+
kind: CodeExportKind;
|
|
1121
|
+
description?: string;
|
|
1122
|
+
line: number;
|
|
1123
|
+
}
|
|
1124
|
+
interface CodeFileEntry {
|
|
1125
|
+
summary?: string;
|
|
1126
|
+
exports: CodeExport[];
|
|
1127
|
+
loc: number;
|
|
1128
|
+
}
|
|
1129
|
+
interface CodeMap {
|
|
1130
|
+
version: 1;
|
|
1131
|
+
generated_at: string;
|
|
1132
|
+
root: string;
|
|
1133
|
+
files: Record<string, CodeFileEntry>;
|
|
1134
|
+
}
|
|
1135
|
+
interface BuildCodeMapOptions {
|
|
1136
|
+
includeExtensions?: string[];
|
|
1137
|
+
excludeDirs?: string[];
|
|
1138
|
+
/** Include untracked files that are not ignored by git. Default: false when the root is a git repo. */
|
|
1139
|
+
includeUntracked?: boolean;
|
|
1140
|
+
}
|
|
1141
|
+
declare function codeMapPath(paths: HaivePaths): string;
|
|
1142
|
+
declare function loadCodeMap(paths: HaivePaths): Promise<CodeMap | null>;
|
|
1143
|
+
declare function saveCodeMap(paths: HaivePaths, map: CodeMap): Promise<void>;
|
|
1144
|
+
declare function buildCodeMap(root: string, options?: BuildCodeMapOptions): Promise<CodeMap>;
|
|
1145
|
+
/**
|
|
1146
|
+
* Count source files physically present on disk (FS walk, excluding node_modules/build/hidden dirs),
|
|
1147
|
+
* regardless of git tracking. Used by `hivelore doctor` to detect a code-map that captured far fewer
|
|
1148
|
+
* files than the repo actually holds (untracked source, or a structure the indexer missed).
|
|
1149
|
+
*/
|
|
1150
|
+
declare function countSourceFilesOnDisk(root: string, options?: {
|
|
1151
|
+
excludeDirs?: string[];
|
|
1152
|
+
}): Promise<number>;
|
|
1153
|
+
interface CodeMapQueryOptions {
|
|
1154
|
+
file?: string;
|
|
1155
|
+
symbol?: string;
|
|
1156
|
+
}
|
|
1157
|
+
declare function queryCodeMap(map: CodeMap, options: CodeMapQueryOptions): {
|
|
1158
|
+
files: Array<{
|
|
1159
|
+
path: string;
|
|
1160
|
+
entry: CodeFileEntry;
|
|
1161
|
+
}>;
|
|
1162
|
+
};
|
|
1163
|
+
|
|
1164
|
+
interface AstExport {
|
|
1165
|
+
name: string;
|
|
1166
|
+
kind: CodeExportKind;
|
|
1167
|
+
/** 1-based line of the declaration (or the `export`/assignment that introduces it). */
|
|
1168
|
+
line: number;
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Parse a source file into its exported symbols using a real AST. Returns null when AST parsing is
|
|
1172
|
+
* unavailable for this file (unsupported extension, runtime init failure, or grammar load failure),
|
|
1173
|
+
* which signals the caller to use the regex fallback. An empty array means "parsed fine, no exports".
|
|
1174
|
+
*/
|
|
1175
|
+
declare function parseFileAst(source: string, ext: string): Promise<AstExport[] | null>;
|
|
1176
|
+
|
|
1177
|
+
declare const CONFIG_FILE = "haive.config.json";
|
|
1178
|
+
/** A remote or local repo to pull shared memories from. */
|
|
1179
|
+
interface CrossRepoSource {
|
|
1180
|
+
/** Human-readable name for this source (used in imported memory tags). */
|
|
1181
|
+
name: string;
|
|
1182
|
+
/** Local filesystem path to the other project's root (relative or absolute). */
|
|
1183
|
+
path?: string;
|
|
1184
|
+
/** Git URL — clone/fetch performed automatically. */
|
|
1185
|
+
git?: string;
|
|
1186
|
+
/** Only import memories matching all of these filters. */
|
|
1187
|
+
filter?: {
|
|
1188
|
+
/** Only import memories with these tags. */
|
|
1189
|
+
tags?: string[];
|
|
1190
|
+
/** Only import memories of these types. */
|
|
1191
|
+
types?: string[];
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
/** An API contract file to snapshot and monitor for breaking changes. */
|
|
1195
|
+
interface ContractFile {
|
|
1196
|
+
/** Human-readable name for this contract. */
|
|
1197
|
+
name: string;
|
|
1198
|
+
/** Path to the contract file, relative to the project root. */
|
|
1199
|
+
path: string;
|
|
1200
|
+
/** Format of the contract file. */
|
|
1201
|
+
format: "openapi" | "graphql" | "proto" | "typescript" | "json-schema";
|
|
1202
|
+
}
|
|
1203
|
+
interface HaiveConfig {
|
|
1204
|
+
/** Autopilot mode: maximum autonomy, minimum human intervention. Default: false. */
|
|
1205
|
+
autopilot?: boolean;
|
|
1206
|
+
/**
|
|
1207
|
+
* Adaptive briefing: when get_briefing finds no team-specific (unguessable) memory for the
|
|
1208
|
+
* task/files, trim the auto-generated project context so the call stays near-zero-cost.
|
|
1209
|
+
* A capable model needs nothing extra in that case. Curated context is never trimmed.
|
|
1210
|
+
* Default: true.
|
|
1211
|
+
*/
|
|
1212
|
+
adaptiveBriefing?: boolean;
|
|
1213
|
+
/**
|
|
1214
|
+
* Memory tags that are EXCLUDED from automatic surfacing in `get_briefing` / `mem_relevant_to`.
|
|
1215
|
+
* Purpose: break the self-reinforcing bias loop where strategy/positioning memories get auto-injected
|
|
1216
|
+
* into every agent's context and shape its *opinions* (not just its facts). Excluded memories are
|
|
1217
|
+
* still fully searchable via explicit `mem_search` / `memory search` — they just don't auto-load.
|
|
1218
|
+
* Matching is case-insensitive on frontmatter tags. Default targets clearly-meta tags; override
|
|
1219
|
+
* (set to `[]` to disable). Note: this filters by TAG, so tag strategy notes accordingly.
|
|
1220
|
+
*/
|
|
1221
|
+
briefingExcludeTags?: string[];
|
|
1222
|
+
/** Default scope for new memories. Default: "personal". Autopilot sets "team". */
|
|
1223
|
+
defaultScope?: "personal" | "team";
|
|
1224
|
+
/**
|
|
1225
|
+
* Default status for new memories saved via mem_save.
|
|
1226
|
+
* Autopilot sets "validated" — skips the approval cycle entirely.
|
|
1227
|
+
* Default: "draft".
|
|
1228
|
+
*/
|
|
1229
|
+
defaultStatus?: "draft" | "validated";
|
|
1230
|
+
/** Auto-approve proposed memories after N hours without rejection. Default: null (disabled). */
|
|
1231
|
+
autoApproveDelayHours?: number | null;
|
|
1232
|
+
/**
|
|
1233
|
+
* Auto-promote proposed→validated after N reads (overrides DEFAULT_AUTO_PROMOTE_RULE).
|
|
1234
|
+
* Autopilot sets 1 (immediate on first use).
|
|
1235
|
+
*/
|
|
1236
|
+
autoPromoteMinReads?: number;
|
|
1237
|
+
/** Auto-save session recap on MCP server exit. Default: true in autopilot, false otherwise. */
|
|
1238
|
+
autoSessionEnd?: boolean;
|
|
1239
|
+
/**
|
|
1240
|
+
* Persist an auto-generated `session_recap` MEMORY into the `.ai/` corpus on automatic
|
|
1241
|
+
* session end (MCP exit / SessionEnd hook). Default: true (preserves historical behavior).
|
|
1242
|
+
* Set false to stop the low-signal recap dump from accumulating in — and biasing — the corpus;
|
|
1243
|
+
* pair with `sessionHandoff` for an ephemeral NEXT.md handoff instead. Has no effect on a
|
|
1244
|
+
* manual `hivelore session end --goal ...` (an explicit recap is always honored).
|
|
1245
|
+
*/
|
|
1246
|
+
autoSessionRecap?: boolean;
|
|
1247
|
+
/**
|
|
1248
|
+
* On automatic session end, write/overwrite an ephemeral `NEXT.md` handoff at the repo root
|
|
1249
|
+
* (open threads + next steps). Default: false. Meant to be gitignored — one overwritten file,
|
|
1250
|
+
* not an accumulating corpus memory. Surfaced as `last_session` when no recap memory exists.
|
|
1251
|
+
*/
|
|
1252
|
+
sessionHandoff?: boolean;
|
|
1253
|
+
/**
|
|
1254
|
+
* Auto-generate a minimal project context from code-map when project-context.md is still
|
|
1255
|
+
* the template. Default: true in autopilot, false otherwise.
|
|
1256
|
+
*/
|
|
1257
|
+
autoContext?: boolean;
|
|
1258
|
+
/**
|
|
1259
|
+
* Safe self-maintenance performed automatically in autopilot mode.
|
|
1260
|
+
* These repairs are intentionally conservative: no guessed anchor is applied
|
|
1261
|
+
* without strong evidence, but headings/tags/indexes/context metadata can be
|
|
1262
|
+
* kept fresh by the tool itself.
|
|
1263
|
+
*/
|
|
1264
|
+
autoRepair?: {
|
|
1265
|
+
/** Keep .ai/project-context.md version metadata aligned with package.json. */
|
|
1266
|
+
context?: boolean;
|
|
1267
|
+
/** Apply safe memory lint fixes: headings and `needs_anchor` tags. */
|
|
1268
|
+
corpus?: boolean;
|
|
1269
|
+
/** Refresh .ai/code-map.json during sync when needed. */
|
|
1270
|
+
codeMap?: boolean;
|
|
1271
|
+
/** Best-effort build of code-search embeddings when @hivelore/embeddings is available. */
|
|
1272
|
+
codeSearch?: boolean;
|
|
1273
|
+
};
|
|
1274
|
+
/**
|
|
1275
|
+
* Other repos to pull `shared`-scoped memories from during `hivelore sync`.
|
|
1276
|
+
* Each source must have either `path` (local) or `git` (remote URL).
|
|
1277
|
+
*
|
|
1278
|
+
* Example:
|
|
1279
|
+
* { "name": "backend", "path": "../repo-backend", "filter": { "tags": ["api-contract"] } }
|
|
1280
|
+
*/
|
|
1281
|
+
crossRepoSources?: CrossRepoSource[];
|
|
1282
|
+
/**
|
|
1283
|
+
* API contract files to snapshot and watch for breaking changes.
|
|
1284
|
+
* `hivelore sync` compares the current file against `.ai/contracts/<name>.lock`
|
|
1285
|
+
* and creates a `gotcha` memory if a breaking change is detected.
|
|
1286
|
+
*
|
|
1287
|
+
* Example:
|
|
1288
|
+
* { "name": "payment-api", "path": "docs/openapi.yaml", "format": "openapi" }
|
|
1289
|
+
*/
|
|
1290
|
+
contractFiles?: ContractFile[];
|
|
1291
|
+
/**
|
|
1292
|
+
* Local path to a shared team-knowledge hub repo.
|
|
1293
|
+
* Used by `hivelore hub pull` and `hivelore hub push`.
|
|
1294
|
+
* Can be relative (resolved from project root) or absolute.
|
|
1295
|
+
*/
|
|
1296
|
+
hubPath?: string;
|
|
1297
|
+
/**
|
|
1298
|
+
* Lock file paths to watch for dependency version changes.
|
|
1299
|
+
* Auto-detected if not specified (package.json, pom.xml, go.mod, etc.).
|
|
1300
|
+
* Set to [] to disable dependency tracking entirely.
|
|
1301
|
+
*/
|
|
1302
|
+
dependencyFiles?: string[];
|
|
1303
|
+
/**
|
|
1304
|
+
* Agent-enforcement settings. Enabled by default so initialized projects
|
|
1305
|
+
* treat Hivelore as infrastructure, not an optional convention.
|
|
1306
|
+
*/
|
|
1307
|
+
enforcement?: {
|
|
1308
|
+
/** Enforcement posture: advisory reports only, warn in hooks, or block workflow gates. */
|
|
1309
|
+
mode?: "off" | "advisory" | "strict";
|
|
1310
|
+
/** Require get_briefing / mem_relevant_to before state-changing MCP tools. */
|
|
1311
|
+
requireBriefingFirst?: boolean;
|
|
1312
|
+
/**
|
|
1313
|
+
* Pre-edit (PreToolUse) behaviour when a file's anchored policy was not yet surfaced:
|
|
1314
|
+
* - "advise" (default): inject the relevant memory content into the agent's context and record
|
|
1315
|
+
* it in the briefing marker, then ALLOW the edit — no round-trip, no separate briefing command.
|
|
1316
|
+
* - "block": hard-block the edit until a briefing covers the file (the legacy strict behaviour).
|
|
1317
|
+
* The commit-time decision-coverage gate and CI enforcement remain the hard backstops either way.
|
|
1318
|
+
*/
|
|
1319
|
+
preEditGate?: "advise" | "block";
|
|
1320
|
+
/** Require a session recap before pre-push / CI gates pass. */
|
|
1321
|
+
requireSessionRecap?: boolean;
|
|
1322
|
+
/** Require memory anchor verification before pre-commit / CI gates pass. */
|
|
1323
|
+
requireMemoryVerify?: boolean;
|
|
1324
|
+
/** Block changes when anchored decisions/gotchas have become stale. */
|
|
1325
|
+
blockStaleDecisionChanges?: boolean;
|
|
1326
|
+
/** Require changed files to be covered by relevant surfaced decisions/policies. */
|
|
1327
|
+
requireDecisionCoverage?: boolean;
|
|
1328
|
+
/**
|
|
1329
|
+
* How hard the pre-commit anti-pattern gate blocks a matching attempt/gotcha:
|
|
1330
|
+
* - off: never block on anti-patterns (report only)
|
|
1331
|
+
* - review: block only on a very strong semantic match (score ≥ 0.75) — soft, legacy default
|
|
1332
|
+
* - anchored: ALSO block when a high-confidence anti-pattern is anchored to a touched file
|
|
1333
|
+
* and corroborated by the diff (literal token or semantic ≥ 0.45). High precision.
|
|
1334
|
+
* - strict: block on any high-confidence anti-pattern match (anchor, literal, or semantic)
|
|
1335
|
+
* Config/docs-only commits are always downgraded regardless of this setting.
|
|
1336
|
+
* Default: "anchored" — makes "known bad approaches are blocked" true for the precise case.
|
|
1337
|
+
*/
|
|
1338
|
+
antiPatternGate?: "off" | "review" | "anchored" | "strict";
|
|
1339
|
+
/**
|
|
1340
|
+
* First-agent bootstrap gate. The trigger is the COLD STATE of the corpus, not a command or flag:
|
|
1341
|
+
* when the knowledge layer is empty, the very first agent is forced to fill the baseline — a filled
|
|
1342
|
+
* project-context, a module context per component, an anchored memory per main code area, and a
|
|
1343
|
+
* sensor per main code area — before its commit / `enforce finish` can pass. Once the baseline
|
|
1344
|
+
* exists the gate is silent for every later agent, so only the first agent ever pays.
|
|
1345
|
+
* - off: never gate on bootstrap completeness
|
|
1346
|
+
* - warn: surface the missing baseline as a warning (advisory)
|
|
1347
|
+
* - block: hard-fail commit/finish until the baseline exists (default)
|
|
1348
|
+
* Config/docs-only commits (no production code changed) are downgraded to a warning regardless.
|
|
1349
|
+
*/
|
|
1350
|
+
bootstrapGate?: "off" | "warn" | "block";
|
|
1351
|
+
/**
|
|
1352
|
+
* Pre-commit/pre-push decision-coverage behaviour. When true (default), the gate SURFACES the
|
|
1353
|
+
* relevant anchored decisions/policies itself and records them in the session marker at commit
|
|
1354
|
+
* time — no separate `hivelore briefing` step required. Set false for the strict legacy behaviour
|
|
1355
|
+
* where the commit is blocked until a prior briefing covered those decisions.
|
|
1356
|
+
*/
|
|
1357
|
+
autoBrief?: boolean;
|
|
1358
|
+
/**
|
|
1359
|
+
* Execute `kind: "shell" | "test"` memory sensors during `hivelore sensors check`.
|
|
1360
|
+
* These run arbitrary repo-authored commands, so they are OFF by default; turn on per repo
|
|
1361
|
+
* (or pass `--commands`) once the team trusts the sensors. Regex sensors always run. Default false.
|
|
1362
|
+
*/
|
|
1363
|
+
runCommandSensors?: boolean;
|
|
1364
|
+
/**
|
|
1365
|
+
* How `hivelore enforce finish` reacts to hard failures observed this session that were never
|
|
1366
|
+
* captured as a lesson (`mem_tried`):
|
|
1367
|
+
* - off: ignore
|
|
1368
|
+
* - warn: surface them as an info finding (default — failure detection has false positives)
|
|
1369
|
+
* - block: hard-block finish until each is captured
|
|
1370
|
+
* Default: "warn".
|
|
1371
|
+
*/
|
|
1372
|
+
failureCaptureGate?: "off" | "warn" | "block";
|
|
1373
|
+
/**
|
|
1374
|
+
* How `hivelore eval --ci` reacts to a harness-quality regression vs the recorded baseline:
|
|
1375
|
+
* - off: never block
|
|
1376
|
+
* - warn: report the drop (default)
|
|
1377
|
+
* - block: exit non-zero on any score drop
|
|
1378
|
+
* Default: "warn".
|
|
1379
|
+
*/
|
|
1380
|
+
evalRegressionGate?: "off" | "warn" | "block";
|
|
1381
|
+
/**
|
|
1382
|
+
* Default unread-age window (in days) for `hivelore memory archive` corpus decay.
|
|
1383
|
+
* A noisy or stale corpus is actively harmful — it makes the agent follow outdated policy.
|
|
1384
|
+
* Default: 180.
|
|
1385
|
+
*/
|
|
1386
|
+
decayAfterDays?: number;
|
|
1387
|
+
/** Minimum score required for strict enforcement gates. */
|
|
1388
|
+
scoreThreshold?: number;
|
|
1389
|
+
/** Remove generated Hivelore runtime/cache files during cleanup gates. */
|
|
1390
|
+
cleanupGeneratedArtifacts?: boolean;
|
|
1391
|
+
/**
|
|
1392
|
+
* MCP tool surface:
|
|
1393
|
+
* - enforcement: compact default harness for coding agents
|
|
1394
|
+
* - maintenance: corpus/admin tools for humans and team stewards
|
|
1395
|
+
* - experimental: research/diagnostic tools that are not core product surface
|
|
1396
|
+
* - full: legacy alias for experimental
|
|
1397
|
+
*/
|
|
1398
|
+
toolProfile?: "enforcement" | "maintenance" | "experimental" | "full";
|
|
1399
|
+
/** Named memory/policy families enabled for this project. */
|
|
1400
|
+
policyPacks?: string[];
|
|
1401
|
+
/**
|
|
1402
|
+
* Branch on which `enforce finish` enforces the release discipline (lockstep version bump +
|
|
1403
|
+
* matching pushed tag) as a HARD gate. On any other branch — feature/* or an integration branch
|
|
1404
|
+
* like `develop` — those same checks are advisory (warn), since the version/tag are produced when
|
|
1405
|
+
* releasing from this branch, not on every integration commit. Default: "main".
|
|
1406
|
+
*/
|
|
1407
|
+
releaseBranch?: string;
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
/**
|
|
1411
|
+
* Tags excluded from automatic briefing surfacing by default. These are "meta" tags — strategy,
|
|
1412
|
+
* positioning, competitive analysis, roadmaps — whose memories bias an agent's opinions rather than
|
|
1413
|
+
* inform a concrete coding task. Still searchable via explicit mem_search.
|
|
1414
|
+
*/
|
|
1415
|
+
declare const DEFAULT_BRIEFING_EXCLUDE_TAGS: string[];
|
|
1416
|
+
declare const DEFAULT_CONFIG: HaiveConfig;
|
|
1417
|
+
declare const AUTOPILOT_DEFAULTS: HaiveConfig;
|
|
1418
|
+
/** The pre-commit anti-pattern gate hardness levels. */
|
|
1419
|
+
type AntiPatternGate = "off" | "review" | "anchored" | "strict";
|
|
1420
|
+
/**
|
|
1421
|
+
* Single source of truth mapping a configured `antiPatternGate` to the
|
|
1422
|
+
* `pre_commit_check` parameters that implement it. Both the git-hook path
|
|
1423
|
+
* (`hivelore enforce check`) and the standalone `hivelore precommit` command derive
|
|
1424
|
+
* their behavior from this so the two surfaces can never drift apart.
|
|
1425
|
+
*/
|
|
1426
|
+
declare function antiPatternGateParams(gate: AntiPatternGate): {
|
|
1427
|
+
block_on: "any" | "high-confidence" | "never";
|
|
1428
|
+
anchored_blocks: boolean;
|
|
1429
|
+
};
|
|
1430
|
+
declare function configPath(paths: HaivePaths): string;
|
|
1431
|
+
declare function loadConfig(paths: HaivePaths): Promise<HaiveConfig>;
|
|
1432
|
+
declare function loadConfigSync(paths: HaivePaths): HaiveConfig;
|
|
1433
|
+
declare function saveConfig(paths: HaivePaths, config: HaiveConfig): Promise<void>;
|
|
1434
|
+
|
|
1435
|
+
interface CrossRepoReport {
|
|
1436
|
+
source: string;
|
|
1437
|
+
imported: string[];
|
|
1438
|
+
updated: string[];
|
|
1439
|
+
skipped: string[];
|
|
1440
|
+
errors: string[];
|
|
1441
|
+
}
|
|
1442
|
+
/**
|
|
1443
|
+
* Pull shared memories from all configured cross-repo sources.
|
|
1444
|
+
* Returns one report per source.
|
|
1445
|
+
*/
|
|
1446
|
+
declare function pullCrossRepoSources(paths: HaivePaths, config: HaiveConfig, projectRoot: string): Promise<CrossRepoReport[]>;
|
|
1447
|
+
|
|
1448
|
+
interface DependencySnapshot {
|
|
1449
|
+
file: string;
|
|
1450
|
+
format: string;
|
|
1451
|
+
captured_at: string;
|
|
1452
|
+
deps: Record<string, string>;
|
|
1453
|
+
}
|
|
1454
|
+
interface DepChange {
|
|
1455
|
+
name: string;
|
|
1456
|
+
from: string;
|
|
1457
|
+
to: string;
|
|
1458
|
+
/** true if the major version number changed */
|
|
1459
|
+
isMajorBump: boolean;
|
|
1460
|
+
}
|
|
1461
|
+
interface DepTrackResult {
|
|
1462
|
+
file: string;
|
|
1463
|
+
changes: DepChange[];
|
|
1464
|
+
}
|
|
1465
|
+
/**
|
|
1466
|
+
* Resolve which manifest files to track.
|
|
1467
|
+
* Uses config.dependencyFiles if set, otherwise auto-detects from KNOWN_MANIFESTS.
|
|
1468
|
+
*/
|
|
1469
|
+
declare function resolveManifestFiles(projectRoot: string, configuredFiles?: string[]): string[];
|
|
1470
|
+
/**
|
|
1471
|
+
* Check all manifest files for version changes since last snapshot.
|
|
1472
|
+
* Returns one result per file that has changes.
|
|
1473
|
+
*/
|
|
1474
|
+
declare function trackDependencies(projectRoot: string, haiveDir: string, manifestFiles: string[]): Promise<DepTrackResult[]>;
|
|
1475
|
+
|
|
1476
|
+
interface ContractSnapshot {
|
|
1477
|
+
name: string;
|
|
1478
|
+
path: string;
|
|
1479
|
+
format: string;
|
|
1480
|
+
captured_at: string;
|
|
1481
|
+
hash: string;
|
|
1482
|
+
endpoints?: string[];
|
|
1483
|
+
types?: string[];
|
|
1484
|
+
fields?: Record<string, string[]>;
|
|
1485
|
+
raw_lines?: string[];
|
|
1486
|
+
}
|
|
1487
|
+
interface BreakingChange {
|
|
1488
|
+
kind: "endpoint_removed" | "endpoint_added" | "type_removed" | "type_added" | "field_removed" | "field_added" | "content_changed";
|
|
1489
|
+
description: string;
|
|
1490
|
+
severity: "breaking" | "additive" | "unknown";
|
|
1491
|
+
}
|
|
1492
|
+
interface ContractDiffResult {
|
|
1493
|
+
contract: string;
|
|
1494
|
+
file: string;
|
|
1495
|
+
changes: BreakingChange[];
|
|
1496
|
+
unchanged: boolean;
|
|
1497
|
+
}
|
|
1498
|
+
declare function contractLockPath(haiveDir: string, name: string): string;
|
|
1499
|
+
/**
|
|
1500
|
+
* Take a snapshot of a contract file and save it to .ai/contracts/<name>.lock.
|
|
1501
|
+
* Returns the snapshot.
|
|
1502
|
+
*/
|
|
1503
|
+
declare function snapshotContract(projectRoot: string, haiveDir: string, contract: ContractFile): Promise<ContractSnapshot>;
|
|
1504
|
+
/**
|
|
1505
|
+
* Compare a contract file against its stored snapshot.
|
|
1506
|
+
* Returns the diff result. If no snapshot exists, creates one and returns unchanged.
|
|
1507
|
+
*/
|
|
1508
|
+
declare function diffContract(projectRoot: string, haiveDir: string, contract: ContractFile): Promise<ContractDiffResult>;
|
|
1509
|
+
/**
|
|
1510
|
+
* Check all configured contract files for changes.
|
|
1511
|
+
*/
|
|
1512
|
+
declare function watchContracts(projectRoot: string, haiveDir: string, contractFiles: ContractFile[]): Promise<ContractDiffResult[]>;
|
|
1513
|
+
|
|
1514
|
+
declare const USAGE_LOG_FILE = "tool-usage.jsonl";
|
|
1515
|
+
declare const USAGE_LOG_DIR = ".usage";
|
|
1516
|
+
interface UsageEvent {
|
|
1517
|
+
/** ISO timestamp */
|
|
1518
|
+
at: string;
|
|
1519
|
+
/** Tool name (MCP tool or CLI command) */
|
|
1520
|
+
tool: string;
|
|
1521
|
+
/** Truncated, non-sensitive snapshot of the input */
|
|
1522
|
+
summary?: string;
|
|
1523
|
+
}
|
|
1524
|
+
declare function usageLogPath(paths: HaivePaths): string;
|
|
1525
|
+
/**
|
|
1526
|
+
* Append a single usage event to the rolling log. Best-effort: failures are
|
|
1527
|
+
* swallowed since logging must never block tool execution.
|
|
1528
|
+
*/
|
|
1529
|
+
declare function appendUsageEvent(paths: HaivePaths, event: UsageEvent): Promise<void>;
|
|
1530
|
+
/**
|
|
1531
|
+
* Read all usage events from disk. Skips malformed lines silently.
|
|
1532
|
+
* For very large logs (>50k lines), prefer `streamUsageEvents` (not implemented yet).
|
|
1533
|
+
*/
|
|
1534
|
+
declare function readUsageEvents(paths: HaivePaths): Promise<UsageEvent[]>;
|
|
1535
|
+
interface UsageAggregate {
|
|
1536
|
+
total: number;
|
|
1537
|
+
by_tool: Array<{
|
|
1538
|
+
tool: string;
|
|
1539
|
+
count: number;
|
|
1540
|
+
last_used: string;
|
|
1541
|
+
}>;
|
|
1542
|
+
/** Most-frequently called tools first */
|
|
1543
|
+
top: Array<{
|
|
1544
|
+
tool: string;
|
|
1545
|
+
count: number;
|
|
1546
|
+
}>;
|
|
1547
|
+
window_start: string | null;
|
|
1548
|
+
window_end: string | null;
|
|
1549
|
+
}
|
|
1550
|
+
/**
|
|
1551
|
+
* Bucket events by tool, optionally filtered by a since cutoff (ISO date or relative like '7d').
|
|
1552
|
+
*/
|
|
1553
|
+
declare function aggregateUsage(events: UsageEvent[], since?: Date): UsageAggregate;
|
|
1554
|
+
/**
|
|
1555
|
+
* Parse a since string: ISO date, or relative like '7d', '24h', '30m'.
|
|
1556
|
+
* Returns null when input is empty/undefined.
|
|
1557
|
+
*/
|
|
1558
|
+
declare function parseSince(input: string | undefined): Date | null;
|
|
1559
|
+
declare function usageLogSize(paths: HaivePaths): Promise<{
|
|
1560
|
+
exists: boolean;
|
|
1561
|
+
size_bytes: number;
|
|
1562
|
+
lines: number;
|
|
1563
|
+
}>;
|
|
1564
|
+
|
|
1565
|
+
/**
|
|
1566
|
+
* Named budgets for get_briefing so agents choose quality vs token cost intentionally.
|
|
1567
|
+
*/
|
|
1568
|
+
type BriefingBudgetPreset = "quick" | "balanced" | "deep";
|
|
1569
|
+
interface BriefingBudgetNumbers {
|
|
1570
|
+
max_tokens: number;
|
|
1571
|
+
max_memories: number;
|
|
1572
|
+
include_module_contexts: boolean;
|
|
1573
|
+
}
|
|
1574
|
+
declare const BRIEFING_PRESET_DEFAULTS: Record<BriefingBudgetPreset, BriefingBudgetNumbers>;
|
|
1575
|
+
/**
|
|
1576
|
+
* Merge preset-derived numbers with caller overrides when no preset was selected.
|
|
1577
|
+
*/
|
|
1578
|
+
declare function resolveBriefingBudget(preset: BriefingBudgetPreset | undefined, overrides: BriefingBudgetNumbers): BriefingBudgetNumbers;
|
|
1579
|
+
|
|
1580
|
+
/**
|
|
1581
|
+
* Strip memory markdown down to actionable bullet lines — cheaper for briefing payloads.
|
|
1582
|
+
*/
|
|
1583
|
+
/**
|
|
1584
|
+
* Prefer markdown list lines; fall back to the first substantive paragraph block.
|
|
1585
|
+
*/
|
|
1586
|
+
declare function extractActionsBriefBody(markdown: string, maxChars?: number): string;
|
|
1587
|
+
|
|
1588
|
+
interface ResolveProjectInfo {
|
|
1589
|
+
cwd: string;
|
|
1590
|
+
resolved_root: string;
|
|
1591
|
+
haive_project_root_env: string | null;
|
|
1592
|
+
explicit_root: boolean;
|
|
1593
|
+
haive_dir_exists: boolean;
|
|
1594
|
+
memories_dir_exists: boolean;
|
|
1595
|
+
runtime_dir: string;
|
|
1596
|
+
/** Which of `.ai`, `.git`, `package.json` exist at `resolved_root`. */
|
|
1597
|
+
markers_found: string[];
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1600
|
+
* Resolve the Hivelore project root for diagnostics (MCP / CLI). Never throws.
|
|
1601
|
+
*/
|
|
1602
|
+
declare function resolveProjectInfo(opts?: {
|
|
1603
|
+
cwd?: string;
|
|
1604
|
+
env?: NodeJS.ProcessEnv;
|
|
1605
|
+
}): ResolveProjectInfo;
|
|
1606
|
+
|
|
1607
|
+
/**
|
|
1608
|
+
* Suggest a stable `topic` frontmatter key (topic-upsert) from type + title.
|
|
1609
|
+
*/
|
|
1610
|
+
declare function suggestTopicKey(type: string, titleOrPhrase: string): {
|
|
1611
|
+
topic_key: string;
|
|
1612
|
+
family: string;
|
|
1613
|
+
};
|
|
1614
|
+
|
|
1615
|
+
interface LexicalRankResult {
|
|
1616
|
+
ranked: LoadedMemory[];
|
|
1617
|
+
scores: number[];
|
|
1618
|
+
}
|
|
1619
|
+
/**
|
|
1620
|
+
* Okapi-BM25–style ranking over a small in-memory corpus (no extra index file).
|
|
1621
|
+
*/
|
|
1622
|
+
declare function rankMemoriesLexical(loadedMemories: LoadedMemory[], query: string, limit: number): LexicalRankResult;
|
|
1623
|
+
|
|
1624
|
+
declare function firstMemoryOneLine(body: string): string;
|
|
1625
|
+
interface TimelineEntry {
|
|
1626
|
+
id: string;
|
|
1627
|
+
type: string;
|
|
1628
|
+
scope: string;
|
|
1629
|
+
created_at: string;
|
|
1630
|
+
one_line: string;
|
|
1631
|
+
topic?: string;
|
|
1632
|
+
}
|
|
1633
|
+
interface CollectTimelineOpts {
|
|
1634
|
+
memoryId?: string;
|
|
1635
|
+
topic?: string;
|
|
1636
|
+
limit: number;
|
|
1637
|
+
}
|
|
1638
|
+
/**
|
|
1639
|
+
* Memories related by id seed (related_ids, shared topic, anchor overlap) or by topic alone.
|
|
1640
|
+
*/
|
|
1641
|
+
declare function collectTimelineEntries(all: LoadedMemory[], opts: CollectTimelineOpts): {
|
|
1642
|
+
entries: TimelineEntry[];
|
|
1643
|
+
notice?: string;
|
|
1644
|
+
};
|
|
1645
|
+
|
|
1646
|
+
interface ConflictCandidatesOpts {
|
|
1647
|
+
sinceDays: number;
|
|
1648
|
+
types: string[];
|
|
1649
|
+
minJaccard: number;
|
|
1650
|
+
maxPairs: number;
|
|
1651
|
+
/** Hard cap on memories considered ( avoids O(n²) explosions). */
|
|
1652
|
+
maxScan: number;
|
|
1653
|
+
}
|
|
1654
|
+
interface ConflictCandidatePair {
|
|
1655
|
+
id_a: string;
|
|
1656
|
+
id_b: string;
|
|
1657
|
+
jaccard: number;
|
|
1658
|
+
}
|
|
1659
|
+
interface TopicStatusPair {
|
|
1660
|
+
id_a: string;
|
|
1661
|
+
id_b: string;
|
|
1662
|
+
topic: string;
|
|
1663
|
+
status_a: string;
|
|
1664
|
+
status_b: string;
|
|
1665
|
+
}
|
|
1666
|
+
/**
|
|
1667
|
+
* Same `topic` key with opposed trust (validated vs rejected) — advisory; use `mem_conflicts_with` per id next.
|
|
1668
|
+
*/
|
|
1669
|
+
declare function findTopicStatusConflictPairs(memories: LoadedMemory[], maxPairs: number): TopicStatusPair[];
|
|
1670
|
+
/**
|
|
1671
|
+
* Bulk heuristic: lexical similarity pairs for human review → often followed by `mem_conflicts_with`.
|
|
1672
|
+
*/
|
|
1673
|
+
declare function findLexicalConflictPairs(memories: LoadedMemory[], opts: ConflictCandidatesOpts): {
|
|
1674
|
+
pairs: ConflictCandidatePair[];
|
|
1675
|
+
scanned: number;
|
|
1676
|
+
truncated: boolean;
|
|
1677
|
+
};
|
|
1678
|
+
|
|
1679
|
+
declare const RUNTIME_JOURNAL_FILENAME = "session-journal.ndjson";
|
|
1680
|
+
interface RuntimeJournalEntry {
|
|
1681
|
+
ts: string;
|
|
1682
|
+
kind: "note" | "session_end" | "mcp";
|
|
1683
|
+
/** Short human or agent message */
|
|
1684
|
+
message: string;
|
|
1685
|
+
/** Optional MCP tool name for kind=mcp */
|
|
1686
|
+
tool?: string;
|
|
1687
|
+
/** Arbitrary JSON-serializable metadata */
|
|
1688
|
+
meta?: Record<string, unknown>;
|
|
1689
|
+
}
|
|
1690
|
+
declare function runtimeJournalPath(paths: HaivePaths): string;
|
|
1691
|
+
/**
|
|
1692
|
+
* Append one NDJSON line under `.ai/.runtime/` (untracked by default).
|
|
1693
|
+
* Never throws to callers of shutdown hooks — wraps internally.
|
|
1694
|
+
*/
|
|
1695
|
+
declare function appendRuntimeJournalEntry(paths: HaivePaths, entry: Omit<RuntimeJournalEntry, "ts"> & {
|
|
1696
|
+
ts?: string;
|
|
1697
|
+
}): Promise<void>;
|
|
1698
|
+
/** Read last N valid JSON lines (oldest-first in returned array). */
|
|
1699
|
+
declare function readRuntimeJournalTail(paths: HaivePaths, limit: number): Promise<RuntimeJournalEntry[]>;
|
|
1700
|
+
|
|
1701
|
+
declare const BRIEFING_MARKER_TTL_MS: number;
|
|
1702
|
+
declare const SESSION_RECAP_TTL_MS: number;
|
|
1703
|
+
interface BriefingMarker {
|
|
1704
|
+
session_id: string;
|
|
1705
|
+
task?: string;
|
|
1706
|
+
source: string;
|
|
1707
|
+
created_at: string;
|
|
1708
|
+
root: string;
|
|
1709
|
+
memory_ids?: string[];
|
|
1710
|
+
files?: string[];
|
|
1711
|
+
}
|
|
1712
|
+
declare function enforcementDir(paths: HaivePaths): string;
|
|
1713
|
+
declare function briefingMarkersDir(paths: HaivePaths): string;
|
|
1714
|
+
declare function normalizeSessionId(sessionId?: string): string;
|
|
1715
|
+
declare function briefingMarkerPath(paths: HaivePaths, sessionId?: string): string;
|
|
1716
|
+
declare function writeBriefingMarker(paths: HaivePaths, input: {
|
|
1717
|
+
sessionId?: string;
|
|
1718
|
+
task?: string;
|
|
1719
|
+
source: string;
|
|
1720
|
+
memoryIds?: string[];
|
|
1721
|
+
files?: string[];
|
|
1722
|
+
/**
|
|
1723
|
+
* Accumulate memory_ids/files with the existing fresh marker for THIS session instead of
|
|
1724
|
+
* overwriting (default true). This is what lets decision-coverage build up as the agent works:
|
|
1725
|
+
* every get_briefing call, every pre-edit injection, every `hivelore briefing` ADDS to the
|
|
1726
|
+
* session's consulted set — so a broad commit no longer requires one giant briefing covering
|
|
1727
|
+
* every relevant decision at once. Pass false to replace (e.g. starting a brand-new session).
|
|
1728
|
+
*/
|
|
1729
|
+
accumulate?: boolean;
|
|
1730
|
+
}): Promise<BriefingMarker>;
|
|
1731
|
+
declare function hasRecentBriefingMarker(paths: HaivePaths, sessionId?: string, ttlMs?: number): Promise<boolean>;
|
|
1732
|
+
declare function readRecentBriefingMarker(paths: HaivePaths, sessionId?: string, ttlMs?: number): Promise<BriefingMarker | null>;
|
|
1733
|
+
declare function isFreshIsoDate(value: string | Date, ttlMs: number, now?: number): boolean;
|
|
1734
|
+
|
|
1735
|
+
interface RetirementSignal {
|
|
1736
|
+
retired: boolean;
|
|
1737
|
+
reason?: string;
|
|
1738
|
+
}
|
|
1739
|
+
/**
|
|
1740
|
+
* Explicit lifecycle gate for records that should not be fed back to agents as active policy.
|
|
1741
|
+
*
|
|
1742
|
+
* `status=deprecated/rejected/stale` is already the hard lifecycle signal. This helper covers
|
|
1743
|
+
* softer signals that teams naturally write while curating a corpus: an `expires_when` date,
|
|
1744
|
+
* a `superseded`/`obsolete` tag, or a short body note saying the attempt is now obsolete.
|
|
1745
|
+
*
|
|
1746
|
+
* Note: a plain `fixed` tag is intentionally NOT retired by itself. Many teams keep fixed
|
|
1747
|
+
* gotchas active as regression guards. To retire one, mark it deprecated, set expires_when,
|
|
1748
|
+
* use `obsolete`/`superseded`, or write that the fixed record is kept for audit/history only.
|
|
1749
|
+
*/
|
|
1750
|
+
declare function retirementSignal(fm: MemoryFrontmatter, body?: string, now?: Date): RetirementSignal;
|
|
1751
|
+
declare function isRetiredMemory(fm: MemoryFrontmatter, body?: string, now?: Date): boolean;
|
|
1752
|
+
|
|
1753
|
+
/**
|
|
1754
|
+
* Is a regex sensor pattern brittle — over-fit to incident-specific literals that rot when code
|
|
1755
|
+
* shifts (hardcoded line numbers / ranges like `1131-1186`)? High-precision by design: digits that
|
|
1756
|
+
* live inside a character class (`[0-9]`) or quantifier (`{2,}`) generalize and are NOT flagged, so
|
|
1757
|
+
* durable patterns like `v[0-9]+\.[0-9]+` or `:\s*any\b` stay clean. Returns a short reason or null.
|
|
1758
|
+
*
|
|
1759
|
+
* Used to keep brittle legacy sensors from being counted as real protection or promoted to `block`.
|
|
1760
|
+
*/
|
|
1761
|
+
declare function sensorPatternBrittleness(pattern: string): string | null;
|
|
1762
|
+
/**
|
|
1763
|
+
* Sensors — the feedback *computational* layer of the harness.
|
|
1764
|
+
*
|
|
1765
|
+
* A memory's `sensor` turns a documented lesson (gotcha/attempt) into a deterministic
|
|
1766
|
+
* check. Unlike semantic anti-pattern matching (probabilistic, warmup-sensitive), a
|
|
1767
|
+
* regex sensor fires the same way every time, so a known mistake becomes a permanent
|
|
1768
|
+
* guardrail. Phase 1 supports `kind: "regex"` only — pure, no I/O. `shell`/`test`
|
|
1769
|
+
* sensors are recognized but not executed here (they must run from the CLI).
|
|
1770
|
+
*/
|
|
1771
|
+
interface SensorHit {
|
|
1772
|
+
/** The memory id whose sensor matched. */
|
|
1773
|
+
memory_id: string;
|
|
1774
|
+
/** The sensor that matched. */
|
|
1775
|
+
sensor: Sensor;
|
|
1776
|
+
/** Project-relative file the match was found in (when known). */
|
|
1777
|
+
file?: string;
|
|
1778
|
+
/** The matched line (trimmed, capped) — useful for review output. */
|
|
1779
|
+
matched_line?: string;
|
|
1780
|
+
/** LLM-facing self-correction message carried from the sensor. */
|
|
1781
|
+
message: string;
|
|
1782
|
+
severity: Sensor["severity"];
|
|
1783
|
+
}
|
|
1784
|
+
/** A unit of code to scan: a file path plus the text to match against. */
|
|
1785
|
+
interface SensorTarget {
|
|
1786
|
+
/** Project-relative path (used for path scoping and reporting). */
|
|
1787
|
+
path: string;
|
|
1788
|
+
/**
|
|
1789
|
+
* Text to scan. For a diff, pass only the added lines (callers should pre-filter)
|
|
1790
|
+
* so a sensor fires on "you introduced the bad pattern", not "you touched a file
|
|
1791
|
+
* that merely mentions it".
|
|
1792
|
+
*/
|
|
1793
|
+
content: string;
|
|
1794
|
+
}
|
|
1795
|
+
/**
|
|
1796
|
+
* Does this sensor apply to `path`? A sensor with no explicit `paths` (and whose
|
|
1797
|
+
* memory has no anchor paths) applies everywhere. Otherwise it applies only to the
|
|
1798
|
+
* exact file or directory prefix. Use an explicit directory path (`src/foo/`) when a
|
|
1799
|
+
* sensor should cover a whole subtree.
|
|
1800
|
+
*/
|
|
1801
|
+
declare function sensorAppliesToPath(sensor: Sensor, anchorPaths: string[], path: string): boolean;
|
|
1802
|
+
/**
|
|
1803
|
+
* Window (in added lines) searched for the `absent` (correct-usage) marker around a trigger match.
|
|
1804
|
+
*
|
|
1805
|
+
* FORWARD-biased on purpose: a risky call's required companion (e.g. an option object) is part of the
|
|
1806
|
+
* call's ARGUMENTS, which follow the call across the next few lines — so we look mostly ahead.
|
|
1807
|
+
* The lookback is tiny (catches an options-object hoisted to the line just above) but small enough
|
|
1808
|
+
* that a *separate* correct call sitting above a faulty one does NOT mask the faulty one (the live
|
|
1809
|
+
* failure that a symmetric window caused). Asymmetry > a single big symmetric window.
|
|
1810
|
+
*/
|
|
1811
|
+
declare const SENSOR_ABSENT_WINDOW = 6;
|
|
1812
|
+
declare const SENSOR_ABSENT_LOOKBACK = 2;
|
|
1813
|
+
/**
|
|
1814
|
+
* Compile a regex sensor. Returns null when the sensor is not a runnable regex
|
|
1815
|
+
* (wrong kind, missing/invalid pattern) so callers can skip it safely.
|
|
1816
|
+
*/
|
|
1817
|
+
declare function compileRegexSensor(sensor: Sensor): RegExp | null;
|
|
1818
|
+
/**
|
|
1819
|
+
* Run a single regex sensor over one target. Returns the first matching line as a hit,
|
|
1820
|
+
* or null. Deterministic and side-effect-free.
|
|
1821
|
+
*/
|
|
1822
|
+
declare function runRegexSensor(memoryId: string, sensor: Sensor, target: SensorTarget): SensorHit | null;
|
|
1823
|
+
/**
|
|
1824
|
+
* Run every memory's regex sensor against every applicable target.
|
|
1825
|
+
*
|
|
1826
|
+
* Memories without a sensor, or with a non-regex sensor, are skipped (non-regex kinds
|
|
1827
|
+
* are the CLI's responsibility). At most one hit per (memory, file) pair is returned.
|
|
1828
|
+
*/
|
|
1829
|
+
declare function runSensors(memories: Memory[], targets: SensorTarget[]): SensorHit[];
|
|
1830
|
+
/**
|
|
1831
|
+
* A shell/test sensor selected for execution — the feedback *computational* layer that a regex
|
|
1832
|
+
* can't express. The schema reserves `kind: "shell" | "test"`; this picks the ones whose memory
|
|
1833
|
+
* applies to the changed paths so the CLI can run `command` (core stays pure — it never executes).
|
|
1834
|
+
*/
|
|
1835
|
+
interface CommandSensorSpec {
|
|
1836
|
+
memory_id: string;
|
|
1837
|
+
/** Command to execute (shell or test runner invocation). */
|
|
1838
|
+
command: string;
|
|
1839
|
+
kind: "shell" | "test";
|
|
1840
|
+
severity: Sensor["severity"];
|
|
1841
|
+
/** LLM-facing self-correction message carried from the sensor. */
|
|
1842
|
+
message: string;
|
|
1843
|
+
/** Anchor/scoped paths this sensor cares about (for reporting). */
|
|
1844
|
+
paths: string[];
|
|
1845
|
+
}
|
|
1846
|
+
/**
|
|
1847
|
+
* Select the shell/test sensors that apply to `changedPaths`. With no changed paths (or a sensor
|
|
1848
|
+
* scoped to everywhere) the sensor is selected unconditionally. Pure: the caller executes commands.
|
|
1849
|
+
*/
|
|
1850
|
+
declare function selectCommandSensors(memories: Memory[], changedPaths: string[]): CommandSensorSpec[];
|
|
1851
|
+
/** Split a unified diff into per-file targets containing only added lines. */
|
|
1852
|
+
declare function sensorTargetsFromDiff(diff: string): SensorTarget[];
|
|
1853
|
+
/**
|
|
1854
|
+
* Files Hivelore itself owns/generates — scanning them with sensors self-matches the very memories
|
|
1855
|
+
* they mirror (a memory body documenting a bad pattern literally contains that pattern, and a
|
|
1856
|
+
* generated bridge re-states the block sensors). Mirrors `isHaiveOwnedPath` in the MCP
|
|
1857
|
+
* anti-pattern check; centralized here so the git-hook gate (`enforce check`) and the standalone
|
|
1858
|
+
* `sensors check` CLI can never drift apart on what counts as scannable code.
|
|
1859
|
+
*/
|
|
1860
|
+
declare const HAIVE_OWNED_FILES: ReadonlySet<string>;
|
|
1861
|
+
/**
|
|
1862
|
+
* A diff target is scannable by sensors only when it is real source — never the `.ai/` knowledge
|
|
1863
|
+
* base or a Hivelore-generated bridge/config file. Without this guard, staging an `.ai/memories/*.md`
|
|
1864
|
+
* file (whose body quotes the bad pattern) makes the sensor fire on itself — a false positive.
|
|
1865
|
+
*/
|
|
1866
|
+
declare function isSensorScannablePath(p: string): boolean;
|
|
1867
|
+
/**
|
|
1868
|
+
* Filter raw diff targets down to scannable source files. Falls back to scanning the whole diff as
|
|
1869
|
+
* one anonymous blob ONLY when the diff carried no file headers at all (e.g. a hand-fed `--diff-file`
|
|
1870
|
+
* with bare content) — never when every header was a Hivelore-owned path, so `.ai/`-only diffs scan nothing.
|
|
1871
|
+
*/
|
|
1872
|
+
declare function scannableSensorTargets(diff: string): SensorTarget[];
|
|
1873
|
+
interface SensorSelfCheck {
|
|
1874
|
+
/** The sensor stays SILENT on the current, presumed-correct code — i.e. it won't false-positive. */
|
|
1875
|
+
silent_on_current: boolean;
|
|
1876
|
+
/** Did it fire on a known-bad example from the lesson? null when no example was available. */
|
|
1877
|
+
fires_on_bad: boolean | null;
|
|
1878
|
+
/** Files whose CURRENT content the sensor matched — evidence of a false positive. */
|
|
1879
|
+
fired_on: string[];
|
|
1880
|
+
/**
|
|
1881
|
+
* Safe to hard-block: silent on the current code AND (fires on the bad example, or there was no
|
|
1882
|
+
* example to test). A sensor that fires on correct code is exactly what trains agents to ignore the
|
|
1883
|
+
* gate — this is the gate that keeps the auto-generation layer honest.
|
|
1884
|
+
*/
|
|
1885
|
+
passed: boolean;
|
|
1886
|
+
}
|
|
1887
|
+
/**
|
|
1888
|
+
* Validate a sensor before it is trusted to hard-block. Pure: the caller supplies the CURRENT
|
|
1889
|
+
* (presumed-correct) file contents and any bad examples lifted from the lesson body.
|
|
1890
|
+
*
|
|
1891
|
+
* - silent_on_current: the sensor must NOT match the current code (else it false-positives).
|
|
1892
|
+
* - fires_on_bad: if the lesson carried a bad code example, the sensor SHOULD match it.
|
|
1893
|
+
*/
|
|
1894
|
+
declare function sensorSelfCheck(sensor: Sensor, input: {
|
|
1895
|
+
currentTargets: SensorTarget[];
|
|
1896
|
+
badExamples: string[];
|
|
1897
|
+
}): SensorSelfCheck;
|
|
1898
|
+
interface ProposedSensorVerdict {
|
|
1899
|
+
/** Safe to store at the requested severity. */
|
|
1900
|
+
accepted: boolean;
|
|
1901
|
+
/** Why a block proposal was rejected (so the agent can revise and re-propose). */
|
|
1902
|
+
reason?: "fires-on-current" | "missed-bad-example" | "brittle";
|
|
1903
|
+
self_check: SensorSelfCheck;
|
|
1904
|
+
/** Brittleness reason (hardcoded line numbers, etc.) or null. */
|
|
1905
|
+
brittle: string | null;
|
|
1906
|
+
}
|
|
1907
|
+
/**
|
|
1908
|
+
* Decide whether a PROPOSED sensor may be trusted at its severity. This is the deterministic gate
|
|
1909
|
+
* behind "the agent (LLM) proposes the sensor, core validates it": a `block` sensor is accepted only
|
|
1910
|
+
* if it is NOT brittle, stays SILENT on the current (presumed-correct) code, and FIRES on the bad
|
|
1911
|
+
* example (when one is available). A `warn` sensor is always accepted (advisory). Pure.
|
|
1912
|
+
*/
|
|
1913
|
+
declare function judgeProposedSensor(sensor: Sensor, input: {
|
|
1914
|
+
currentTargets: SensorTarget[];
|
|
1915
|
+
badExamples: string[];
|
|
1916
|
+
}): ProposedSensorVerdict;
|
|
1917
|
+
/**
|
|
1918
|
+
* Pull candidate bad-code examples from a lesson body: fenced code blocks and inline code spans that
|
|
1919
|
+
* look like code (contain a call/dot/assignment). Used to confirm a generated sensor actually fires
|
|
1920
|
+
* on the mistake it describes.
|
|
1921
|
+
*/
|
|
1922
|
+
declare function extractSensorExamples(body: string): string[];
|
|
1923
|
+
/**
|
|
1924
|
+
* Extract the added lines from a unified diff (lines starting with a single `+`,
|
|
1925
|
+
* excluding the `+++` file header). Mirrors the diff-handling already used by the
|
|
1926
|
+
* anti-pattern tokenizer so sensors fire on introductions, not mere mentions.
|
|
1927
|
+
*/
|
|
1928
|
+
declare function addedLinesFromDiff(diff: string): string;
|
|
1929
|
+
|
|
1930
|
+
interface SensorSuggestionOptions {
|
|
1931
|
+
/** Extra paths to put on the sensor. Defaults to the memory anchor paths. */
|
|
1932
|
+
paths?: string[];
|
|
1933
|
+
}
|
|
1934
|
+
/**
|
|
1935
|
+
* A non-persisted sensor *seed*: a candidate pattern (and optional discriminating `absent` companion)
|
|
1936
|
+
* extracted heuristically from a lesson body, handed to the agent to PRE-FILL a `propose_sensor` call.
|
|
1937
|
+
*
|
|
1938
|
+
* A seed is NOT a live guardrail. The agent-in-the-loop write paths no longer persist heuristic
|
|
1939
|
+
* sensors onto frontmatter; instead they surface this seed so the agent can refine it and let
|
|
1940
|
+
* `propose_sensor` validate it (silent on current code, fires on the bad example) before it is trusted
|
|
1941
|
+
* to block. This is the "generate-then-validate via the LLM" pipeline — the heuristic only proposes.
|
|
1942
|
+
*/
|
|
1943
|
+
interface SensorSeed {
|
|
1944
|
+
/** Regex matching the faulty usage. */
|
|
1945
|
+
pattern: string;
|
|
1946
|
+
/** Regex for the correct-usage marker (discriminating sensor) — present for "X without Y" lessons. */
|
|
1947
|
+
absent?: string;
|
|
1948
|
+
/** LLM-facing fix message derived from the lesson. */
|
|
1949
|
+
message: string;
|
|
1950
|
+
/** Scope paths for the eventual sensor (the lesson's anchor paths). */
|
|
1951
|
+
paths: string[];
|
|
1952
|
+
}
|
|
1953
|
+
/**
|
|
1954
|
+
* Conservatively extract a sensor SEED from a gotcha/attempt body — a candidate pattern for the agent
|
|
1955
|
+
* to validate via `propose_sensor`, never a persisted live sensor.
|
|
1956
|
+
*
|
|
1957
|
+
* This helper intentionally returns null more often than it guesses: a wrong seed wastes an agent's
|
|
1958
|
+
* attention, so when no distinctive token / discriminating companion is found it yields nothing and
|
|
1959
|
+
* the agent is simply told to author the pattern itself.
|
|
1960
|
+
*/
|
|
1961
|
+
declare function suggestSensorSeed(body: string, anchorPaths: string[], options?: SensorSuggestionOptions): SensorSeed | null;
|
|
1962
|
+
/**
|
|
1963
|
+
* @deprecated The agent-in-the-loop write paths no longer persist heuristic sensors — they surface a
|
|
1964
|
+
* {@link SensorSeed} for `propose_sensor` to validate instead. This wrapper remains only for
|
|
1965
|
+
* back-compat (and the scanner-ingestion draft path, where the sensor lands on a human-reviewed
|
|
1966
|
+
* `proposed` draft and can never hard-block). It builds the old `autogen: true, severity: "warn"`
|
|
1967
|
+
* sensor from a seed. Prefer {@link suggestSensorSeed}.
|
|
1968
|
+
*/
|
|
1969
|
+
declare function suggestSensorFromMemory(body: string, anchorPaths: string[], options?: SensorSuggestionOptions): Sensor | null;
|
|
1970
|
+
|
|
1971
|
+
/**
|
|
1972
|
+
* First-agent bootstrap state — is the repo's knowledge layer filled enough for later agents to rely on?
|
|
1973
|
+
*
|
|
1974
|
+
* The trigger for forcing a bootstrap is NOT an agent identity or a flag: it is the COLD STATE of the
|
|
1975
|
+
* corpus itself. The very first agent on a fresh `hivelore init` faces an empty knowledge layer; this
|
|
1976
|
+
* assessment turns "fill everything that must be filled" into a FINITE, repo-derived checklist so the
|
|
1977
|
+
* gate has a precise, reachable DONE state. Once the baseline exists the assessment returns `ready`
|
|
1978
|
+
* and every gate that consults it goes silent — so only the first agent ever pays the cost.
|
|
1979
|
+
*
|
|
1980
|
+
* Pure domain logic: no I/O. Callers load memories / code-map / module dirs and pass them in.
|
|
1981
|
+
*/
|
|
1982
|
+
|
|
1983
|
+
type BootstrapGate = "off" | "warn" | "block";
|
|
1984
|
+
type BootstrapState = "cold" | "partial" | "ready";
|
|
1985
|
+
interface BootstrapGap {
|
|
1986
|
+
kind: "project-context" | "module-context" | "memory-coverage" | "sensor-coverage" | "index-code";
|
|
1987
|
+
/** Human/agent-readable description of what is missing. */
|
|
1988
|
+
detail: string;
|
|
1989
|
+
/** Concrete items the gap applies to (component names / areas). */
|
|
1990
|
+
items: string[];
|
|
1991
|
+
/** Exact tool/command call that closes the gap. */
|
|
1992
|
+
fix: string;
|
|
1993
|
+
}
|
|
1994
|
+
interface BootstrapMetrics {
|
|
1995
|
+
projectContextFilled: boolean;
|
|
1996
|
+
components: string[];
|
|
1997
|
+
modulesCovered: number;
|
|
1998
|
+
modulesRequired: number;
|
|
1999
|
+
memoryCoveredAreas: number;
|
|
2000
|
+
sensorCoveredAreas: number;
|
|
2001
|
+
mainAreas: number;
|
|
2002
|
+
teamMemories: number;
|
|
2003
|
+
sensors: number;
|
|
2004
|
+
}
|
|
2005
|
+
interface BootstrapAssessment {
|
|
2006
|
+
state: BootstrapState;
|
|
2007
|
+
/** Empty exactly when state === "ready". */
|
|
2008
|
+
gaps: BootstrapGap[];
|
|
2009
|
+
metrics: BootstrapMetrics;
|
|
2010
|
+
}
|
|
2011
|
+
interface BootstrapStateInput {
|
|
2012
|
+
/** Raw project-context.md content ("" if absent). */
|
|
2013
|
+
projectContextRaw: string;
|
|
2014
|
+
/** All memories loaded from .ai/memories. */
|
|
2015
|
+
memories: LoadedMemory[];
|
|
2016
|
+
/** Code file paths from the code-map (raw; this module filters out non-production files). */
|
|
2017
|
+
codeFiles: string[];
|
|
2018
|
+
/** Module names present under .ai/modules/<name>/ (directory names). */
|
|
2019
|
+
existingModules: string[];
|
|
2020
|
+
}
|
|
2021
|
+
/**
|
|
2022
|
+
* project-context is still the auto-generated template / stub rather than real content.
|
|
2023
|
+
* Matches the canonical detection used in briefing.ts / get-briefing: the auto-context marker counts
|
|
2024
|
+
* ONLY when the unfilled `TODO —` placeholders are still present (>=2). A filled context that merely
|
|
2025
|
+
* mentions "auto-generated by `hivelore init`" in prose (e.g. a glossary line) is NOT a template.
|
|
2026
|
+
*/
|
|
2027
|
+
declare function isTemplateProjectContext(raw: string): boolean;
|
|
2028
|
+
/** Derive the component (main code area) a file belongs to. */
|
|
2029
|
+
declare function componentOf(file: string): string;
|
|
2030
|
+
/** The module-context directory name for a component (its last path segment). */
|
|
2031
|
+
declare function moduleNameOf(component: string): string;
|
|
2032
|
+
/**
|
|
2033
|
+
* Assess whether the repo knowledge layer is filled enough (the EXHAUSTIVE bar):
|
|
2034
|
+
* 1. project-context.md is filled (not the template, and substantial)
|
|
2035
|
+
* 2. every detected component (≥2) has a module context
|
|
2036
|
+
* 3. every main code area has at least one anchored memory (knowledge coverage)
|
|
2037
|
+
* 4. every main code area has at least one memory carrying a sensor (guardrail coverage)
|
|
2038
|
+
*/
|
|
2039
|
+
declare function assessBootstrapState(input: BootstrapStateInput): BootstrapAssessment;
|
|
2040
|
+
/** Render the assessment's gaps as a numbered checklist for an agent (action_required / gate message). */
|
|
2041
|
+
declare function renderBootstrapChecklist(assessment: BootstrapAssessment): string;
|
|
2042
|
+
|
|
2043
|
+
/**
|
|
2044
|
+
* Findings ingestion — the self-feeding half of the sensors story (feature B).
|
|
2045
|
+
*
|
|
2046
|
+
* Phase 1/2 turned a documented mistake into an executable `sensor`. But someone still has
|
|
2047
|
+
* to *document* the mistake. Findings ingestion closes the review↔memory loop: a real defect
|
|
2048
|
+
* reported by a scanner (SonarQube, or any SARIF-emitting tool like ESLint/Semgrep/CodeQL)
|
|
2049
|
+
* becomes an anchored `gotcha`/`convention` memory, pre-filled with a conservative autogen
|
|
2050
|
+
* sensor, so the *next* agent is steered away from it before it writes the same code.
|
|
2051
|
+
*
|
|
2052
|
+
* This module is pure: parsers + draft synthesis, no I/O. The CLI (`hivelore ingest`) and the
|
|
2053
|
+
* MCP tool (`ingest_findings`) read files / write memories around these functions.
|
|
2054
|
+
*
|
|
2055
|
+
* Safety: every draft is `status: proposed` and every suggested sensor is `severity: warn`
|
|
2056
|
+
* + `autogen: true`. Ingestion never auto-validates and never auto-blocks (safety rules +
|
|
2057
|
+
* `2026-05-07-attempt-strict-precommit-gate-on-haive`). A human promotes both.
|
|
2058
|
+
*/
|
|
2059
|
+
type FindingSeverity = "info" | "minor" | "major" | "critical" | "blocker";
|
|
2060
|
+
interface Finding {
|
|
2061
|
+
/** Source tool, e.g. "sonar", "eslint", "semgrep". */
|
|
2062
|
+
tool: string;
|
|
2063
|
+
/** Rule key, e.g. "typescript:S1234" or "no-unused-vars". */
|
|
2064
|
+
ruleId: string;
|
|
2065
|
+
/** Human-readable description of the problem. */
|
|
2066
|
+
message: string;
|
|
2067
|
+
severity: FindingSeverity;
|
|
2068
|
+
/** Project-relative file path. */
|
|
2069
|
+
path: string;
|
|
2070
|
+
/** 1-based line number, when known. */
|
|
2071
|
+
line?: number;
|
|
2072
|
+
/** Offending source snippet, when the report provides one. */
|
|
2073
|
+
snippet?: string;
|
|
2074
|
+
/**
|
|
2075
|
+
* Stable dedup key: `tool:ruleId:path`. Deliberately excludes the line so re-running a
|
|
2076
|
+
* scan after unrelated edits (which shift line numbers) does not re-propose the same memory.
|
|
2077
|
+
*/
|
|
2078
|
+
key: string;
|
|
2079
|
+
}
|
|
2080
|
+
interface MemoryDraft {
|
|
2081
|
+
key: string;
|
|
2082
|
+
/** `ingest:<key>` — used as the memory `topic` so re-ingestion upserts instead of duplicating. */
|
|
2083
|
+
topic: string;
|
|
2084
|
+
frontmatter: MemoryFrontmatter;
|
|
2085
|
+
body: string;
|
|
2086
|
+
finding: Finding;
|
|
2087
|
+
/** True when a conservative sensor could be derived from the finding. */
|
|
2088
|
+
has_sensor: boolean;
|
|
2089
|
+
}
|
|
2090
|
+
interface DraftOptions {
|
|
2091
|
+
/** Memory type for the draft. Default "gotcha". */
|
|
2092
|
+
type?: "gotcha" | "convention";
|
|
2093
|
+
/** Scope for the draft. Default "team". */
|
|
2094
|
+
scope?: "personal" | "team" | "module";
|
|
2095
|
+
module?: string;
|
|
2096
|
+
author?: string;
|
|
2097
|
+
}
|
|
2098
|
+
interface DraftsOptions extends DraftOptions {
|
|
2099
|
+
/** Cap on number of drafts produced (after in-batch dedup). */
|
|
2100
|
+
limit?: number;
|
|
2101
|
+
/** Only ingest findings at or above this severity. Default: none (all). */
|
|
2102
|
+
minSeverity?: FindingSeverity;
|
|
2103
|
+
/** Include auto-fixable stylistic rules (semi/quotes/indent/prefer-const…). Default false — they are
|
|
2104
|
+
* linter-autofix noise, not lessons worth a memory. */
|
|
2105
|
+
includeStylistic?: boolean;
|
|
2106
|
+
}
|
|
2107
|
+
/** True when a finding's rule is pure auto-fixable formatting / naming convention (no lesson value as a seed). */
|
|
2108
|
+
declare function isStylisticRule(ruleId: string): boolean;
|
|
2109
|
+
/** Normalize a tool-specific severity string to the shared scale. */
|
|
2110
|
+
declare function normalizeFindingSeverity(raw: string | undefined | null): FindingSeverity;
|
|
2111
|
+
/**
|
|
2112
|
+
* Parse SARIF 2.1.0 (`runs[].results[]`). Works for any SARIF emitter (ESLint, Semgrep,
|
|
2113
|
+
* CodeQL, etc.). The tool name comes from `runs[].tool.driver.name`.
|
|
2114
|
+
*/
|
|
2115
|
+
declare function parseSarif(input: string | unknown): Finding[];
|
|
2116
|
+
/**
|
|
2117
|
+
* Parse the SonarQube issues payload (`issues[]` from `/api/issues/search`). The file path
|
|
2118
|
+
* lives in `component` as `projectKey:relative/path`; we strip the project key.
|
|
2119
|
+
*/
|
|
2120
|
+
declare function parseSonar(input: string | unknown): Finding[];
|
|
2121
|
+
/**
|
|
2122
|
+
* Parse the ESLint JSON formatter output (`eslint --format json`): an array of
|
|
2123
|
+
* `{ filePath, messages: [{ ruleId, severity, message, line }] }`. No SARIF formatter
|
|
2124
|
+
* package needed — this is ESLint's built-in format. `severity` is 2=error, 1=warning.
|
|
2125
|
+
* `opts.cwd`, when given, makes absolute `filePath`s project-relative so anchoring works.
|
|
2126
|
+
*/
|
|
2127
|
+
declare function parseEslintJson(input: string | unknown, opts?: {
|
|
2128
|
+
cwd?: string;
|
|
2129
|
+
}): Finding[];
|
|
2130
|
+
/**
|
|
2131
|
+
* Parse `npm audit --json` output (`vulnerabilities` map). Each vulnerable package becomes one
|
|
2132
|
+
* finding anchored to `package.json` (vulnerabilities are dependency-level, not file-level), so
|
|
2133
|
+
* the next agent is warned before re-introducing or ignoring the advisory.
|
|
2134
|
+
*/
|
|
2135
|
+
declare function parseNpmAudit(input: string | unknown): Finding[];
|
|
2136
|
+
type FindingFormat = "sarif" | "sonar" | "eslint" | "npm-audit";
|
|
2137
|
+
/** Dispatch to the right parser by declared format. */
|
|
2138
|
+
declare function parseFindings(format: FindingFormat, input: string | unknown, opts?: {
|
|
2139
|
+
cwd?: string;
|
|
2140
|
+
}): Finding[];
|
|
2141
|
+
/** Build the markdown body for a finding-derived memory. */
|
|
2142
|
+
declare function findingBody(finding: Finding): string;
|
|
2143
|
+
/** Convert one finding into a proposed memory draft (with a conservative sensor when derivable). */
|
|
2144
|
+
declare function findingToDraft(finding: Finding, options?: DraftOptions): MemoryDraft;
|
|
2145
|
+
/** Convert a batch of findings into drafts, deduped within the batch and capped/filtered. */
|
|
2146
|
+
declare function draftsFromFindings(findings: Finding[], options?: DraftsOptions): MemoryDraft[];
|
|
2147
|
+
/** Drop drafts whose topic already exists in the corpus (cross-run dedup). */
|
|
2148
|
+
declare function filterNewDrafts(drafts: MemoryDraft[], existingTopics: Iterable<string>): MemoryDraft[];
|
|
2149
|
+
|
|
2150
|
+
/**
|
|
2151
|
+
* Gate signal-quality — is the inferential (anti-pattern) gate earning trust or crying wolf?
|
|
2152
|
+
*
|
|
2153
|
+
* Hivelore's anti-pattern gate is probabilistic and warmup-sensitive, so it is deliberately calibrated
|
|
2154
|
+
* NOT to hard-block on weak matches. But a team needs to SEE whether the gate's signal is precise:
|
|
2155
|
+
* are its catches turning out to be real (prevented mistakes, applied lessons) or noise (rejected by
|
|
2156
|
+
* humans via `mem_feedback`)? This module turns the signals Hivelore already records — prevention events
|
|
2157
|
+
* (by source) and per-memory rejection counts — into a precision indicator and an actionable tuning
|
|
2158
|
+
* suggestion for `enforcement.antiPatternGate`. Pure: no I/O.
|
|
2159
|
+
*/
|
|
2160
|
+
|
|
2161
|
+
interface GatePrecision {
|
|
2162
|
+
/** Catches recorded by deterministic regex/command sensors. */
|
|
2163
|
+
sensor_catches: number;
|
|
2164
|
+
/** Catches recorded by the inferential anti-pattern gate. */
|
|
2165
|
+
anti_pattern_catches: number;
|
|
2166
|
+
/** Total "useful" outcomes (catches + human-applied lessons). */
|
|
2167
|
+
useful: number;
|
|
2168
|
+
/** Total human rejections (mem_feedback "not useful"). Proxy for false positives. */
|
|
2169
|
+
rejections: number;
|
|
2170
|
+
/** useful / (useful + rejections), 0..1. Null when there is no signal yet. */
|
|
2171
|
+
precision: number | null;
|
|
2172
|
+
/** A tuning recommendation for enforcement.antiPatternGate, or null when current looks right. */
|
|
2173
|
+
suggestion: GateTuningSuggestion | null;
|
|
2174
|
+
}
|
|
2175
|
+
interface GateTuningSuggestion {
|
|
2176
|
+
recommended: AntiPatternGate;
|
|
2177
|
+
reason: string;
|
|
2178
|
+
}
|
|
2179
|
+
interface GatePrecisionMetricDelta {
|
|
2180
|
+
baseline: number | null;
|
|
2181
|
+
current: number | null;
|
|
2182
|
+
delta: number | null;
|
|
2183
|
+
}
|
|
2184
|
+
interface GatePrecisionDelta {
|
|
2185
|
+
precision: GatePrecisionMetricDelta;
|
|
2186
|
+
rejections: GatePrecisionMetricDelta;
|
|
2187
|
+
/** True when humans rejected more gate output than the baseline run. */
|
|
2188
|
+
false_positives_increased: boolean;
|
|
2189
|
+
/** True when precision is known on both sides and dropped. */
|
|
2190
|
+
precision_regressed: boolean;
|
|
2191
|
+
/** CI-friendly rollup: either more false positives or lower known precision. */
|
|
2192
|
+
regressed: boolean;
|
|
2193
|
+
}
|
|
2194
|
+
/**
|
|
2195
|
+
* Compute the gate's signal quality from prevention events + usage.
|
|
2196
|
+
* @param currentGate the configured antiPatternGate, used to decide whether to suggest a change.
|
|
2197
|
+
*/
|
|
2198
|
+
declare function computeGatePrecision(events: PreventionEvent[], usage: UsageIndex, currentGate?: AntiPatternGate): GatePrecision;
|
|
2199
|
+
/**
|
|
2200
|
+
* Suggest loosening the gate when it is noisy (low precision with real rejection volume), or
|
|
2201
|
+
* tightening it when it is precise but currently soft. Returns null when current looks right or
|
|
2202
|
+
* there isn't enough signal to act on.
|
|
2203
|
+
*/
|
|
2204
|
+
declare function suggestGate(precision: number | null, rejections: number, currentGate: AntiPatternGate): GateTuningSuggestion | null;
|
|
2205
|
+
/** Compare gate signal quality against a baseline for CI regression gates. Pure. */
|
|
2206
|
+
declare function compareGatePrecision(baseline: GatePrecision, current: GatePrecision): GatePrecisionDelta;
|
|
2207
|
+
|
|
2208
|
+
interface DashboardOptions {
|
|
2209
|
+
/** How many rows to include in each "top" list. Default 10. */
|
|
2210
|
+
top?: number;
|
|
2211
|
+
/** Dormancy window for impact scoring. Defaults to impact's own default. */
|
|
2212
|
+
dormantDays?: number;
|
|
2213
|
+
now?: Date;
|
|
2214
|
+
/** Prevention event log (from `loadPreventionEvents`) — powers the trend + recurrence rollups. */
|
|
2215
|
+
preventionEvents?: PreventionEvent[];
|
|
2216
|
+
/** Configured anti-pattern gate — lets the gate-precision rollup suggest tightening/loosening. */
|
|
2217
|
+
antiPatternGate?: AntiPatternGate;
|
|
2218
|
+
}
|
|
2219
|
+
interface ImpactRow {
|
|
2220
|
+
id: string;
|
|
2221
|
+
score: number;
|
|
2222
|
+
tier: ImpactScore["tier"];
|
|
2223
|
+
signals: string[];
|
|
2224
|
+
prune_candidate: boolean;
|
|
2225
|
+
}
|
|
2226
|
+
interface SensorRow {
|
|
2227
|
+
id: string;
|
|
2228
|
+
severity: "warn" | "block";
|
|
2229
|
+
last_fired: string;
|
|
2230
|
+
}
|
|
2231
|
+
interface DormantRow {
|
|
2232
|
+
id: string;
|
|
2233
|
+
last_read_at: string | null;
|
|
2234
|
+
age_days: number;
|
|
2235
|
+
}
|
|
2236
|
+
interface PreventionRow {
|
|
2237
|
+
id: string;
|
|
2238
|
+
type: string;
|
|
2239
|
+
prevented_count: number;
|
|
2240
|
+
last_prevented_at: string | null;
|
|
2241
|
+
}
|
|
2242
|
+
interface DashboardReport {
|
|
2243
|
+
generated_at: string;
|
|
2244
|
+
inventory: {
|
|
2245
|
+
/** Policy corpus size (excludes session_recap). */
|
|
2246
|
+
total: number;
|
|
2247
|
+
session_recaps: number;
|
|
2248
|
+
active: number;
|
|
2249
|
+
retired: number;
|
|
2250
|
+
by_scope: Record<string, number>;
|
|
2251
|
+
by_type: Record<string, number>;
|
|
2252
|
+
by_status: Record<string, number>;
|
|
2253
|
+
};
|
|
2254
|
+
/** OUTCOME measurement: prevention events = times a memory's sensor/anti-pattern fired on a real
|
|
2255
|
+
* diff, intercepting a known mistake. Distinct from retrieval (reads) — demonstrated value. */
|
|
2256
|
+
prevention: {
|
|
2257
|
+
total_events: number;
|
|
2258
|
+
memories_with_catches: number;
|
|
2259
|
+
top: PreventionRow[];
|
|
2260
|
+
/** Catch volume over time (from the prevention event log). */
|
|
2261
|
+
trend: PreventionTrend;
|
|
2262
|
+
/** Lessons re-introduced after capture (caught on >= 2 distinct days). */
|
|
2263
|
+
recurrence: RecurrenceReport;
|
|
2264
|
+
};
|
|
2265
|
+
/** Inferential-gate signal quality: are catches real (useful) or noise (rejected)? + tuning hint. */
|
|
2266
|
+
gate_precision: GatePrecision;
|
|
2267
|
+
impact: ImpactSummary & {
|
|
2268
|
+
top: ImpactRow[];
|
|
2269
|
+
};
|
|
2270
|
+
sensors: {
|
|
2271
|
+
total: number;
|
|
2272
|
+
warn: number;
|
|
2273
|
+
block: number;
|
|
2274
|
+
autogen: number;
|
|
2275
|
+
fired: number;
|
|
2276
|
+
recently_fired: SensorRow[];
|
|
2277
|
+
};
|
|
2278
|
+
health: {
|
|
2279
|
+
stale: number;
|
|
2280
|
+
retired: number;
|
|
2281
|
+
/** Validated decision/gotcha/architecture memories with no anchor paths or symbols. */
|
|
2282
|
+
anchorless: number;
|
|
2283
|
+
/** Memories awaiting review (draft/proposed). */
|
|
2284
|
+
pending: number;
|
|
2285
|
+
prune_candidates: number;
|
|
2286
|
+
};
|
|
2287
|
+
decay: {
|
|
2288
|
+
decay_days: number;
|
|
2289
|
+
decaying: number;
|
|
2290
|
+
top_dormant: DormantRow[];
|
|
2291
|
+
};
|
|
2292
|
+
corpus: {
|
|
2293
|
+
/** Number of memory files (policy corpus, excludes session_recap). */
|
|
2294
|
+
memory_files: number;
|
|
2295
|
+
body_chars: number;
|
|
2296
|
+
/** Rough token estimate (~chars/4) — how heavy the corpus is to inject. */
|
|
2297
|
+
est_tokens: number;
|
|
2298
|
+
};
|
|
2299
|
+
}
|
|
2300
|
+
/** Build the full observability rollup from the loaded corpus + usage index. Pure. */
|
|
2301
|
+
declare function buildDashboard(memories: LoadedMemory[], usage: UsageIndex, options?: DashboardOptions): DashboardReport;
|
|
2302
|
+
|
|
2303
|
+
/**
|
|
2304
|
+
* Failure-capture coverage — the gate behind Hivelore's "never silently fix the same mistake" loop.
|
|
2305
|
+
*
|
|
2306
|
+
* `hivelore observe` (the PostToolUse hook) appends an observation per tool call to
|
|
2307
|
+
* `.ai/.cache/observations.jsonl`, tagging hard failures with `failure_hint: true`
|
|
2308
|
+
* (non-zero Bash exit, `error TSxxxx`, ENOENT, …). Those failures are exactly the
|
|
2309
|
+
* `mem_tried` candidates the harness wants captured — otherwise the next session repeats them.
|
|
2310
|
+
*
|
|
2311
|
+
* This module is the pure decision layer: given the failure observations and the corpus's
|
|
2312
|
+
* `attempt`/`gotcha` memories, which failures look UNCAPTURED (no lesson recorded after them)?
|
|
2313
|
+
* The CLI reads the files and turns the result into an `enforce finish` finding. No I/O here.
|
|
2314
|
+
*/
|
|
2315
|
+
interface FailureObservation {
|
|
2316
|
+
/** ISO timestamp of the observation. */
|
|
2317
|
+
ts: string;
|
|
2318
|
+
/** Tool that failed (Bash / Edit / …). */
|
|
2319
|
+
tool: string;
|
|
2320
|
+
/** Short human-readable summary of what was attempted. */
|
|
2321
|
+
summary: string;
|
|
2322
|
+
}
|
|
2323
|
+
interface UncapturedFailure {
|
|
2324
|
+
ts: string;
|
|
2325
|
+
tool: string;
|
|
2326
|
+
summary: string;
|
|
2327
|
+
}
|
|
2328
|
+
interface FailureCoverageOptions {
|
|
2329
|
+
/** Only consider failures newer than this many hours (avoid stale observations blocking forever). Default 24. */
|
|
2330
|
+
windowHours?: number;
|
|
2331
|
+
/** Collapse near-identical failures (same normalized summary) to one row. Default true. */
|
|
2332
|
+
dedupe?: boolean;
|
|
2333
|
+
now?: Date;
|
|
2334
|
+
}
|
|
2335
|
+
/**
|
|
2336
|
+
* A failure is CAPTURED when an `attempt`/`gotcha` lesson was recorded at or after it
|
|
2337
|
+
* (within the window) — the agent stopped and wrote the lesson down. Failures that pre-date
|
|
2338
|
+
* every recent capture are uncaptured: the gate should nudge (or block) on those.
|
|
2339
|
+
*
|
|
2340
|
+
* @param failures failure-tagged observations (any order)
|
|
2341
|
+
* @param captureTimes ISO created_at of every attempt/gotcha memory in the corpus
|
|
2342
|
+
*/
|
|
2343
|
+
declare function findUncapturedFailures(failures: FailureObservation[], captureTimes: string[], options?: FailureCoverageOptions): UncapturedFailure[];
|
|
2344
|
+
|
|
2345
|
+
/**
|
|
2346
|
+
* Harness coverage-gap detection — "which churny files have NO team knowledge on them?".
|
|
2347
|
+
*
|
|
2348
|
+
* Hivelore's `eval` synthesizes cases from the memories that EXIST (does the corpus surface
|
|
2349
|
+
* correctly?). It cannot tell you what knowledge is MISSING. This module answers the inverse,
|
|
2350
|
+
* proactive question Fowler frames as an open challenge: of the files the team edits most, which
|
|
2351
|
+
* carry no covering decision/convention/gotcha/architecture memory? Those are the blind spots
|
|
2352
|
+
* where a confident agent is most likely to violate an unwritten rule.
|
|
2353
|
+
*
|
|
2354
|
+
* Pure: the caller supplies hot files (from git history / briefing-radar) and the loaded corpus.
|
|
2355
|
+
*/
|
|
2356
|
+
|
|
2357
|
+
/** Where a file's "heat" came from: committed git churn, agent edits this/recent sessions, or both. */
|
|
2358
|
+
type HotFileSource = "git" | "agent" | "both";
|
|
2359
|
+
interface HotFile {
|
|
2360
|
+
path: string;
|
|
2361
|
+
/** Number of times the file changed in the lookback window (the "heat"). */
|
|
2362
|
+
changes: number;
|
|
2363
|
+
/** Provenance of the heat. Optional for back-compat with git-only callers. */
|
|
2364
|
+
source?: HotFileSource;
|
|
2365
|
+
}
|
|
2366
|
+
interface CoverageGap {
|
|
2367
|
+
path: string;
|
|
2368
|
+
changes: number;
|
|
2369
|
+
/** Provenance of the heat that made this file a blind spot. */
|
|
2370
|
+
source?: HotFileSource;
|
|
2371
|
+
}
|
|
2372
|
+
interface CoverageOptions {
|
|
2373
|
+
/** Only flag files with at least this many changes. Default 3. */
|
|
2374
|
+
minChanges?: number;
|
|
2375
|
+
/** Memory types that count as "covering" a file. Default decision/convention/gotcha/architecture. */
|
|
2376
|
+
coveringTypes?: string[];
|
|
2377
|
+
/** Cap on returned gaps. Default 20. */
|
|
2378
|
+
limit?: number;
|
|
2379
|
+
}
|
|
2380
|
+
/**
|
|
2381
|
+
* Build the set of path prefixes the corpus covers: every anchor path of a non-dead,
|
|
2382
|
+
* non-recap covering memory. A file is covered if it equals, or sits under, one of them.
|
|
2383
|
+
*/
|
|
2384
|
+
declare function buildCoverageIndex(memories: LoadedMemory[], coveringTypes?: string[]): Set<string>;
|
|
2385
|
+
/** True when `file` equals or is nested under any covered path prefix. */
|
|
2386
|
+
declare function isCovered(file: string, coverage: Set<string>): boolean;
|
|
2387
|
+
/**
|
|
2388
|
+
* Cross hot files with the coverage index → the uncovered, frequently-edited files.
|
|
2389
|
+
* Highest heat first. These are the highest-value places to add a memory or sensor.
|
|
2390
|
+
*/
|
|
2391
|
+
declare function findCoverageGaps(hotFiles: HotFile[], memories: LoadedMemory[], options?: CoverageOptions): CoverageGap[];
|
|
2392
|
+
/**
|
|
2393
|
+
* Tally a flat list of edited file paths into HotFiles — the agent-edit heat signal from the
|
|
2394
|
+
* PostToolUse observation log (files agents actually touch), complementary to committed git churn.
|
|
2395
|
+
* Pure: the caller reads/normalizes the observation paths.
|
|
2396
|
+
*/
|
|
2397
|
+
declare function tallyHotFiles(paths: string[], source?: HotFileSource): HotFile[];
|
|
2398
|
+
/**
|
|
2399
|
+
* Merge two HotFile lists, summing heat per path. A file hot in both lists is tagged `both` so the
|
|
2400
|
+
* report can show that agents AND git churn both point at the same uncovered file (the strongest gap).
|
|
2401
|
+
*/
|
|
2402
|
+
declare function mergeHotFiles(a: HotFile[], b: HotFile[]): HotFile[];
|
|
2403
|
+
|
|
2404
|
+
interface EvalHistoryEntry {
|
|
2405
|
+
/** ISO timestamp of the eval run. */
|
|
2406
|
+
at: string;
|
|
2407
|
+
/** Overall 0..100 score. */
|
|
2408
|
+
score: number;
|
|
2409
|
+
/** Optional component metrics for richer trend views. */
|
|
2410
|
+
mean_recall?: number;
|
|
2411
|
+
mrr?: number;
|
|
2412
|
+
catch_rate?: number;
|
|
2413
|
+
/** Optional version/commit the run was taken at. */
|
|
2414
|
+
ref?: string;
|
|
2415
|
+
}
|
|
2416
|
+
declare function evalHistoryPath(paths: HaivePaths): string;
|
|
2417
|
+
/** Append one eval run to the history. Best-effort, creates the dir on demand. */
|
|
2418
|
+
declare function appendEvalHistory(paths: HaivePaths, entry: EvalHistoryEntry): Promise<void>;
|
|
2419
|
+
/** Read all eval runs (skips malformed lines). */
|
|
2420
|
+
declare function loadEvalHistory(paths: HaivePaths): Promise<EvalHistoryEntry[]>;
|
|
2421
|
+
interface EvalTrend {
|
|
2422
|
+
/** Most recent score, or null when there is no history. */
|
|
2423
|
+
latest: number | null;
|
|
2424
|
+
/** Score before the latest, or null. */
|
|
2425
|
+
previous: number | null;
|
|
2426
|
+
/** latest − previous (positive = improving). */
|
|
2427
|
+
delta: number | null;
|
|
2428
|
+
/** Best score ever recorded. */
|
|
2429
|
+
best: number | null;
|
|
2430
|
+
/** Number of runs recorded. */
|
|
2431
|
+
runs: number;
|
|
2432
|
+
/** Last N scores oldest → newest for a sparkline. */
|
|
2433
|
+
recent: number[];
|
|
2434
|
+
/** True when the latest run dropped vs the previous one. */
|
|
2435
|
+
regressed: boolean;
|
|
2436
|
+
}
|
|
2437
|
+
/** Pure trend over the history (chronological order is enforced internally). */
|
|
2438
|
+
declare function computeEvalTrend(entries: EvalHistoryEntry[], recentN?: number): EvalTrend;
|
|
2439
|
+
|
|
2440
|
+
/**
|
|
2441
|
+
* Contradiction resolution planning — turns "two memories conflict" into "do THIS".
|
|
2442
|
+
*
|
|
2443
|
+
* `conflict-candidates.ts` surfaces pairs (same topic with opposed status, or lexically near-
|
|
2444
|
+
* duplicate). That's detection, not resolution — and Fowler lists incoherence-at-scale (a harness
|
|
2445
|
+
* full of contradictory guides) as a core open challenge. This module decides, deterministically,
|
|
2446
|
+
* which memory of a pair should WIN and which should be superseded (deprecated), so the CLI can
|
|
2447
|
+
* apply it. Pure: no I/O, unit-tested.
|
|
2448
|
+
*
|
|
2449
|
+
* Decision order (strongest signal first):
|
|
2450
|
+
* 1. status — a `validated` memory beats a `rejected`/`deprecated`/`stale` one.
|
|
2451
|
+
* 2. revision — higher `revision_count` (more refined via topic-upsert) wins.
|
|
2452
|
+
* 3. recency — newer `created_at` wins (the team's latest word).
|
|
2453
|
+
*/
|
|
2454
|
+
|
|
2455
|
+
interface ConflictResolution {
|
|
2456
|
+
/** Memory id to keep authoritative. */
|
|
2457
|
+
keep_id: string;
|
|
2458
|
+
/** Memory id to deprecate (superseded). */
|
|
2459
|
+
supersede_id: string;
|
|
2460
|
+
/** Human-readable reason the winner was chosen. */
|
|
2461
|
+
reason: string;
|
|
2462
|
+
/** stale_reason to stamp on the superseded memory. */
|
|
2463
|
+
stale_reason: string;
|
|
2464
|
+
}
|
|
2465
|
+
/** Compare two memories; returns the one that should WIN plus the reason. Pure. */
|
|
2466
|
+
declare function planConflictResolution(a: LoadedMemory, b: LoadedMemory): ConflictResolution;
|
|
2467
|
+
interface AppliedConflictResolution {
|
|
2468
|
+
/** Updated frontmatter for the memory to keep (promoted). */
|
|
2469
|
+
winner: MemoryFrontmatter;
|
|
2470
|
+
/** Updated frontmatter for the memory to supersede (deprecated). */
|
|
2471
|
+
loser: MemoryFrontmatter;
|
|
2472
|
+
/** Topic the winner now carries — the consolidation target for future `mem_save` upserts. Null when neither carried one. */
|
|
2473
|
+
topic: string | null;
|
|
2474
|
+
/** True when the winner adopted the loser's topic because it had none. */
|
|
2475
|
+
topic_adopted: boolean;
|
|
2476
|
+
}
|
|
2477
|
+
/**
|
|
2478
|
+
* Turn a {@link ConflictResolution} plan into the two concrete frontmatter updates — the guided
|
|
2479
|
+
* supersede the backlog called for, wired into topic-upsert/revision_count:
|
|
2480
|
+
* - loser → deprecated, stamped with stale_reason + a related_ids link to the winner.
|
|
2481
|
+
* - winner → revision_count++ (it absorbed a contradiction), verified now, linked to the loser,
|
|
2482
|
+
* and it ADOPTS the loser's topic when it had none — so the next `mem_save` on this subject
|
|
2483
|
+
* upserts into the winner instead of spawning a third conflicting memory. An existing winner
|
|
2484
|
+
* topic is never overwritten. Pure: the caller persists both.
|
|
2485
|
+
*/
|
|
2486
|
+
declare function applyConflictResolution(winner: LoadedMemory, loser: LoadedMemory, plan: ConflictResolution, now?: Date): AppliedConflictResolution;
|
|
2487
|
+
|
|
2488
|
+
/**
|
|
2489
|
+
* Cold-start seeding from git history — the harness has value only once the corpus is populated,
|
|
2490
|
+
* and a fresh repo starts empty (Fowler's "harnessability": greenfield is easy, legacy is hard).
|
|
2491
|
+
*
|
|
2492
|
+
* Reverts and fixups are the cheapest signal of a real, repo-specific mistake: a commit that had to
|
|
2493
|
+
* be undone or hot-fixed encodes a lesson the team already paid for. This module parses a list of
|
|
2494
|
+
* commits (the CLI runs `git log`) and proposes DRAFT `attempt` seeds — never validated, always
|
|
2495
|
+
* human-reviewed. Pure: the caller does the git I/O and the memory writes.
|
|
2496
|
+
*/
|
|
2497
|
+
interface GitCommit {
|
|
2498
|
+
sha: string;
|
|
2499
|
+
subject: string;
|
|
2500
|
+
/** Files touched by the commit (optional — improves anchoring). */
|
|
2501
|
+
files?: string[];
|
|
2502
|
+
}
|
|
2503
|
+
interface SeedProposal {
|
|
2504
|
+
/** Kebab-ish slug derived from the reverted subject. */
|
|
2505
|
+
slug: string;
|
|
2506
|
+
/** What was tried (the thing that had to be reverted/fixed). */
|
|
2507
|
+
what: string;
|
|
2508
|
+
/** Why it failed (inferred from the revert/fixup). */
|
|
2509
|
+
why_failed: string;
|
|
2510
|
+
/** Suggested anchor paths (from the commit's files). */
|
|
2511
|
+
paths: string[];
|
|
2512
|
+
/** The source commit, for provenance. */
|
|
2513
|
+
source_sha: string;
|
|
2514
|
+
/** Detected signal kind. */
|
|
2515
|
+
kind: "revert" | "fixup" | "workaround";
|
|
2516
|
+
}
|
|
2517
|
+
/** True when a reverted/fixed subject is mechanical noise (merge/bump/deps/wip/format), not a lesson. */
|
|
2518
|
+
declare function isNoiseSubject(subject: string): boolean;
|
|
2519
|
+
/**
|
|
2520
|
+
* Turn commits into seed proposals. A `Revert "X"` commit proposes an attempt about X; an obvious
|
|
2521
|
+
* hotfix/fixup commit proposes an attempt about the fixed area. Deduped by slug. Pure.
|
|
2522
|
+
*/
|
|
2523
|
+
declare function proposeSeedsFromCommits(commits: GitCommit[], limit?: number): SeedProposal[];
|
|
2524
|
+
|
|
2525
|
+
/**
|
|
2526
|
+
* Pure stack-detection helpers for cold-start seeding.
|
|
2527
|
+
*
|
|
2528
|
+
* Multi-language: reads package.json deps (JS/TS), requirements.txt (Python),
|
|
2529
|
+
* go.mod (Go), and pom.xml (Java/Spring) to produce the list of detected stacks.
|
|
2530
|
+
* No I/O — the caller reads the files and passes contents in, making this fully testable.
|
|
2531
|
+
*/
|
|
2532
|
+
interface DetectStacksInput {
|
|
2533
|
+
/** Merged deps from package.json (dependencies + devDependencies). */
|
|
2534
|
+
packageJsonDeps?: Record<string, string>;
|
|
2535
|
+
/** Raw text of requirements.txt (or any requirements file). */
|
|
2536
|
+
requirementsTxt?: string;
|
|
2537
|
+
/** Raw text of go.mod. */
|
|
2538
|
+
goMod?: string;
|
|
2539
|
+
/** Raw text of pom.xml. */
|
|
2540
|
+
pomXml?: string;
|
|
2541
|
+
/** Raw text of composer.json (PHP). */
|
|
2542
|
+
composerJson?: string;
|
|
2543
|
+
/** Raw text of Gemfile (Ruby). */
|
|
2544
|
+
gemfile?: string;
|
|
2545
|
+
/** True when at least one .csproj/.sln file is present (.NET). */
|
|
2546
|
+
hasCsproj?: boolean;
|
|
2547
|
+
/** True when a Dockerfile is present. */
|
|
2548
|
+
hasDockerfile?: boolean;
|
|
2549
|
+
/** True when turbo.json is present (Turborepo). */
|
|
2550
|
+
hasTurboJson?: boolean;
|
|
2551
|
+
/** True when nx.json is present (Nx). */
|
|
2552
|
+
hasNxJson?: boolean;
|
|
2553
|
+
}
|
|
2554
|
+
type DetectableStack = "nestjs" | "nextjs" | "remix" | "react" | "express" | "fastify" | "prisma" | "drizzle" | "zustand" | "redux" | "reactquery" | "trpc" | "mongoose" | "graphql" | "vue" | "tailwind" | "vite" | "sveltekit" | "astro" | "typescript" | "monorepo" | "fastapi" | "django" | "flask" | "go" | "spring" | "laravel" | "rails" | "dotnet" | "docker";
|
|
2555
|
+
/**
|
|
2556
|
+
* Detect stacks present in a project from the raw contents of its manifest files.
|
|
2557
|
+
* Pure — no I/O. Pass what you have; omit what you don't.
|
|
2558
|
+
*/
|
|
2559
|
+
declare function detectStacksFromManifests(input: DetectStacksInput): DetectableStack[];
|
|
2560
|
+
|
|
2561
|
+
interface MergeResult {
|
|
2562
|
+
/** The chosen file content. */
|
|
2563
|
+
content: string;
|
|
2564
|
+
/** Which side won. */
|
|
2565
|
+
winner: "ours" | "theirs";
|
|
2566
|
+
/** Why (for logging). */
|
|
2567
|
+
reason: string;
|
|
2568
|
+
}
|
|
2569
|
+
/**
|
|
2570
|
+
* Resolve two versions of the same memory file. Returns the winning content and the rationale.
|
|
2571
|
+
* Falls back to "ours" when either side can't be parsed (never throws — a merge driver must not).
|
|
2572
|
+
*/
|
|
2573
|
+
declare function mergeMemoryVersions(ours: string, theirs: string): MergeResult;
|
|
2574
|
+
|
|
2575
|
+
/**
|
|
2576
|
+
* Recap compaction — keep the auto-generated session recap from dominating the briefing head.
|
|
2577
|
+
*
|
|
2578
|
+
* The MCP server auto-saves a minimal recap on exit (goal = "Auto-captured session (N tool calls)",
|
|
2579
|
+
* body = a raw tool-call/file dump). It's low signal, yet get_briefing shows the freshest recap's
|
|
2580
|
+
* full body at the very top of every briefing. A human/post_task recap (with a real Discoveries
|
|
2581
|
+
* section) is far richer. This module detects an auto recap and compresses it to its useful core
|
|
2582
|
+
* (the Discoveries, if any) so it informs without crowding. Pure, unit-tested.
|
|
2583
|
+
*/
|
|
2584
|
+
/**
|
|
2585
|
+
* True when a recap body looks auto-generated (vs. a human/post_task recap). Auto recaps come in a
|
|
2586
|
+
* couple of shapes, all low-signal: the session-tracker's "Auto-captured session (N tool calls)" and
|
|
2587
|
+
* the run-wrapper's "Edited N files across M tool calls". The common tell is a raw tool-call count.
|
|
2588
|
+
*/
|
|
2589
|
+
declare function isAutoRecap(body: string): boolean;
|
|
2590
|
+
/**
|
|
2591
|
+
* Return a compact version of an auto recap body: a one-line header (the Goal line) plus the
|
|
2592
|
+
* Discoveries section when it carries real content (e.g. detected failures). Non-auto recaps are
|
|
2593
|
+
* returned unchanged.
|
|
2594
|
+
*/
|
|
2595
|
+
declare function compactAutoRecapBody(body: string, maxChars?: number): string;
|
|
2596
|
+
|
|
2597
|
+
/** Filename of the ephemeral handoff at the repo root. */
|
|
2598
|
+
declare const HANDOFF_FILENAME = "NEXT.md";
|
|
2599
|
+
/** Absolute path to the handoff file for a given project root. */
|
|
2600
|
+
declare function handoffFilePath(root: string): string;
|
|
2601
|
+
interface SessionHandoffData {
|
|
2602
|
+
/** One-line focus of the session that just ended. */
|
|
2603
|
+
goal: string;
|
|
2604
|
+
/** Short work summary (e.g. tool/file rollup) — optional. */
|
|
2605
|
+
summary?: string;
|
|
2606
|
+
/** Unresolved items the next session should pick up (failures, attempts, TODOs). */
|
|
2607
|
+
openThreads?: string[];
|
|
2608
|
+
/** Files touched during the session. */
|
|
2609
|
+
filesTouched?: string[];
|
|
2610
|
+
/** What should happen next, free text. */
|
|
2611
|
+
nextSteps?: string;
|
|
2612
|
+
/** Optional `git diff --stat` style block to show what is uncommitted. */
|
|
2613
|
+
diffStat?: string;
|
|
2614
|
+
/** Timestamp; defaults to now. */
|
|
2615
|
+
at?: Date;
|
|
2616
|
+
}
|
|
2617
|
+
/**
|
|
2618
|
+
* Build the markdown body of the ephemeral handoff. Pure — no I/O.
|
|
2619
|
+
* Deliberately compact: focus + open threads + next steps are the load-bearing parts.
|
|
2620
|
+
*/
|
|
2621
|
+
declare function buildHandoffMarkdown(data: SessionHandoffData): string;
|
|
2622
|
+
/** Write (overwrite) the ephemeral handoff. Returns the file path. Best-effort caller should catch. */
|
|
2623
|
+
declare function writeSessionHandoff(root: string, data: SessionHandoffData): Promise<string>;
|
|
2624
|
+
/** Read the handoff body if present, else null. */
|
|
2625
|
+
declare function readSessionHandoff(root: string): Promise<string | null>;
|
|
2626
|
+
/** Age of the handoff file in milliseconds (by mtime), or null if it does not exist. */
|
|
2627
|
+
declare function handoffAgeMs(root: string, now?: Date): Promise<number | null>;
|
|
2628
|
+
|
|
2629
|
+
type MemoryPriority = "must_read" | "useful" | "background";
|
|
2630
|
+
/**
|
|
2631
|
+
* Normalized priority evidence. A caller fills only the signals it can compute; unknown ones default
|
|
2632
|
+
* to false (see {@link DEFAULT_PRIORITY_SIGNALS}). The MCP path has semantic scores; the CLI path has
|
|
2633
|
+
* lexical scores — both reduce to these booleans.
|
|
2634
|
+
*/
|
|
2635
|
+
interface PrioritySignals {
|
|
2636
|
+
/** Memory type (attempt, gotcha, skill, decision, …). */
|
|
2637
|
+
type: string;
|
|
2638
|
+
/** Memory tags — used for the stack-pack / env-workaround down-rank. */
|
|
2639
|
+
tags: string[];
|
|
2640
|
+
/** The memory demands explicit human approval — always surface first. */
|
|
2641
|
+
requiresHumanApproval: boolean;
|
|
2642
|
+
/** Anchored to a file the agent is editing. */
|
|
2643
|
+
directAnchor: boolean;
|
|
2644
|
+
/** Anchored to a symbol the agent requested. */
|
|
2645
|
+
directSymbol: boolean;
|
|
2646
|
+
/** Exact/literal task match (semantic match_quality "exact", or an exact lexical task hit). */
|
|
2647
|
+
exactTaskMatch: boolean;
|
|
2648
|
+
/** Strong semantic relevance (cosine ≥ 0.65). CLI has no embeddings → passes false. */
|
|
2649
|
+
strongSemantic: boolean;
|
|
2650
|
+
/** Useful-level relevance: semantic ≥ 0.35, a partial task hit, or a high lexical score. */
|
|
2651
|
+
usefulSemantic: boolean;
|
|
2652
|
+
/** Matched an inferred module or domain from the touched files. */
|
|
2653
|
+
moduleOrDomainMatch: boolean;
|
|
2654
|
+
/** A memory tag matched a task token. */
|
|
2655
|
+
tagTaskMatch: boolean;
|
|
2656
|
+
}
|
|
2657
|
+
declare const DEFAULT_PRIORITY_SIGNALS: PrioritySignals;
|
|
2658
|
+
/** Convenience: build a full signal set from a partial one. */
|
|
2659
|
+
declare function prioritySignals(partial: Partial<PrioritySignals>): PrioritySignals;
|
|
2660
|
+
/**
|
|
2661
|
+
* Classify a memory's briefing priority from its signals. Order matters:
|
|
2662
|
+
* 1. must_read — human-approval gates, direct anchor/symbol matches, and exact/strong hits on
|
|
2663
|
+
* negative (attempt) or skill memories: the things an agent must not miss.
|
|
2664
|
+
* 2. background (down-rank) — generic stack-pack seeds and local dev-environment workarounds never
|
|
2665
|
+
* claim `useful` on a semantic/tag match alone; they'd crowd out repo-specific knowledge. (A
|
|
2666
|
+
* direct anchor already promoted them to must_read above, so genuinely-relevant ones still rank.)
|
|
2667
|
+
* 3. useful — skills, module/domain matches, exact hits, and useful-level relevance.
|
|
2668
|
+
* 4. background — everything else.
|
|
2669
|
+
*/
|
|
2670
|
+
declare function classifyMemoryPriority(signals: PrioritySignals): MemoryPriority;
|
|
2671
|
+
declare function priorityRank(priority: MemoryPriority): number;
|
|
2672
|
+
|
|
2673
|
+
/**
|
|
2674
|
+
* Native bridge generator — produces agent-harness-specific config files
|
|
2675
|
+
* from the Hivelore corpus (validated memories + block sensors).
|
|
2676
|
+
*
|
|
2677
|
+
* One pure formatter per target; no I/O.
|
|
2678
|
+
* The CLI command (cli/commands/bridges.ts) handles file writes and
|
|
2679
|
+
* idempotent marker-based updates.
|
|
2680
|
+
*
|
|
2681
|
+
* Exposed for Lot A (init.ts): call generateBridges() from hivelore init
|
|
2682
|
+
* to seed all bridges at initialisation time.
|
|
2683
|
+
*/
|
|
2684
|
+
|
|
2685
|
+
type BridgeTarget = "claude" | "cursor" | "cline" | "windsurf" | "continue" | "cody" | "zed" | "roo" | "gemini" | "aider" | "agents" | "copilot";
|
|
2686
|
+
/** Canonical relative path from project root for each target. */
|
|
2687
|
+
declare const BRIDGE_TARGET_PATH: Record<BridgeTarget, string>;
|
|
2688
|
+
declare const BRIDGE_TARGETS: BridgeTarget[];
|
|
2689
|
+
/**
|
|
2690
|
+
* Condensed sensor shape for bridge injection.
|
|
2691
|
+
* Callers extract this from Memory.frontmatter.sensor — no sensor-module import needed.
|
|
2692
|
+
*/
|
|
2693
|
+
interface BridgeSensor {
|
|
2694
|
+
id: string;
|
|
2695
|
+
severity: "block" | "warn";
|
|
2696
|
+
message: string;
|
|
2697
|
+
/** Regex pattern, present when sensor.kind === "regex". */
|
|
2698
|
+
pattern?: string;
|
|
2699
|
+
/** Scoped file paths (sensor.paths ?? anchor.paths). */
|
|
2700
|
+
paths: string[];
|
|
2701
|
+
}
|
|
2702
|
+
interface BridgeMemoryEntry {
|
|
2703
|
+
id: string;
|
|
2704
|
+
scope: string;
|
|
2705
|
+
type: string;
|
|
2706
|
+
summary: string;
|
|
2707
|
+
/** Anchor paths the memory applies to (for path-scoped display / Cursor globs). */
|
|
2708
|
+
paths: string[];
|
|
2709
|
+
}
|
|
2710
|
+
interface GenerateBridgesOptions {
|
|
2711
|
+
/** Max memories to inject per bridge (default: 8). */
|
|
2712
|
+
maxMemories?: number;
|
|
2713
|
+
/** Restrict generation to these targets. Defaults to all BRIDGE_TARGETS. */
|
|
2714
|
+
targets?: BridgeTarget[];
|
|
2715
|
+
}
|
|
2716
|
+
interface BridgeFileOutput {
|
|
2717
|
+
target: BridgeTarget;
|
|
2718
|
+
/** Relative path from project root. */
|
|
2719
|
+
path: string;
|
|
2720
|
+
content: string;
|
|
2721
|
+
}
|
|
2722
|
+
declare const BRIDGE_MARKERS: {
|
|
2723
|
+
readonly bridgeStart: "<!-- haive:bridge-start -->";
|
|
2724
|
+
readonly bridgeEnd: "<!-- haive:bridge-end -->";
|
|
2725
|
+
readonly memoriesStart: "<!-- haive:memories-start -->";
|
|
2726
|
+
readonly memoriesEnd: "<!-- haive:memories-end -->";
|
|
2727
|
+
readonly sensorsStart: "<!-- haive:sensors-start -->";
|
|
2728
|
+
readonly sensorsEnd: "<!-- haive:sensors-end -->";
|
|
2729
|
+
};
|
|
2730
|
+
/** First meaningful line of a memory body, condensed for bridge display. */
|
|
2731
|
+
declare function bridgeMemorySummary(body: string): string;
|
|
2732
|
+
/**
|
|
2733
|
+
* Filter and rank memories + sensors for bridge injection.
|
|
2734
|
+
* Pure — callers load data; this function does not read files.
|
|
2735
|
+
*/
|
|
2736
|
+
declare function prepareBridgeData(memories: Memory[], sensors: BridgeSensor[], opts?: Pick<GenerateBridgesOptions, "maxMemories">): {
|
|
2737
|
+
topMemories: BridgeMemoryEntry[];
|
|
2738
|
+
blockSensors: BridgeSensor[];
|
|
2739
|
+
};
|
|
2740
|
+
/**
|
|
2741
|
+
* Generate bridge file content for the requested targets.
|
|
2742
|
+
*
|
|
2743
|
+
* Pure: accepts loaded memories + sensors, returns file content strings.
|
|
2744
|
+
* The CLI command handles I/O and idempotent marker-based updates.
|
|
2745
|
+
*
|
|
2746
|
+
* **Lot A integration point**: call this from `hivelore init` to seed bridges at initialisation.
|
|
2747
|
+
* Signature is intentionally stable — init.ts should call:
|
|
2748
|
+
* `generateBridges(memories, sensors, { targets: BRIDGE_TARGETS })`
|
|
2749
|
+
*/
|
|
2750
|
+
declare function generateBridges(memories: Memory[], sensors: BridgeSensor[], opts?: GenerateBridgesOptions): BridgeFileOutput[];
|
|
2751
|
+
|
|
2752
|
+
export { AUTOPILOT_DEFAULTS, type Activation, type ActivationContext, ActivationSchema, type Anchor, AnchorSchema, type AntiPatternGate, type AppliedConflictResolution, type AstExport, type AutoPromoteRule, BRIDGE_MARKERS, BRIDGE_TARGETS, BRIDGE_TARGET_PATH, BRIEFING_MARKER_TTL_MS, BRIEFING_PRESET_DEFAULTS, type BootstrapAssessment, type BootstrapGap, type BootstrapGate, type BootstrapMetrics, type BootstrapState, type BootstrapStateInput, type BreakingChange, type BridgeFileOutput, type BridgeMemoryEntry, type BridgeSensor, type BridgeTarget, type BriefingBudgetNumbers, type BriefingBudgetPreset, type BriefingMarker, type BriefingProofLineOptions, type BudgetPart, type BudgetSlice, type BuildCodeMapOptions, CHARS_PER_TOKEN, CODE_MAP_FILE, CODE_STOPWORDS, CONFIG_FILE, type CaughtForYouOptions, type CaughtForYouRow, type CaughtForYouSummary, type CodeExport, type CodeExportKind, type CodeFileEntry, type CodeMap, type CodeMapQueryOptions, type CollectTimelineOpts, type CommandSensorSpec, type ConfidenceLevel, type ConfidenceThresholds, type ConflictCandidatePair, type ConflictCandidatesOpts, type ConflictResolution, type ContractDiffResult, type ContractFile, type ContractSnapshot, type CoverageGap, type CoverageOptions, CrossRepoProvenanceSchema, type CrossRepoReport, type CrossRepoSource, DECAY_DAYS, DEFAULT_AUTO_PROMOTE_RULE, DEFAULT_BRIEFING_EXCLUDE_TAGS, DEFAULT_CONFIDENCE_THRESHOLDS, DEFAULT_CONFIG, DEFAULT_DORMANT_DAYS, DEFAULT_PRIORITY_SIGNALS, type DashboardOptions, type DashboardReport, type DepChange, type DepTrackResult, type DependencySnapshot, type DetectStacksInput, type DetectableStack, type DocFrequency, type DormantRow, type DraftOptions, type DraftsOptions, ENV_WORKAROUND_TAGS, type EvalDelta, type EvalHistoryEntry, type EvalReport, type EvalSpec, type EvalTrend, type FailureCoverageOptions, type FailureObservation, type FeedbackAdjustment, type FeedbackAdjustmentAction, type FeedbackAdjustmentOptions, type Finding, type FindingFormat, type FindingSeverity, GUESSABLE_THRESHOLD, type GatePrecision, type GatePrecisionDelta, type GatePrecisionMetricDelta, type GateTuningSuggestion, type GenerateBridgesOptions, type GitCommit, HAIVE_DIR, HAIVE_OWNED_FILES, HANDOFF_FILENAME, type HaiveConfig, type HaivePaths, type HotFile, type HotFileSource, type ImpactOptions, type ImpactRow, type ImpactScore, type ImpactSummary, type ImpactTier, type LexicalRankResult, type LoadedMemory, MEMORIES_DIR, MIN_WORD_LEN, type Memory, type MemoryDraft, type MemoryFrontmatter, MemoryFrontmatterSchema, type MemoryPriority, type MemoryScope, MemoryScopeSchema, type MemoryStatus, MemoryStatusSchema, type MemoryType, MemoryTypeSchema, type MemoryUsage, type MergeResult, type MetricDelta, PREVENTION_DEBOUNCE_MS, PROJECT_CONTEXT_FILE, PROJECT_CONTEXT_THROTTLE_MS, type PreventionEvent, type PreventionRow, type PreventionSource, type PreventionTrend, type PrioritySignals, type ProposedSensorVerdict, RUNTIME_JOURNAL_FILENAME, type RecurrenceReport, type RecurrenceRow, type ResolveProjectInfo, type RetirementSignal, type RetrievalAggregate, type RetrievalCase, type RetrievalCaseResult, type RuntimeJournalEntry, SEED_QUALITY_FLOOR, SENSOR_ABSENT_LOOKBACK, SENSOR_ABSENT_WINDOW, SESSION_RECAP_TTL_MS, STACK_PACK_TAG, type SeedProposal, type SelfEvalOptions, type Sensor, type SensorAggregate, type SensorCase, type SensorCaseResult, type SensorHit, type SensorRow, SensorSchema, type SensorSeed, type SensorSelfCheck, type SensorSuggestionOptions, type SensorTarget, type SessionHandoffData, type SkillActivation, type TimelineEntry, type TopicStatusPair, type TruncateOptions, type TruncateResult, USAGE_FILE, USAGE_LOG_DIR, USAGE_LOG_FILE, type UncapturedFailure, type UsageAggregate, type UsageEvent, type UsageIndex, type VerifyOptions, type VerifyResult, addedLinesFromDiff, aggregateRetrieval, aggregateSensors, aggregateUsage, allocateBudget, antiPatternGateParams, appendEvalHistory, appendPreventionEvent, appendRuntimeJournalEntry, appendUsageEvent, applyConflictResolution, applyFeedbackAdjustment, assessBootstrapState, bridgeMemorySummary, briefingMarkerPath, briefingMarkersDir, briefingProofLine, buildCodeMap, buildCoverageIndex, buildDashboard, buildDocFrequency, buildFrontmatter, buildHandoffMarkdown, buildReport, bumpRead, classifyMemoryPriority, codeMapPath, collectTimelineEntries, compactAutoRecapBody, compareEvalReports, compareGatePrecision, compareImpact, compileRegexSensor, componentOf, computeEvalTrend, computeGatePrecision, computeImpact, computePreventionTrend, computeRecurrence, configPath, contractLockPath, countSourceFilesOnDisk, deriveConfidence, detectStacksFromManifests, diffContract, diffHasDistinctiveOverlap, distinctiveCap, draftsFromFindings, emptyUsage, emptyUsageIndex, enforcementDir, estimateTokens, evalHistoryPath, evaluateSkillActivation, extractActionsBriefBody, extractReferencedPaths, extractSensorExamples, extractSnippet, filterNewDrafts, findCoverageGaps, findLexicalConflictPairs, findProjectRoot, findTopicStatusConflictPairs, findUncapturedFailures, findingBody, findingToDraft, firstMemoryOneLine, generateBridges, getUsage, globToRegExp, handoffAgeMs, handoffFilePath, hasRecentBriefingMarker, hashProjectContext, inferModulesFromPaths, isAutoPromoteEligible, isAutoRecap, isCovered, isDecaying, isDistinctiveToken, isEnvWorkaroundMemory, isFreshIsoDate, isGlobPath, isLikelyGuessable, isNoiseSubject, isRetiredMemory, isSensorScannablePath, isSkill, isSkillSuppressed, isStackPackSeed, isStylisticRule, isTemplateProjectContext, judgeProposedSensor, listMarkdownFilesRecursive, literalMatchesAllTokens, literalMatchesAnyToken, loadCodeMap, loadConfig, loadConfigSync, loadEvalHistory, loadMemoriesFromDir, loadMemory, loadPreventionEvents, loadUsageIndex, looksLikeGenericAdvice, meetsSeedQualityFloor, memoryFilePath, memoryHasExcludedTag, memoryMatchesAnchorPaths, mergeHotFiles, mergeMemoryVersions, moduleNameOf, newMemoryId, normalizeFindingSeverity, normalizeSessionId, overallScore, parseEslintJson, parseFileAst, parseFindings, parseMemory, parseNpmAudit, parseSarif, parseSince, parseSonar, pathsOverlap, pickSnippetNeedle, planConflictResolution, prepareBridgeData, preventionLogPath, priorityRank, prioritySignals, projectContextRecentlyEmitted, proposeSeedsFromCommits, pullCrossRepoSources, queryCodeMap, rankMemoriesLexical, readRecentBriefingMarker, readRuntimeJournalTail, readSessionHandoff, readUsageEvents, recommendFeedbackAdjustment, recordApplied, recordPrevention, recordPreventionHits, recordProjectContextEmission, recordRejection, relPathFrom, renderBootstrapChecklist, renderCaughtForYou, resolveBriefingBudget, resolveHaivePaths, resolveManifestFiles, resolveProjectInfo, retirementSignal, runRegexSensor, runSensors, runtimeJournalPath, saveCodeMap, saveConfig, saveUsageIndex, scannableSensorTargets, scoreRetrievalCase, scoreSensorCase, selectCommandSensors, sensorAppliesToPath, sensorPatternBrittleness, sensorSelfCheck, sensorTargetsFromDiff, serializeMemory, snapshotContract, specificityScore, stripPrivate, suggestGate, suggestSensorFromMemory, suggestSensorSeed, suggestTopicKey, summarizeCaughtForYou, summarizeImpact, synthesizeSelfEvalCases, tallyHotFiles, titleFromBody, tokenizeQuery, tokenizeWords, trackDependencies, trackReads, truncateToTokens, usageLogPath, usageLogSize, usagePath, verifyAnchor, watchContracts, writeBriefingMarker, writeSessionHandoff };
|