@kernloop/faculty-memory 0.1.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/dist/episodic.d.ts +34 -0
- package/dist/episodic.d.ts.map +1 -0
- package/dist/episodic.js +59 -0
- package/dist/episodic.js.map +1 -0
- package/dist/errors.d.ts +29 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +38 -0
- package/dist/errors.js.map +1 -0
- package/dist/export.d.ts +78 -0
- package/dist/export.d.ts.map +1 -0
- package/dist/export.js +98 -0
- package/dist/export.js.map +1 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.d.ts +15 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +37 -0
- package/dist/manifest.js.map +1 -0
- package/dist/semantic.d.ts +54 -0
- package/dist/semantic.d.ts.map +1 -0
- package/dist/semantic.js +92 -0
- package/dist/semantic.js.map +1 -0
- package/dist/store.d.ts +27 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +50 -0
- package/dist/store.js.map +1 -0
- package/package.json +40 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 William Zujkowski and Kernloop contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Episodic store (spec §5.2): replayable traces as compressed summary +
|
|
3
|
+
* pointer to the full trace. Write policy — auto on Outcome, summarized at
|
|
4
|
+
* write time (spec §8 item 5: digests, not transcripts; the transcript lives
|
|
5
|
+
* behind `traceRef`). Read policy — summaries, newest first.
|
|
6
|
+
*/
|
|
7
|
+
import type Database from 'better-sqlite3';
|
|
8
|
+
import { type Outcome, type OutcomeStatus } from '@kernloop/contracts';
|
|
9
|
+
/** A stored episodic trace summary. `createdAt` is epoch milliseconds. */
|
|
10
|
+
export interface TraceSummary {
|
|
11
|
+
taskId: string;
|
|
12
|
+
summary: string;
|
|
13
|
+
traceRef: string;
|
|
14
|
+
status: OutcomeStatus;
|
|
15
|
+
distillCandidates: string[];
|
|
16
|
+
createdAt: number;
|
|
17
|
+
}
|
|
18
|
+
/** Default maximum number of summaries a listing returns. */
|
|
19
|
+
export declare const DEFAULT_LIST_LIMIT = 20;
|
|
20
|
+
/**
|
|
21
|
+
* Record an Outcome as a trace summary (CLM-0024). The Outcome is
|
|
22
|
+
* zod-validated at the boundary; invalid values throw
|
|
23
|
+
* {@link InvalidOutcomeError}. Stored: the write-time `summary`, the
|
|
24
|
+
* `traceRef` pointer, status, and distill candidates — never the full
|
|
25
|
+
* transcript. One row per task; re-recording a task's Outcome replaces it.
|
|
26
|
+
*/
|
|
27
|
+
export declare function recordOutcome(db: Database.Database, now: number, outcome: Outcome, summary: string): TraceSummary;
|
|
28
|
+
/** Fetch one task's trace summary by id, or `undefined` when absent. */
|
|
29
|
+
export declare function getTraceSummary(db: Database.Database, taskId: string): TraceSummary | undefined;
|
|
30
|
+
/** List trace summaries newest-first (CLM-0024); ties break on later insert. */
|
|
31
|
+
export declare function listSummaries(db: Database.Database, options?: {
|
|
32
|
+
limit?: number;
|
|
33
|
+
}): TraceSummary[];
|
|
34
|
+
//# sourceMappingURL=episodic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"episodic.d.ts","sourceRoot":"","sources":["../src/episodic.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C,OAAO,EAAiB,KAAK,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGtF,0EAA0E;AAC1E,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,aAAa,CAAC;IACtB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,6DAA6D;AAC7D,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAsBrC;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,GACd,YAAY,CAsBd;AAED,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAQ/F;AAED,gFAAgF;AAChF,wBAAgB,aAAa,CAC3B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,YAAY,EAAE,CAShB"}
|
package/dist/episodic.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { OutcomeSchema } from '@kernloop/contracts';
|
|
3
|
+
import { InvalidOutcomeError } from './errors.js';
|
|
4
|
+
/** Default maximum number of summaries a listing returns. */
|
|
5
|
+
export const DEFAULT_LIST_LIMIT = 20;
|
|
6
|
+
function toSummary(row) {
|
|
7
|
+
return {
|
|
8
|
+
taskId: row.taskId,
|
|
9
|
+
summary: row.summary,
|
|
10
|
+
traceRef: row.traceRef,
|
|
11
|
+
status: row.status,
|
|
12
|
+
distillCandidates: JSON.parse(row.distillCandidates),
|
|
13
|
+
createdAt: row.createdAt,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Record an Outcome as a trace summary (CLM-0024). The Outcome is
|
|
18
|
+
* zod-validated at the boundary; invalid values throw
|
|
19
|
+
* {@link InvalidOutcomeError}. Stored: the write-time `summary`, the
|
|
20
|
+
* `traceRef` pointer, status, and distill candidates — never the full
|
|
21
|
+
* transcript. One row per task; re-recording a task's Outcome replaces it.
|
|
22
|
+
*/
|
|
23
|
+
export function recordOutcome(db, now, outcome, summary) {
|
|
24
|
+
const parsed = OutcomeSchema.safeParse(outcome);
|
|
25
|
+
if (!parsed.success) {
|
|
26
|
+
throw new InvalidOutcomeError(`episodic memory write rejected: ${z.prettifyError(parsed.error)}`);
|
|
27
|
+
}
|
|
28
|
+
const { taskId, traceRef, status, distillCandidates } = parsed.data;
|
|
29
|
+
const row = db
|
|
30
|
+
.prepare(`INSERT INTO traces (taskId, summary, traceRef, status, distillCandidates, createdAt)
|
|
31
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
32
|
+
ON CONFLICT(taskId) DO UPDATE SET
|
|
33
|
+
summary = excluded.summary,
|
|
34
|
+
traceRef = excluded.traceRef,
|
|
35
|
+
status = excluded.status,
|
|
36
|
+
distillCandidates = excluded.distillCandidates,
|
|
37
|
+
createdAt = excluded.createdAt
|
|
38
|
+
RETURNING taskId, summary, traceRef, status, distillCandidates, createdAt`)
|
|
39
|
+
.get(taskId, summary, traceRef, status, JSON.stringify(distillCandidates), now);
|
|
40
|
+
return toSummary(row);
|
|
41
|
+
}
|
|
42
|
+
/** Fetch one task's trace summary by id, or `undefined` when absent. */
|
|
43
|
+
export function getTraceSummary(db, taskId) {
|
|
44
|
+
const row = db
|
|
45
|
+
.prepare(`SELECT taskId, summary, traceRef, status, distillCandidates, createdAt
|
|
46
|
+
FROM traces WHERE taskId = ?`)
|
|
47
|
+
.get(taskId);
|
|
48
|
+
return row === undefined ? undefined : toSummary(row);
|
|
49
|
+
}
|
|
50
|
+
/** List trace summaries newest-first (CLM-0024); ties break on later insert. */
|
|
51
|
+
export function listSummaries(db, options = {}) {
|
|
52
|
+
const limit = options.limit ?? DEFAULT_LIST_LIMIT;
|
|
53
|
+
const rows = db
|
|
54
|
+
.prepare(`SELECT taskId, summary, traceRef, status, distillCandidates, createdAt
|
|
55
|
+
FROM traces ORDER BY createdAt DESC, rowid DESC LIMIT ?`)
|
|
56
|
+
.all(limit);
|
|
57
|
+
return rows.map(toSummary);
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=episodic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"episodic.js","sourceRoot":"","sources":["../src/episodic.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,aAAa,EAAoC,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAYlD,6DAA6D;AAC7D,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAWrC,SAAS,SAAS,CAAC,GAAa;IAC9B,OAAO;QACL,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,MAAM,EAAE,GAAG,CAAC,MAAuB;QACnC,iBAAiB,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAa;QAChE,SAAS,EAAE,GAAG,CAAC,SAAS;KACzB,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAC3B,EAAqB,EACrB,GAAW,EACX,OAAgB,EAChB,OAAe;IAEf,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAChD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,mBAAmB,CAC3B,mCAAmC,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACnE,CAAC;IACJ,CAAC;IACD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC;IACpE,MAAM,GAAG,GAAG,EAAE;SACX,OAAO,CACN;;;;;;;;iFAQ2E,CAC5E;SACA,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,EAAE,GAAG,CAAC,CAAC;IAClF,OAAO,SAAS,CAAC,GAAe,CAAC,CAAC;AACpC,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,eAAe,CAAC,EAAqB,EAAE,MAAc;IACnE,MAAM,GAAG,GAAG,EAAE;SACX,OAAO,CACN;oCAC8B,CAC/B;SACA,GAAG,CAAC,MAAM,CAAyB,CAAC;IACvC,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;AACxD,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,aAAa,CAC3B,EAAqB,EACrB,UAA8B,EAAE;IAEhC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,kBAAkB,CAAC;IAClD,MAAM,IAAI,GAAG,EAAE;SACZ,OAAO,CACN;+DACyD,CAC1D;SACA,GAAG,CAAC,KAAK,CAAe,CAAC;IAC5B,OAAO,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AAC7B,CAAC"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed errors thrown at the faculty's write boundaries. Callers (the kernel,
|
|
3
|
+
* at the bus boundary) can discriminate on `name` or `instanceof` — never on
|
|
4
|
+
* message text.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Thrown when a semantic memory write arrives without provenance (spec §5.2:
|
|
8
|
+
* "provenance mandatory"; CLM-0022). Missing, empty, and whitespace-only
|
|
9
|
+
* provenance all reject.
|
|
10
|
+
*/
|
|
11
|
+
export declare class ProvenanceRequiredError extends Error {
|
|
12
|
+
constructor(message?: string);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Thrown when a semantic memory write is malformed for a reason other than
|
|
16
|
+
* provenance (empty fact text, out-of-range confidence, wrong types).
|
|
17
|
+
*/
|
|
18
|
+
export declare class InvalidFactError extends Error {
|
|
19
|
+
constructor(message: string);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Thrown when `recordOutcome` receives a value that fails `OutcomeSchema`
|
|
23
|
+
* validation at the boundary (charter: zod-validate at every contract
|
|
24
|
+
* boundary; CLM-0024).
|
|
25
|
+
*/
|
|
26
|
+
export declare class InvalidOutcomeError extends Error {
|
|
27
|
+
constructor(message: string);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;GAIG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,OAAO,SAAwE;CAI5F;AAED;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed errors thrown at the faculty's write boundaries. Callers (the kernel,
|
|
3
|
+
* at the bus boundary) can discriminate on `name` or `instanceof` — never on
|
|
4
|
+
* message text.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Thrown when a semantic memory write arrives without provenance (spec §5.2:
|
|
8
|
+
* "provenance mandatory"; CLM-0022). Missing, empty, and whitespace-only
|
|
9
|
+
* provenance all reject.
|
|
10
|
+
*/
|
|
11
|
+
export class ProvenanceRequiredError extends Error {
|
|
12
|
+
constructor(message = 'semantic memory write rejected: provenance is mandatory (spec §5.2)') {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = 'ProvenanceRequiredError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Thrown when a semantic memory write is malformed for a reason other than
|
|
19
|
+
* provenance (empty fact text, out-of-range confidence, wrong types).
|
|
20
|
+
*/
|
|
21
|
+
export class InvalidFactError extends Error {
|
|
22
|
+
constructor(message) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = 'InvalidFactError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Thrown when `recordOutcome` receives a value that fails `OutcomeSchema`
|
|
29
|
+
* validation at the boundary (charter: zod-validate at every contract
|
|
30
|
+
* boundary; CLM-0024).
|
|
31
|
+
*/
|
|
32
|
+
export class InvalidOutcomeError extends Error {
|
|
33
|
+
constructor(message) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = 'InvalidOutcomeError';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;GAIG;AACH,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IAChD,YAAY,OAAO,GAAG,qEAAqE;QACzF,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IACzC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAC5C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF"}
|
package/dist/export.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portable memory export/import (spec §7: "intuition travels with git clone";
|
|
3
|
+
* spec §12.4: the database is gitignored for privacy, so a reviewable JSON
|
|
4
|
+
* export is the portability path). The exported document is plain
|
|
5
|
+
* JSON-serializable data — semantic facts plus episodic trace summaries — and
|
|
6
|
+
* re-imports loss-free over the existing insert paths (provenance still
|
|
7
|
+
* mandatory; dedup by the existing UNIQUE keys). Faculty isolation holds: this
|
|
8
|
+
* module imports only zod and the faculty's own stores (CLM-0069).
|
|
9
|
+
*/
|
|
10
|
+
import type Database from 'better-sqlite3';
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
/** One exported semantic fact — the durable fields, timestamps included. */
|
|
13
|
+
export declare const SemanticFactExportSchema: z.ZodObject<{
|
|
14
|
+
fact: z.ZodString;
|
|
15
|
+
provenance: z.ZodString;
|
|
16
|
+
confidence: z.ZodNullable<z.ZodNumber>;
|
|
17
|
+
createdAt: z.ZodNumber;
|
|
18
|
+
refreshedAt: z.ZodNumber;
|
|
19
|
+
}, z.core.$strict>;
|
|
20
|
+
export type SemanticFactExport = z.infer<typeof SemanticFactExportSchema>;
|
|
21
|
+
/** One exported episodic trace summary — never the full transcript (spec §8). */
|
|
22
|
+
export declare const TraceSummaryExportSchema: z.ZodObject<{
|
|
23
|
+
taskId: z.ZodString;
|
|
24
|
+
summary: z.ZodString;
|
|
25
|
+
traceRef: z.ZodString;
|
|
26
|
+
status: z.ZodEnum<{
|
|
27
|
+
success: "success";
|
|
28
|
+
partial: "partial";
|
|
29
|
+
failure: "failure";
|
|
30
|
+
cancelled: "cancelled";
|
|
31
|
+
}>;
|
|
32
|
+
distillCandidates: z.ZodArray<z.ZodString>;
|
|
33
|
+
createdAt: z.ZodNumber;
|
|
34
|
+
}, z.core.$strict>;
|
|
35
|
+
export type TraceSummaryExport = z.infer<typeof TraceSummaryExportSchema>;
|
|
36
|
+
/** The portable memory document (spec §7). Versioned for forward migration. */
|
|
37
|
+
export declare const MemoryExportSchema: z.ZodObject<{
|
|
38
|
+
version: z.ZodLiteral<"1">;
|
|
39
|
+
facts: z.ZodArray<z.ZodObject<{
|
|
40
|
+
fact: z.ZodString;
|
|
41
|
+
provenance: z.ZodString;
|
|
42
|
+
confidence: z.ZodNullable<z.ZodNumber>;
|
|
43
|
+
createdAt: z.ZodNumber;
|
|
44
|
+
refreshedAt: z.ZodNumber;
|
|
45
|
+
}, z.core.$strict>>;
|
|
46
|
+
traces: z.ZodArray<z.ZodObject<{
|
|
47
|
+
taskId: z.ZodString;
|
|
48
|
+
summary: z.ZodString;
|
|
49
|
+
traceRef: z.ZodString;
|
|
50
|
+
status: z.ZodEnum<{
|
|
51
|
+
success: "success";
|
|
52
|
+
partial: "partial";
|
|
53
|
+
failure: "failure";
|
|
54
|
+
cancelled: "cancelled";
|
|
55
|
+
}>;
|
|
56
|
+
distillCandidates: z.ZodArray<z.ZodString>;
|
|
57
|
+
createdAt: z.ZodNumber;
|
|
58
|
+
}, z.core.$strict>>;
|
|
59
|
+
}, z.core.$strict>;
|
|
60
|
+
export type MemoryExport = z.infer<typeof MemoryExportSchema>;
|
|
61
|
+
/**
|
|
62
|
+
* Export every semantic fact and episodic trace summary as a portable,
|
|
63
|
+
* JSON-serializable document (CLM-0069). Facts carry their timestamps so a
|
|
64
|
+
* round-trip preserves the decay clock; traces carry their pointer and status
|
|
65
|
+
* but never the transcript behind `traceRef`.
|
|
66
|
+
*/
|
|
67
|
+
export declare function exportMemory(db: Database.Database): MemoryExport;
|
|
68
|
+
/**
|
|
69
|
+
* Import a memory document, upserting through the existing insert paths
|
|
70
|
+
* (CLM-0069). Provenance stays mandatory and dedup uses the existing UNIQUE
|
|
71
|
+
* keys (`facts.fact`, `traces.taskId`). Returns the counts written so the
|
|
72
|
+
* caller can audit the mutation. zod-validates the document at the boundary.
|
|
73
|
+
*/
|
|
74
|
+
export declare function importMemory(db: Database.Database, data: MemoryExport): {
|
|
75
|
+
facts: number;
|
|
76
|
+
traces: number;
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=export.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../src/export.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,4EAA4E;AAC5E,eAAO,MAAM,wBAAwB;;;;;;kBAMnC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,iFAAiF;AACjF,eAAO,MAAM,wBAAwB;;;;;;;;;;;;kBAOnC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,+EAA+E;AAC/E,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;kBAI7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAmB9D;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,YAAY,CA+BhE;AAeD;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,IAAI,EAAE,YAAY,GACjB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAqBnC"}
|
package/dist/export.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { rememberFact } from './semantic.js';
|
|
3
|
+
import { recordOutcome } from './episodic.js';
|
|
4
|
+
import { OutcomeStatusSchema } from '@kernloop/contracts';
|
|
5
|
+
/** One exported semantic fact — the durable fields, timestamps included. */
|
|
6
|
+
export const SemanticFactExportSchema = z.strictObject({
|
|
7
|
+
fact: z.string().min(1),
|
|
8
|
+
provenance: z.string().min(1),
|
|
9
|
+
confidence: z.number().min(0).max(1).nullable(),
|
|
10
|
+
createdAt: z.number().int().nonnegative(),
|
|
11
|
+
refreshedAt: z.number().int().nonnegative(),
|
|
12
|
+
});
|
|
13
|
+
/** One exported episodic trace summary — never the full transcript (spec §8). */
|
|
14
|
+
export const TraceSummaryExportSchema = z.strictObject({
|
|
15
|
+
taskId: z.string().min(1),
|
|
16
|
+
summary: z.string(),
|
|
17
|
+
traceRef: z.string().min(1),
|
|
18
|
+
status: OutcomeStatusSchema,
|
|
19
|
+
distillCandidates: z.array(z.string()),
|
|
20
|
+
createdAt: z.number().int().nonnegative(),
|
|
21
|
+
});
|
|
22
|
+
/** The portable memory document (spec §7). Versioned for forward migration. */
|
|
23
|
+
export const MemoryExportSchema = z.strictObject({
|
|
24
|
+
version: z.literal('1'),
|
|
25
|
+
facts: z.array(SemanticFactExportSchema),
|
|
26
|
+
traces: z.array(TraceSummaryExportSchema),
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* Export every semantic fact and episodic trace summary as a portable,
|
|
30
|
+
* JSON-serializable document (CLM-0069). Facts carry their timestamps so a
|
|
31
|
+
* round-trip preserves the decay clock; traces carry their pointer and status
|
|
32
|
+
* but never the transcript behind `traceRef`.
|
|
33
|
+
*/
|
|
34
|
+
export function exportMemory(db) {
|
|
35
|
+
const factRows = db
|
|
36
|
+
.prepare('SELECT fact, provenance, confidence, createdAt, refreshedAt FROM facts ORDER BY id ASC')
|
|
37
|
+
.all();
|
|
38
|
+
const facts = factRows.map((row) => ({
|
|
39
|
+
fact: row.fact,
|
|
40
|
+
provenance: row.provenance,
|
|
41
|
+
confidence: row.confidence,
|
|
42
|
+
createdAt: row.createdAt,
|
|
43
|
+
refreshedAt: row.refreshedAt,
|
|
44
|
+
}));
|
|
45
|
+
// Insertion order (rowid ASC) — stable through a round-trip so re-export of
|
|
46
|
+
// an imported document is byte-identical (the newest-first read order of
|
|
47
|
+
// listSummaries is for display, not for portability).
|
|
48
|
+
const traceRows = db
|
|
49
|
+
.prepare(`SELECT taskId, summary, traceRef, status, distillCandidates, createdAt
|
|
50
|
+
FROM traces ORDER BY rowid ASC`)
|
|
51
|
+
.all();
|
|
52
|
+
const traces = traceRows.map((row) => ({
|
|
53
|
+
taskId: row.taskId,
|
|
54
|
+
summary: row.summary,
|
|
55
|
+
traceRef: row.traceRef,
|
|
56
|
+
status: row.status,
|
|
57
|
+
distillCandidates: JSON.parse(row.distillCandidates),
|
|
58
|
+
createdAt: row.createdAt,
|
|
59
|
+
}));
|
|
60
|
+
return { version: '1', facts, traces };
|
|
61
|
+
}
|
|
62
|
+
/** Upsert one exported fact, preserving its original timestamps. */
|
|
63
|
+
function importFact(db, fact) {
|
|
64
|
+
const record = rememberFact(db, fact.refreshedAt, {
|
|
65
|
+
fact: fact.fact,
|
|
66
|
+
provenance: fact.provenance,
|
|
67
|
+
...(fact.confidence === null ? {} : { confidence: fact.confidence }),
|
|
68
|
+
});
|
|
69
|
+
// rememberFact stamps both clocks to `now`; restore the exported createdAt
|
|
70
|
+
// so a round-trip is loss-free on a fresh row (UNIQUE(fact) dedup otherwise).
|
|
71
|
+
db.prepare('UPDATE facts SET createdAt = ? WHERE id = ?').run(fact.createdAt, record.id);
|
|
72
|
+
return { ...record, createdAt: fact.createdAt };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Import a memory document, upserting through the existing insert paths
|
|
76
|
+
* (CLM-0069). Provenance stays mandatory and dedup uses the existing UNIQUE
|
|
77
|
+
* keys (`facts.fact`, `traces.taskId`). Returns the counts written so the
|
|
78
|
+
* caller can audit the mutation. zod-validates the document at the boundary.
|
|
79
|
+
*/
|
|
80
|
+
export function importMemory(db, data) {
|
|
81
|
+
const parsed = MemoryExportSchema.parse(data);
|
|
82
|
+
return db.transaction(() => {
|
|
83
|
+
for (const fact of parsed.facts)
|
|
84
|
+
importFact(db, fact);
|
|
85
|
+
for (const t of parsed.traces) {
|
|
86
|
+
recordOutcome(db, t.createdAt, {
|
|
87
|
+
taskId: t.taskId,
|
|
88
|
+
status: t.status,
|
|
89
|
+
signals: [],
|
|
90
|
+
traceRef: t.traceRef,
|
|
91
|
+
distillCandidates: t.distillCandidates,
|
|
92
|
+
cost: { tokens: 0, usd: 0 },
|
|
93
|
+
}, t.summary);
|
|
94
|
+
}
|
|
95
|
+
return { facts: parsed.facts.length, traces: parsed.traces.length };
|
|
96
|
+
})();
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export.js","sourceRoot":"","sources":["../src/export.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @kernloop/faculty-memory — Layer 2 memory faculty (spec §5.2).
|
|
3
|
+
*
|
|
4
|
+
* P1 surface: the episodic and semantic stores over repo-local SQLite — one
|
|
5
|
+
* database file per overlay (spec §3.3, §7). The procedural store (SKILL.md
|
|
6
|
+
* via the `distill` ratification path) lands in P3; absent here by design,
|
|
7
|
+
* not stubbed (constitutional rule 1). Auditing of memory operations happens
|
|
8
|
+
* kernel-side at the bus boundary; this faculty imports only
|
|
9
|
+
* @kernloop/contracts and external dependencies (constitutional rule 5).
|
|
10
|
+
*/
|
|
11
|
+
import type { Outcome } from '@kernloop/contracts';
|
|
12
|
+
import { type FactRecord, type RecallOptions, type RecalledFact, type RememberFactInput } from './semantic.js';
|
|
13
|
+
import { type TraceSummary } from './episodic.js';
|
|
14
|
+
import { type MemoryExport } from './export.js';
|
|
15
|
+
export { ProvenanceRequiredError, InvalidFactError, InvalidOutcomeError } from './errors.js';
|
|
16
|
+
export { SCHEMA_DDL } from './store.js';
|
|
17
|
+
export { DECAY_HALF_LIFE_MS, DEFAULT_RECALL_LIMIT } from './semantic.js';
|
|
18
|
+
export type { FactRecord, RecalledFact, RememberFactInput, RecallOptions } from './semantic.js';
|
|
19
|
+
export { DEFAULT_LIST_LIMIT } from './episodic.js';
|
|
20
|
+
export type { TraceSummary } from './episodic.js';
|
|
21
|
+
export { MemoryExportSchema, SemanticFactExportSchema, TraceSummaryExportSchema, } from './export.js';
|
|
22
|
+
export type { MemoryExport, SemanticFactExport, TraceSummaryExport } from './export.js';
|
|
23
|
+
export { memoryManifest } from './manifest.js';
|
|
24
|
+
/** Options for {@link createMemory}. */
|
|
25
|
+
export interface CreateMemoryOptions {
|
|
26
|
+
/**
|
|
27
|
+
* Write-time clock returning epoch ms; defaults to `Date.now`. Injectable
|
|
28
|
+
* so decay/refresh behavior is deterministic under test.
|
|
29
|
+
*/
|
|
30
|
+
clock?: () => number;
|
|
31
|
+
}
|
|
32
|
+
/** The memory faculty's API over one overlay database. */
|
|
33
|
+
export interface Memory {
|
|
34
|
+
/** Semantic write — provenance mandatory (CLM-0022); refresh on re-write. */
|
|
35
|
+
rememberFact(input: RememberFactInput): FactRecord;
|
|
36
|
+
/** Semantic read — ranked by relevance × provenance × recency (CLM-0023). */
|
|
37
|
+
recallFacts(query: string, options?: RecallOptions): RecalledFact[];
|
|
38
|
+
/** Episodic write — zod-validated Outcome → summary + traceRef (CLM-0024). */
|
|
39
|
+
recordOutcome(outcome: Outcome, summary: string): TraceSummary;
|
|
40
|
+
/** Episodic read — one task's summary, or `undefined` when absent. */
|
|
41
|
+
getTraceSummary(taskId: string): TraceSummary | undefined;
|
|
42
|
+
/** Episodic read — summaries newest-first (CLM-0024). */
|
|
43
|
+
listSummaries(options?: {
|
|
44
|
+
limit?: number;
|
|
45
|
+
}): TraceSummary[];
|
|
46
|
+
/** Portable export — facts + trace summaries as a JSON document (CLM-0069). */
|
|
47
|
+
exportMemory(): MemoryExport;
|
|
48
|
+
/** Loss-free import — upserts the export over the existing insert paths;
|
|
49
|
+
* returns the counts written so the caller can audit it (CLM-0069). */
|
|
50
|
+
importMemory(data: MemoryExport): {
|
|
51
|
+
facts: number;
|
|
52
|
+
traces: number;
|
|
53
|
+
};
|
|
54
|
+
/** Close the underlying database handle. */
|
|
55
|
+
close(): void;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Open (creating and migrating if absent) the overlay memory database at
|
|
59
|
+
* `dbPath` (spec §7: `.kernloop/memory.sqlite`). Deleting the file and
|
|
60
|
+
* calling this again yields a functional, empty store (CLM-0025).
|
|
61
|
+
*/
|
|
62
|
+
export declare function createMemory(dbPath: string, options?: CreateMemoryOptions): Memory;
|
|
63
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACvB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAiD,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AACjG,OAAO,EAA8B,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAE5E,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC7F,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACzE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAChG,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACxF,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,wCAAwC;AACxC,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;CACtB;AAED,0DAA0D;AAC1D,MAAM,WAAW,MAAM;IACrB,6EAA6E;IAC7E,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,UAAU,CAAC;IACnD,6EAA6E;IAC7E,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,YAAY,EAAE,CAAC;IACpE,8EAA8E;IAC9E,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,CAAC;IAC/D,sEAAsE;IACtE,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IAC1D,yDAAyD;IACzD,aAAa,CAAC,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,YAAY,EAAE,CAAC;IAC5D,+EAA+E;IAC/E,YAAY,IAAI,YAAY,CAAC;IAC7B;2EACuE;IACvE,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACpE,4CAA4C;IAC5C,KAAK,IAAI,IAAI,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB,GAAG,MAAM,CAatF"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { openStore } from './store.js';
|
|
2
|
+
import { rememberFact, recallFacts, } from './semantic.js';
|
|
3
|
+
import { getTraceSummary, listSummaries, recordOutcome } from './episodic.js';
|
|
4
|
+
import { exportMemory, importMemory } from './export.js';
|
|
5
|
+
export { ProvenanceRequiredError, InvalidFactError, InvalidOutcomeError } from './errors.js';
|
|
6
|
+
export { SCHEMA_DDL } from './store.js';
|
|
7
|
+
export { DECAY_HALF_LIFE_MS, DEFAULT_RECALL_LIMIT } from './semantic.js';
|
|
8
|
+
export { DEFAULT_LIST_LIMIT } from './episodic.js';
|
|
9
|
+
export { MemoryExportSchema, SemanticFactExportSchema, TraceSummaryExportSchema, } from './export.js';
|
|
10
|
+
export { memoryManifest } from './manifest.js';
|
|
11
|
+
/**
|
|
12
|
+
* Open (creating and migrating if absent) the overlay memory database at
|
|
13
|
+
* `dbPath` (spec §7: `.kernloop/memory.sqlite`). Deleting the file and
|
|
14
|
+
* calling this again yields a functional, empty store (CLM-0025).
|
|
15
|
+
*/
|
|
16
|
+
export function createMemory(dbPath, options = {}) {
|
|
17
|
+
const clock = options.clock ?? Date.now;
|
|
18
|
+
const db = openStore(dbPath);
|
|
19
|
+
return {
|
|
20
|
+
rememberFact: (input) => rememberFact(db, clock(), input),
|
|
21
|
+
recallFacts: (query, opts) => recallFacts(db, query, opts),
|
|
22
|
+
recordOutcome: (outcome, summary) => recordOutcome(db, clock(), outcome, summary),
|
|
23
|
+
getTraceSummary: (taskId) => getTraceSummary(db, taskId),
|
|
24
|
+
listSummaries: (opts) => listSummaries(db, opts),
|
|
25
|
+
exportMemory: () => exportMemory(db),
|
|
26
|
+
importMemory: (data) => importMemory(db, data),
|
|
27
|
+
close: () => db.close(),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EACL,YAAY,EACZ,WAAW,GAKZ,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAqB,MAAM,eAAe,CAAC;AACjG,OAAO,EAAE,YAAY,EAAE,YAAY,EAAqB,MAAM,aAAa,CAAC;AAE5E,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC7F,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAEzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAgC/C;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,UAA+B,EAAE;IAC5E,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC;IACxC,MAAM,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IAC7B,OAAO;QACL,YAAY,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,KAAK,CAAC;QACzD,WAAW,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC;QAC1D,aAAa,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,aAAa,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC;QACjF,eAAe,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,eAAe,CAAC,EAAE,EAAE,MAAM,CAAC;QACxD,aAAa,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,EAAE,EAAE,IAAI,CAAC;QAChD,YAAY,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC;QACpC,YAAY,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,EAAE,EAAE,IAAI,CAAC;QAC9C,KAAK,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE;KACxB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The memory faculty's registration record (spec §4, §5.2). Parsed through
|
|
3
|
+
* `ManifestSchema` at module load so an invalid manifest fails fast, not at
|
|
4
|
+
* registry time.
|
|
5
|
+
*/
|
|
6
|
+
import { type Manifest } from '@kernloop/contracts';
|
|
7
|
+
/**
|
|
8
|
+
* The faculty-memory registration manifest. Tier is `suggest` — the
|
|
9
|
+
* conservative entry default for anything generative-adjacent (spec §3.2);
|
|
10
|
+
* tier assignment is under ratification review and the orchestrator may
|
|
11
|
+
* adjust at integration. Consumes Outcome (episodic auto-write); emits
|
|
12
|
+
* nothing — recall results travel as Brief fragments compiled elsewhere.
|
|
13
|
+
*/
|
|
14
|
+
export declare const memoryManifest: Manifest;
|
|
15
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpE;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,EAAE,QAsB3B,CAAC"}
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The memory faculty's registration record (spec §4, §5.2). Parsed through
|
|
3
|
+
* `ManifestSchema` at module load so an invalid manifest fails fast, not at
|
|
4
|
+
* registry time.
|
|
5
|
+
*/
|
|
6
|
+
import { ManifestSchema } from '@kernloop/contracts';
|
|
7
|
+
/**
|
|
8
|
+
* The faculty-memory registration manifest. Tier is `suggest` — the
|
|
9
|
+
* conservative entry default for anything generative-adjacent (spec §3.2);
|
|
10
|
+
* tier assignment is under ratification review and the orchestrator may
|
|
11
|
+
* adjust at integration. Consumes Outcome (episodic auto-write); emits
|
|
12
|
+
* nothing — recall results travel as Brief fragments compiled elsewhere.
|
|
13
|
+
*/
|
|
14
|
+
export const memoryManifest = ManifestSchema.parse({
|
|
15
|
+
name: '@kernloop/faculty-memory',
|
|
16
|
+
version: '0.1.0',
|
|
17
|
+
kind: 'faculty',
|
|
18
|
+
capabilities: [
|
|
19
|
+
{ name: 'memory.semantic.write', description: 'Store a typed fact; provenance mandatory' },
|
|
20
|
+
{ name: 'memory.semantic.recall', description: 'Rank facts by relevance, provenance, recency' },
|
|
21
|
+
{ name: 'memory.episodic.write', description: 'Record an Outcome as summary + trace pointer' },
|
|
22
|
+
{ name: 'memory.episodic.read', description: 'Fetch trace summaries, newest first' },
|
|
23
|
+
],
|
|
24
|
+
contracts: {
|
|
25
|
+
consumes: ['Outcome'],
|
|
26
|
+
emits: [],
|
|
27
|
+
},
|
|
28
|
+
cost: {
|
|
29
|
+
tokens: 0,
|
|
30
|
+
usd: 0,
|
|
31
|
+
latencyMs: 50,
|
|
32
|
+
},
|
|
33
|
+
tier: 'suggest',
|
|
34
|
+
claims: ['CLM-0022', 'CLM-0023', 'CLM-0024', 'CLM-0025'],
|
|
35
|
+
maturity: 'stable',
|
|
36
|
+
});
|
|
37
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,cAAc,EAAiB,MAAM,qBAAqB,CAAC;AAEpE;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAa,cAAc,CAAC,KAAK,CAAC;IAC3D,IAAI,EAAE,0BAA0B;IAChC,OAAO,EAAE,OAAO;IAChB,IAAI,EAAE,SAAS;IACf,YAAY,EAAE;QACZ,EAAE,IAAI,EAAE,uBAAuB,EAAE,WAAW,EAAE,0CAA0C,EAAE;QAC1F,EAAE,IAAI,EAAE,wBAAwB,EAAE,WAAW,EAAE,8CAA8C,EAAE;QAC/F,EAAE,IAAI,EAAE,uBAAuB,EAAE,WAAW,EAAE,8CAA8C,EAAE;QAC9F,EAAE,IAAI,EAAE,sBAAsB,EAAE,WAAW,EAAE,qCAAqC,EAAE;KACrF;IACD,SAAS,EAAE;QACT,QAAQ,EAAE,CAAC,SAAS,CAAC;QACrB,KAAK,EAAE,EAAE;KACV;IACD,IAAI,EAAE;QACJ,MAAM,EAAE,CAAC;QACT,GAAG,EAAE,CAAC;QACN,SAAS,EAAE,EAAE;KACd;IACD,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,CAAC;IACxD,QAAQ,EAAE,QAAQ;CACnB,CAAC,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic store (spec §5.2): typed facts in SQLite. Write policy —
|
|
3
|
+
* provenance mandatory, optional confidence, decay clock (unrefreshed facts
|
|
4
|
+
* fade). Read policy — ranked by relevance × provenance × recency.
|
|
5
|
+
* All SQL uses parameterized statements; fact/provenance text is data, never
|
|
6
|
+
* SQL.
|
|
7
|
+
*/
|
|
8
|
+
import type Database from 'better-sqlite3';
|
|
9
|
+
/** A stored semantic fact. Timestamps are epoch milliseconds. */
|
|
10
|
+
export interface FactRecord {
|
|
11
|
+
id: number;
|
|
12
|
+
fact: string;
|
|
13
|
+
provenance: string;
|
|
14
|
+
confidence: number | null;
|
|
15
|
+
createdAt: number;
|
|
16
|
+
refreshedAt: number;
|
|
17
|
+
}
|
|
18
|
+
/** A recalled fact with its ranking score (see {@link recallFacts}). */
|
|
19
|
+
export interface RecalledFact extends FactRecord {
|
|
20
|
+
score: number;
|
|
21
|
+
}
|
|
22
|
+
/** Input to {@link rememberFact}. Provenance is mandatory (spec §5.2). */
|
|
23
|
+
export interface RememberFactInput {
|
|
24
|
+
fact: string;
|
|
25
|
+
provenance: string;
|
|
26
|
+
confidence?: number;
|
|
27
|
+
}
|
|
28
|
+
/** Options for {@link recallFacts}. */
|
|
29
|
+
export interface RecallOptions {
|
|
30
|
+
/** Clock for decay scoring, epoch ms; injectable for deterministic tests. */
|
|
31
|
+
now?: number;
|
|
32
|
+
/** Maximum facts returned; default {@link DEFAULT_RECALL_LIMIT}. */
|
|
33
|
+
limit?: number;
|
|
34
|
+
}
|
|
35
|
+
/** Decay half-life: an unrefreshed fact's recency factor halves every 14 days. */
|
|
36
|
+
export declare const DECAY_HALF_LIFE_MS: number;
|
|
37
|
+
/** Default maximum number of facts a recall returns. */
|
|
38
|
+
export declare const DEFAULT_RECALL_LIMIT = 10;
|
|
39
|
+
/**
|
|
40
|
+
* Write (or refresh) a fact. Provenance is mandatory: missing, empty, or
|
|
41
|
+
* whitespace-only provenance throws {@link ProvenanceRequiredError}
|
|
42
|
+
* (CLM-0022); other malformed input throws {@link InvalidFactError}.
|
|
43
|
+
* Re-remembering an identical fact does not duplicate it — it updates
|
|
44
|
+
* provenance/confidence and resets `refreshedAt`, the decay clock (CLM-0023).
|
|
45
|
+
*/
|
|
46
|
+
export declare function rememberFact(db: Database.Database, now: number, input: RememberFactInput): FactRecord;
|
|
47
|
+
/**
|
|
48
|
+
* Recall facts relevant to `query`, ranked by the score documented on
|
|
49
|
+
* {@link scoreFact}. Facts with zero relevance are omitted. Ties break on
|
|
50
|
+
* fresher `refreshedAt`, then insertion order. `now` is injectable for
|
|
51
|
+
* deterministic decay tests.
|
|
52
|
+
*/
|
|
53
|
+
export declare function recallFacts(db: Database.Database, query: string, options?: RecallOptions): RecalledFact[];
|
|
54
|
+
//# sourceMappingURL=semantic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semantic.d.ts","sourceRoot":"","sources":["../src/semantic.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAI3C,iEAAiE;AACjE,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wEAAwE;AACxE,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,KAAK,EAAE,MAAM,CAAC;CACf;AAED,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,uCAAuC;AACvC,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAQD,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,QAA2B,CAAC;AAE3D,wDAAwD;AACxD,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,iBAAiB,GACvB,UAAU,CAqBZ;AAuCD;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,aAAkB,GAC1B,YAAY,EAAE,CAYhB"}
|
package/dist/semantic.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { InvalidFactError, ProvenanceRequiredError } from './errors.js';
|
|
3
|
+
const RememberFactSchema = z.strictObject({
|
|
4
|
+
fact: z.string().min(1, 'fact must be a non-empty string'),
|
|
5
|
+
provenance: z.string().optional(),
|
|
6
|
+
confidence: z.number().min(0).max(1).optional(),
|
|
7
|
+
});
|
|
8
|
+
/** Decay half-life: an unrefreshed fact's recency factor halves every 14 days. */
|
|
9
|
+
export const DECAY_HALF_LIFE_MS = 14 * 24 * 60 * 60 * 1000;
|
|
10
|
+
/** Default maximum number of facts a recall returns. */
|
|
11
|
+
export const DEFAULT_RECALL_LIMIT = 10;
|
|
12
|
+
/**
|
|
13
|
+
* Write (or refresh) a fact. Provenance is mandatory: missing, empty, or
|
|
14
|
+
* whitespace-only provenance throws {@link ProvenanceRequiredError}
|
|
15
|
+
* (CLM-0022); other malformed input throws {@link InvalidFactError}.
|
|
16
|
+
* Re-remembering an identical fact does not duplicate it — it updates
|
|
17
|
+
* provenance/confidence and resets `refreshedAt`, the decay clock (CLM-0023).
|
|
18
|
+
*/
|
|
19
|
+
export function rememberFact(db, now, input) {
|
|
20
|
+
const parsed = RememberFactSchema.safeParse(input);
|
|
21
|
+
if (!parsed.success) {
|
|
22
|
+
throw new InvalidFactError(`semantic memory write rejected: ${z.prettifyError(parsed.error)}`);
|
|
23
|
+
}
|
|
24
|
+
const { fact, provenance, confidence } = parsed.data;
|
|
25
|
+
if (provenance === undefined || provenance.trim().length === 0) {
|
|
26
|
+
throw new ProvenanceRequiredError();
|
|
27
|
+
}
|
|
28
|
+
const row = db
|
|
29
|
+
.prepare(`INSERT INTO facts (fact, provenance, confidence, createdAt, refreshedAt)
|
|
30
|
+
VALUES (?, ?, ?, ?, ?)
|
|
31
|
+
ON CONFLICT(fact) DO UPDATE SET
|
|
32
|
+
provenance = excluded.provenance,
|
|
33
|
+
confidence = COALESCE(excluded.confidence, facts.confidence),
|
|
34
|
+
refreshedAt = excluded.refreshedAt
|
|
35
|
+
RETURNING id, fact, provenance, confidence, createdAt, refreshedAt`)
|
|
36
|
+
.get(fact, provenance, confidence ?? null, now, now);
|
|
37
|
+
return row;
|
|
38
|
+
}
|
|
39
|
+
/** Lowercased word tokens of a text (split on non-alphanumerics, deduped). */
|
|
40
|
+
function tokenize(text) {
|
|
41
|
+
return new Set(text
|
|
42
|
+
.toLowerCase()
|
|
43
|
+
.split(/[^\p{L}\p{N}]+/u)
|
|
44
|
+
.filter((t) => t.length > 0));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Score one fact against a query (spec §5.2 read policy: relevance ×
|
|
48
|
+
* provenance × recency). The formula, exactly:
|
|
49
|
+
*
|
|
50
|
+
* - **relevance** — token overlap: |query tokens ∩ fact tokens| / |query
|
|
51
|
+
* tokens|, over lowercased word tokens. 0 means no shared token.
|
|
52
|
+
* - **provenance** — 1 when provenance is present, else 0. Writes make
|
|
53
|
+
* provenance mandatory, so this factor is 1 for every stored fact; it is
|
|
54
|
+
* kept explicit so the spec's ranking contract is visible in code.
|
|
55
|
+
* - **recency** — exponential decay on the decay clock:
|
|
56
|
+
* `2 ** (-(now - refreshedAt) / DECAY_HALF_LIFE_MS)`. A fact refreshed
|
|
57
|
+
* "now" scores 1; every 14 unrefreshed days halve it. Re-remembering
|
|
58
|
+
* resets `refreshedAt`, so refreshed facts outrank stale ones (CLM-0023).
|
|
59
|
+
*/
|
|
60
|
+
function scoreFact(fact, queryTokens, now) {
|
|
61
|
+
let overlap = 0;
|
|
62
|
+
const factTokens = tokenize(fact.fact);
|
|
63
|
+
for (const token of queryTokens) {
|
|
64
|
+
if (factTokens.has(token))
|
|
65
|
+
overlap += 1;
|
|
66
|
+
}
|
|
67
|
+
const relevance = queryTokens.size === 0 ? 0 : overlap / queryTokens.size;
|
|
68
|
+
const provenanceFactor = fact.provenance.trim().length > 0 ? 1 : 0;
|
|
69
|
+
const ageMs = Math.max(0, now - fact.refreshedAt);
|
|
70
|
+
const recency = 2 ** (-ageMs / DECAY_HALF_LIFE_MS);
|
|
71
|
+
return relevance * provenanceFactor * recency;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Recall facts relevant to `query`, ranked by the score documented on
|
|
75
|
+
* {@link scoreFact}. Facts with zero relevance are omitted. Ties break on
|
|
76
|
+
* fresher `refreshedAt`, then insertion order. `now` is injectable for
|
|
77
|
+
* deterministic decay tests.
|
|
78
|
+
*/
|
|
79
|
+
export function recallFacts(db, query, options = {}) {
|
|
80
|
+
const now = options.now ?? Date.now();
|
|
81
|
+
const limit = options.limit ?? DEFAULT_RECALL_LIMIT;
|
|
82
|
+
const queryTokens = tokenize(query);
|
|
83
|
+
const rows = db
|
|
84
|
+
.prepare('SELECT id, fact, provenance, confidence, createdAt, refreshedAt FROM facts')
|
|
85
|
+
.all();
|
|
86
|
+
return rows
|
|
87
|
+
.map((fact) => ({ ...fact, score: scoreFact(fact, queryTokens, now) }))
|
|
88
|
+
.filter((fact) => fact.score > 0)
|
|
89
|
+
.sort((a, b) => b.score - a.score || b.refreshedAt - a.refreshedAt || a.id - b.id)
|
|
90
|
+
.slice(0, limit);
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=semantic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semantic.js","sourceRoot":"","sources":["../src/semantic.ts"],"names":[],"mappings":"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"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite storage for the memory faculty (spec §3.3, §7): one database file
|
|
3
|
+
* per overlay, opened and migrated on `createMemory`. Migration is a set of
|
|
4
|
+
* idempotent `CREATE TABLE IF NOT EXISTS` statements, so deleting the file
|
|
5
|
+
* and reopening yields a functional, empty store (CLM-0025) and reopening an
|
|
6
|
+
* existing file preserves state.
|
|
7
|
+
*/
|
|
8
|
+
import Database from 'better-sqlite3';
|
|
9
|
+
/**
|
|
10
|
+
* The complete schema. Timestamps are epoch milliseconds.
|
|
11
|
+
*
|
|
12
|
+
* - `facts` — semantic store (spec §5.2): typed facts with mandatory
|
|
13
|
+
* provenance, optional confidence, and the decay clock (`refreshedAt`).
|
|
14
|
+
* `fact` is UNIQUE so re-remembering an identical fact refreshes rather
|
|
15
|
+
* than duplicates.
|
|
16
|
+
* - `traces` — episodic store (spec §5.2, §8 item 5): compressed summary +
|
|
17
|
+
* pointer to the full trace (`traceRef`), never the transcript itself.
|
|
18
|
+
* One row per task; a re-recorded Outcome replaces the row.
|
|
19
|
+
*/
|
|
20
|
+
export declare const SCHEMA_DDL = "\nCREATE TABLE IF NOT EXISTS facts (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n fact TEXT NOT NULL UNIQUE,\n provenance TEXT NOT NULL,\n confidence REAL,\n createdAt INTEGER NOT NULL,\n refreshedAt INTEGER NOT NULL\n);\nCREATE TABLE IF NOT EXISTS traces (\n taskId TEXT PRIMARY KEY,\n summary TEXT NOT NULL,\n traceRef TEXT NOT NULL,\n status TEXT NOT NULL,\n distillCandidates TEXT NOT NULL,\n createdAt INTEGER NOT NULL\n);\n";
|
|
21
|
+
/** Open (creating if absent) and migrate the overlay database at `dbPath`.
|
|
22
|
+
* WAL + a busy timeout let a stateless reader (`status`/`observe`/`metrics`)
|
|
23
|
+
* run concurrently with the resident `serve` writer without a SQLITE_BUSY
|
|
24
|
+
* (#157): WAL readers do not block on the single writer, and the timeout waits
|
|
25
|
+
* on genuine lock contention instead of erroring immediately. */
|
|
26
|
+
export declare function openStore(dbPath: string): Database.Database;
|
|
27
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAEtC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,UAAU,2bAiBtB,CAAC;AAEF;;;;iEAIiE;AACjE,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAM3D"}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite storage for the memory faculty (spec §3.3, §7): one database file
|
|
3
|
+
* per overlay, opened and migrated on `createMemory`. Migration is a set of
|
|
4
|
+
* idempotent `CREATE TABLE IF NOT EXISTS` statements, so deleting the file
|
|
5
|
+
* and reopening yields a functional, empty store (CLM-0025) and reopening an
|
|
6
|
+
* existing file preserves state.
|
|
7
|
+
*/
|
|
8
|
+
import Database from 'better-sqlite3';
|
|
9
|
+
/**
|
|
10
|
+
* The complete schema. Timestamps are epoch milliseconds.
|
|
11
|
+
*
|
|
12
|
+
* - `facts` — semantic store (spec §5.2): typed facts with mandatory
|
|
13
|
+
* provenance, optional confidence, and the decay clock (`refreshedAt`).
|
|
14
|
+
* `fact` is UNIQUE so re-remembering an identical fact refreshes rather
|
|
15
|
+
* than duplicates.
|
|
16
|
+
* - `traces` — episodic store (spec §5.2, §8 item 5): compressed summary +
|
|
17
|
+
* pointer to the full trace (`traceRef`), never the transcript itself.
|
|
18
|
+
* One row per task; a re-recorded Outcome replaces the row.
|
|
19
|
+
*/
|
|
20
|
+
export const SCHEMA_DDL = `
|
|
21
|
+
CREATE TABLE IF NOT EXISTS facts (
|
|
22
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
23
|
+
fact TEXT NOT NULL UNIQUE,
|
|
24
|
+
provenance TEXT NOT NULL,
|
|
25
|
+
confidence REAL,
|
|
26
|
+
createdAt INTEGER NOT NULL,
|
|
27
|
+
refreshedAt INTEGER NOT NULL
|
|
28
|
+
);
|
|
29
|
+
CREATE TABLE IF NOT EXISTS traces (
|
|
30
|
+
taskId TEXT PRIMARY KEY,
|
|
31
|
+
summary TEXT NOT NULL,
|
|
32
|
+
traceRef TEXT NOT NULL,
|
|
33
|
+
status TEXT NOT NULL,
|
|
34
|
+
distillCandidates TEXT NOT NULL,
|
|
35
|
+
createdAt INTEGER NOT NULL
|
|
36
|
+
);
|
|
37
|
+
`;
|
|
38
|
+
/** Open (creating if absent) and migrate the overlay database at `dbPath`.
|
|
39
|
+
* WAL + a busy timeout let a stateless reader (`status`/`observe`/`metrics`)
|
|
40
|
+
* run concurrently with the resident `serve` writer without a SQLITE_BUSY
|
|
41
|
+
* (#157): WAL readers do not block on the single writer, and the timeout waits
|
|
42
|
+
* on genuine lock contention instead of erroring immediately. */
|
|
43
|
+
export function openStore(dbPath) {
|
|
44
|
+
const db = new Database(dbPath);
|
|
45
|
+
db.pragma('journal_mode = WAL');
|
|
46
|
+
db.pragma('busy_timeout = 5000');
|
|
47
|
+
db.exec(SCHEMA_DDL);
|
|
48
|
+
return db;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAEtC;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;CAiBzB,CAAC;AAEF;;;;iEAIiE;AACjE,MAAM,UAAU,SAAS,CAAC,MAAc;IACtC,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChC,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAChC,EAAE,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC;IACjC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACpB,OAAO,EAAE,CAAC;AACZ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kernloop/faculty-memory",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Kernloop Layer 2 — the memory faculty (spec §5.2). P1 scope: episodic and semantic stores over repo-local SQLite (one DB file per overlay, spec §3.3, §7). Procedural memory (SKILL.md distillation) arrives in P3; absent here by design.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"better-sqlite3": "^12.11.1",
|
|
20
|
+
"zod": "^4.4.3",
|
|
21
|
+
"@kernloop/contracts": "0.1.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
25
|
+
"@types/node": "^25.9.3"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/kernloop/kernloop.git",
|
|
33
|
+
"directory": "packages/faculty-memory"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsc -p tsconfig.json",
|
|
37
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
38
|
+
"test": "vitest run --coverage"
|
|
39
|
+
}
|
|
40
|
+
}
|