@theihtisham/agent-shadow-brain 7.1.0 → 7.1.3

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.
Files changed (111) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +47 -407
  3. package/dist/activity-cli.d.ts +3 -0
  4. package/dist/activity-cli.d.ts.map +1 -0
  5. package/dist/activity-cli.js +78 -0
  6. package/dist/activity-cli.js.map +1 -0
  7. package/dist/adapters/index.d.ts +2 -1
  8. package/dist/adapters/index.d.ts.map +1 -1
  9. package/dist/adapters/index.js +3 -2
  10. package/dist/adapters/index.js.map +1 -1
  11. package/dist/brain/activity-journal.d.ts +82 -0
  12. package/dist/brain/activity-journal.d.ts.map +1 -0
  13. package/dist/brain/activity-journal.js +314 -0
  14. package/dist/brain/activity-journal.js.map +1 -0
  15. package/dist/brain/activity-reporting.d.ts +14 -0
  16. package/dist/brain/activity-reporting.d.ts.map +1 -0
  17. package/dist/brain/activity-reporting.js +17 -0
  18. package/dist/brain/activity-reporting.js.map +1 -0
  19. package/dist/brain/auto-update.js +1 -1
  20. package/dist/brain/brain-portability.js +1 -1
  21. package/dist/brain/client-integrations.d.ts +61 -0
  22. package/dist/brain/client-integrations.d.ts.map +1 -0
  23. package/dist/brain/client-integrations.js +98 -0
  24. package/dist/brain/client-integrations.js.map +1 -0
  25. package/dist/brain/context-assembly.d.ts +76 -0
  26. package/dist/brain/context-assembly.d.ts.map +1 -0
  27. package/dist/brain/context-assembly.js +305 -0
  28. package/dist/brain/context-assembly.js.map +1 -0
  29. package/dist/brain/feature-settings.d.ts +58 -0
  30. package/dist/brain/feature-settings.d.ts.map +1 -0
  31. package/dist/brain/feature-settings.js +241 -0
  32. package/dist/brain/feature-settings.js.map +1 -0
  33. package/dist/brain/global-brain.d.ts +7 -0
  34. package/dist/brain/global-brain.d.ts.map +1 -1
  35. package/dist/brain/global-brain.js +19 -4
  36. package/dist/brain/global-brain.js.map +1 -1
  37. package/dist/brain/mcp-server.d.ts +21 -1
  38. package/dist/brain/mcp-server.d.ts.map +1 -1
  39. package/dist/brain/mcp-server.js +191 -25
  40. package/dist/brain/mcp-server.js.map +1 -1
  41. package/dist/brain/mcp-stdio.d.ts +12 -0
  42. package/dist/brain/mcp-stdio.d.ts.map +1 -0
  43. package/dist/brain/mcp-stdio.js +134 -0
  44. package/dist/brain/mcp-stdio.js.map +1 -0
  45. package/dist/brain/memory-annotations.d.ts +97 -0
  46. package/dist/brain/memory-annotations.d.ts.map +1 -0
  47. package/dist/brain/memory-annotations.js +388 -0
  48. package/dist/brain/memory-annotations.js.map +1 -0
  49. package/dist/brain/memory-graph.d.ts.map +1 -1
  50. package/dist/brain/memory-graph.js +12 -23
  51. package/dist/brain/memory-graph.js.map +1 -1
  52. package/dist/brain/memory-sources.d.ts +8 -0
  53. package/dist/brain/memory-sources.d.ts.map +1 -0
  54. package/dist/brain/memory-sources.js +27 -0
  55. package/dist/brain/memory-sources.js.map +1 -0
  56. package/dist/brain/orchestrator.js +1 -1
  57. package/dist/brain/subconscious.d.ts +21 -24
  58. package/dist/brain/subconscious.d.ts.map +1 -1
  59. package/dist/brain/subconscious.js +82 -218
  60. package/dist/brain/subconscious.js.map +1 -1
  61. package/dist/brain/workflow-skills.d.ts +5 -5
  62. package/dist/cli.js +19 -2
  63. package/dist/cli.js.map +1 -1
  64. package/dist/context-cli.d.ts +3 -0
  65. package/dist/context-cli.d.ts.map +1 -0
  66. package/dist/context-cli.js +56 -0
  67. package/dist/context-cli.js.map +1 -0
  68. package/dist/curation-cli.d.ts +4 -0
  69. package/dist/curation-cli.d.ts.map +1 -0
  70. package/dist/curation-cli.js +118 -0
  71. package/dist/curation-cli.js.map +1 -0
  72. package/dist/dashboard/live-workspace-html.d.ts +2 -0
  73. package/dist/dashboard/live-workspace-html.d.ts.map +1 -0
  74. package/dist/dashboard/live-workspace-html.js +893 -0
  75. package/dist/dashboard/live-workspace-html.js.map +1 -0
  76. package/dist/dashboard/server.d.ts +1 -0
  77. package/dist/dashboard/server.d.ts.map +1 -1
  78. package/dist/dashboard/server.js +53 -18
  79. package/dist/dashboard/server.js.map +1 -1
  80. package/dist/dashboard/v6-dashboard-html.d.ts +1 -1
  81. package/dist/dashboard/v6-dashboard-html.d.ts.map +1 -1
  82. package/dist/dashboard/v6-dashboard-html.js +7 -29
  83. package/dist/dashboard/v6-dashboard-html.js.map +1 -1
  84. package/dist/dashboard/workspace-api.d.ts +16 -0
  85. package/dist/dashboard/workspace-api.d.ts.map +1 -0
  86. package/dist/dashboard/workspace-api.js +309 -0
  87. package/dist/dashboard/workspace-api.js.map +1 -0
  88. package/dist/dashboard/workspace-html.d.ts +2 -0
  89. package/dist/dashboard/workspace-html.d.ts.map +1 -0
  90. package/dist/dashboard/workspace-html.js +2132 -0
  91. package/dist/dashboard/workspace-html.js.map +1 -0
  92. package/dist/index.d.ts +10 -0
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/index.js +8 -0
  95. package/dist/index.js.map +1 -1
  96. package/dist/integration-cli.d.ts +3 -0
  97. package/dist/integration-cli.d.ts.map +1 -0
  98. package/dist/integration-cli.js +14 -0
  99. package/dist/integration-cli.js.map +1 -0
  100. package/dist/types.d.ts +12 -3
  101. package/dist/types.d.ts.map +1 -1
  102. package/docs/WORKSPACE.md +111 -0
  103. package/docs/assets/context-flow-v7.2.png +0 -0
  104. package/docs/assets/live-v7.1.3.png +0 -0
  105. package/docs/assets/memory-detail-v7.1.3.png +0 -0
  106. package/docs/assets/overview-v7.1.3.png +0 -0
  107. package/docs/assets/workspace-graph-v7.2.png +0 -0
  108. package/docs/assets/workspace-overview-v7.2.png +0 -0
  109. package/docs/launch/RELEASE_NOTES_v7.1.3.md +40 -0
  110. package/docs/launch/RELEASE_NOTES_v7.2.0.md +24 -0
  111. package/package.json +20 -121
@@ -0,0 +1,12 @@
1
+ import type { Readable, Writable } from 'node:stream';
2
+ import { MCPServer } from './mcp-server.js';
3
+ /** Newline-delimited MCP, with bounded input and sequential backpressure. */
4
+ export declare function serveMCPStdio(server: Pick<MCPServer, 'handleRequest'>, input: Readable, output: Writable): Promise<void>;
5
+ /** Provider-free entry point: no orchestrator, client-profile scan, or config merge. */
6
+ export declare function runMemoryMCPStdio(options?: {
7
+ dbPath?: string;
8
+ settingsPath?: string;
9
+ projectDir?: string;
10
+ projectId?: string;
11
+ }): Promise<void>;
12
+ //# sourceMappingURL=mcp-stdio.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-stdio.d.ts","sourceRoot":"","sources":["../../src/brain/mcp-stdio.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGtD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAS5C,6EAA6E;AAC7E,wBAAsB,aAAa,CACjC,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE,eAAe,CAAC,EACxC,KAAK,EAAE,QAAQ,EACf,MAAM,EAAE,QAAQ,GACf,OAAO,CAAC,IAAI,CAAC,CAoFf;AAED,wFAAwF;AACxF,wBAAsB,iBAAiB,CAAC,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAQxJ"}
@@ -0,0 +1,134 @@
1
+ import { StringDecoder } from 'node:string_decoder';
2
+ import * as path from 'node:path';
3
+ import { MCPServer } from './mcp-server.js';
4
+ import { getGlobalBrain } from './global-brain.js';
5
+ import { FeatureSettingsStore } from './feature-settings.js';
6
+ const MAX_MESSAGE_BYTES = 1024 * 1024;
7
+ const MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
8
+ const record = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
9
+ /** Newline-delimited MCP, with bounded input and sequential backpressure. */
10
+ export async function serveMCPStdio(server, input, output) {
11
+ const decoder = new StringDecoder('utf8');
12
+ let pending = '';
13
+ let initialized = false;
14
+ let ready = false;
15
+ // Node emits a stream error after invoking a failed write callback. Keep a
16
+ // lifetime listener until those queued events settle, even after send rejects.
17
+ const consumeOutputError = () => { };
18
+ output.on('error', consumeOutputError);
19
+ const send = async (message) => {
20
+ let payload = JSON.stringify(message) + '\n';
21
+ if (Buffer.byteLength(payload) > MAX_RESPONSE_BYTES) {
22
+ payload = JSON.stringify({ jsonrpc: '2.0', id: message.id ?? null,
23
+ error: { code: -32001, message: 'Result exceeds 4 MiB. Request fewer memories or a smaller context.' } }) + '\n';
24
+ }
25
+ await new Promise((resolve, reject) => {
26
+ if (output.destroyed || output.writableEnded)
27
+ return reject(new Error('MCP output is closed.'));
28
+ const timeout = setTimeout(() => finish(new Error('MCP output write timed out.')), 30000);
29
+ const closed = () => finish(new Error('MCP output closed before delivery.'));
30
+ const finish = (error) => {
31
+ clearTimeout(timeout);
32
+ output.off('close', closed);
33
+ output.off('error', finish);
34
+ error ? reject(error) : resolve();
35
+ };
36
+ output.once('close', closed);
37
+ output.once('error', finish);
38
+ output.write(payload, finish);
39
+ });
40
+ };
41
+ const error = (id, code, message) => send({ jsonrpc: '2.0', id: id ?? null, error: { code, message } });
42
+ const dispatch = async (line) => {
43
+ if (!line.trim())
44
+ return;
45
+ if (Buffer.byteLength(line) > MAX_MESSAGE_BYTES)
46
+ throw new Error('MCP message exceeds 1 MiB.');
47
+ let value;
48
+ try {
49
+ value = JSON.parse(line);
50
+ }
51
+ catch {
52
+ await error(null, -32700, 'Invalid JSON.');
53
+ return;
54
+ }
55
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
56
+ await error(null, -32600, 'Expected one JSON-RPC message.');
57
+ return;
58
+ }
59
+ const request = value;
60
+ const valid = request.jsonrpc === '2.0' && typeof request.method === 'string' &&
61
+ (request.id === undefined || typeof request.id === 'string' || (typeof request.id === 'number' && Number.isFinite(request.id))) &&
62
+ (request.params === undefined || (request.params !== null && typeof request.params === 'object' && !Array.isArray(request.params)));
63
+ if (!valid) {
64
+ await error(null, -32600, 'Invalid JSON-RPC request.');
65
+ return;
66
+ }
67
+ // Notifications never receive responses and never dispatch mutating tool calls.
68
+ if (request.id === undefined) {
69
+ if (request.method === 'notifications/initialized' && initialized)
70
+ ready = true;
71
+ return;
72
+ }
73
+ if (request.method === 'initialize') {
74
+ if (initialized) {
75
+ await error(request.id, -32600, 'Already initialized.');
76
+ return;
77
+ }
78
+ if (typeof request.params?.protocolVersion !== 'string' || !record(request.params.clientInfo) || !record(request.params.capabilities) ||
79
+ typeof request.params.clientInfo.name !== 'string' || !request.params.clientInfo.name.trim() ||
80
+ typeof request.params.clientInfo.version !== 'string' || !request.params.clientInfo.version.trim()) {
81
+ await error(request.id, -32602, 'Initialization requires protocolVersion, clientInfo, and capabilities.');
82
+ return;
83
+ }
84
+ const response = await server.handleRequest(request);
85
+ if (!response.error)
86
+ initialized = true;
87
+ await send(response);
88
+ return;
89
+ }
90
+ if (request.method !== 'ping' && !ready) {
91
+ await error(request.id, -32002, 'Initialize and send notifications/initialized before using tools.');
92
+ return;
93
+ }
94
+ const response = await server.handleRequest(request);
95
+ await send(response);
96
+ };
97
+ try {
98
+ for await (const chunk of input) {
99
+ pending += decoder.write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
100
+ let newline;
101
+ while ((newline = pending.indexOf('\n')) >= 0) {
102
+ const line = pending.slice(0, newline);
103
+ pending = pending.slice(newline + 1);
104
+ await dispatch(line);
105
+ }
106
+ if (Buffer.byteLength(pending) > MAX_MESSAGE_BYTES)
107
+ throw new Error('MCP message exceeds 1 MiB.');
108
+ }
109
+ pending += decoder.end();
110
+ if (pending.trim())
111
+ await error(null, -32700, 'Connection ended before the message newline.');
112
+ }
113
+ finally {
114
+ await new Promise(resolve => setImmediate(resolve));
115
+ output.off('error', consumeOutputError);
116
+ }
117
+ }
118
+ /** Provider-free entry point: no orchestrator, client-profile scan, or config merge. */
119
+ export async function runMemoryMCPStdio(options = {}) {
120
+ if (options.projectId !== undefined && !/^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/.test(options.projectId))
121
+ throw new TypeError('Project ID must be a bounded identifier.');
122
+ const brain = getGlobalBrain(options.dbPath ? { dbPath: path.resolve(options.dbPath) } : undefined);
123
+ const settings = new FeatureSettingsStore({ filePath: options.settingsPath ??
124
+ (options.dbPath ? path.join(path.dirname(path.resolve(options.dbPath)), 'feature-settings.json') : undefined) });
125
+ const server = new MCPServer(null, { authToken: '', toolProfile: 'memory' }, { brain, settings, projectDir: options.projectDir, projectId: options.projectId });
126
+ try {
127
+ await serveMCPStdio(server, process.stdin, process.stdout);
128
+ }
129
+ finally {
130
+ await server.finishMemorySession();
131
+ await brain.shutdown();
132
+ }
133
+ }
134
+ //# sourceMappingURL=mcp-stdio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-stdio.js","sourceRoot":"","sources":["../../src/brain/mcp-stdio.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,97 @@
1
+ export declare const MEMORY_ANNOTATIONS_FILENAME = "memory-annotations.json";
2
+ export declare const MEMORY_ANNOTATION_LIMITS: Readonly<{
3
+ reviews: 5000;
4
+ links: 5000;
5
+ history: 500;
6
+ note: 1000;
7
+ bytes: number;
8
+ }>;
9
+ export declare const MEMORY_REVIEW_STATES: readonly ["active", "archived", "contested"];
10
+ export declare const MEMORY_RELATION_TYPES: readonly ["related", "supports", "contradicts", "supersedes"];
11
+ export type MemoryReviewState = typeof MEMORY_REVIEW_STATES[number];
12
+ export type MemoryRelationType = typeof MEMORY_RELATION_TYPES[number];
13
+ export type EffectiveMemoryState = MemoryReviewState | 'superseded';
14
+ export interface MemoryReview {
15
+ projectId: string;
16
+ state: MemoryReviewState;
17
+ note: string;
18
+ updatedAt: string;
19
+ }
20
+ export interface MemoryRelation {
21
+ id: string;
22
+ projectId: string;
23
+ fromId: string;
24
+ toId: string;
25
+ type: MemoryRelationType;
26
+ note: string;
27
+ createdAt: string;
28
+ }
29
+ export type MemoryAnnotationCommand = {
30
+ kind: 'review';
31
+ projectId: string;
32
+ memoryId: string;
33
+ state: MemoryReviewState;
34
+ note?: string;
35
+ } | {
36
+ kind: 'link';
37
+ projectId: string;
38
+ fromId: string;
39
+ toId: string;
40
+ type: MemoryRelationType;
41
+ note?: string;
42
+ } | {
43
+ kind: 'unlink';
44
+ linkId: string;
45
+ };
46
+ export interface MemoryAnnotationEvent {
47
+ revision: number;
48
+ at: string;
49
+ projectId: string;
50
+ command: MemoryAnnotationCommand;
51
+ relationId?: string;
52
+ relation?: MemoryRelation;
53
+ previousReview?: MemoryReview;
54
+ }
55
+ export interface MemoryAnnotationSnapshot {
56
+ schemaVersion: 1;
57
+ revision: number;
58
+ updatedAt: string | null;
59
+ reviews: Record<string, MemoryReview>;
60
+ links: MemoryRelation[];
61
+ history: MemoryAnnotationEvent[];
62
+ }
63
+ type MemoryIdentity = {
64
+ id: string;
65
+ projectId: string;
66
+ };
67
+ export interface MemoryAnnotationsOptions {
68
+ filePath?: string;
69
+ lockTimeoutMs?: number;
70
+ resolveMemory: (id: string) => MemoryIdentity | undefined;
71
+ }
72
+ export declare class MemoryAnnotationsError extends Error {
73
+ readonly code: 'ANNOTATIONS_INVALID_COMMAND' | 'ANNOTATIONS_INVALID_STORE' | 'ANNOTATIONS_CONFLICT' | 'ANNOTATIONS_NOT_FOUND' | 'ANNOTATIONS_LOCK_TIMEOUT' | 'ANNOTATIONS_LOCK_FAILED' | 'ANNOTATIONS_READ_FAILED' | 'ANNOTATIONS_WRITE_FAILED';
74
+ constructor(code: 'ANNOTATIONS_INVALID_COMMAND' | 'ANNOTATIONS_INVALID_STORE' | 'ANNOTATIONS_CONFLICT' | 'ANNOTATIONS_NOT_FOUND' | 'ANNOTATIONS_LOCK_TIMEOUT' | 'ANNOTATIONS_LOCK_FAILED' | 'ANNOTATIONS_READ_FAILED' | 'ANNOTATIONS_WRITE_FAILED', message: string);
75
+ }
76
+ /** Use the latest review and replacement relation for the supplied project and memory. */
77
+ export declare function memoryState(snapshot: MemoryAnnotationSnapshot, projectId: string, memoryId: string): EffectiveMemoryState;
78
+ /** Independent local curation state. The original memory store remains schema version 1. */
79
+ export declare class MemoryAnnotationsStore {
80
+ readonly filePath: string;
81
+ private readonly resolveMemory;
82
+ private readonly lockTimeout;
83
+ constructor(options: MemoryAnnotationsOptions);
84
+ get(): MemoryAnnotationSnapshot;
85
+ update(raw: unknown, expectedRevision?: number): Promise<MemoryAnnotationSnapshot>;
86
+ private requireMemory;
87
+ private apply;
88
+ private acquireLock;
89
+ private unlinkOwnedLock;
90
+ private replace;
91
+ }
92
+ export declare function memoryAnnotationsFor(brain: {
93
+ dbPath: string;
94
+ getEntry(id: string): MemoryIdentity | undefined;
95
+ }): MemoryAnnotationsStore;
96
+ export {};
97
+ //# sourceMappingURL=memory-annotations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-annotations.d.ts","sourceRoot":"","sources":["../../src/brain/memory-annotations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,388 @@
1
+ import * as fs from 'node:fs';
2
+ import * as os from 'node:os';
3
+ import * as path from 'node:path';
4
+ import { randomUUID } from 'node:crypto';
5
+ import { setTimeout as delay } from 'node:timers/promises';
6
+ export const MEMORY_ANNOTATIONS_FILENAME = 'memory-annotations.json';
7
+ export const MEMORY_ANNOTATION_LIMITS = Object.freeze({
8
+ reviews: 5000, links: 5000, history: 500, note: 1000, bytes: 16 * 1024 * 1024,
9
+ });
10
+ export const MEMORY_REVIEW_STATES = ['active', 'archived', 'contested'];
11
+ export const MEMORY_RELATION_TYPES = ['related', 'supports', 'contradicts', 'supersedes'];
12
+ export class MemoryAnnotationsError extends Error {
13
+ constructor(code, message) {
14
+ super(message);
15
+ this.code = code;
16
+ this.name = 'MemoryAnnotationsError';
17
+ }
18
+ }
19
+ const LOCK_TIMEOUT = 3000;
20
+ const LOCK_POLL = 20;
21
+ const MAX_IDENTIFIER = 256;
22
+ const FORBIDDEN_IDS = new Set(['__proto__', 'constructor', 'prototype']);
23
+ function errno(error) {
24
+ return error?.code;
25
+ }
26
+ function record(value) {
27
+ if (value === null || typeof value !== 'object')
28
+ return false;
29
+ const prototype = Object.getPrototypeOf(value);
30
+ return prototype === Object.prototype || prototype === null;
31
+ }
32
+ function identifier(value) {
33
+ return typeof value === 'string' && value.length > 0 && value.length <= MAX_IDENTIFIER &&
34
+ value === value.trim() && !/[\u0000-\u001f\u007f]/.test(value) && !FORBIDDEN_IDS.has(value);
35
+ }
36
+ function note(value) {
37
+ return typeof value === 'string' && value.length <= MEMORY_ANNOTATION_LIMITS.note;
38
+ }
39
+ function instant(value) {
40
+ if (typeof value !== 'string')
41
+ return false;
42
+ const time = Date.parse(value);
43
+ return Number.isFinite(time) && new Date(time).toISOString() === value;
44
+ }
45
+ function reviewState(value) {
46
+ return MEMORY_REVIEW_STATES.some(state => state === value);
47
+ }
48
+ function relationType(value) {
49
+ return MEMORY_RELATION_TYPES.some(type => type === value);
50
+ }
51
+ function keysOnly(input, allowed) {
52
+ return Object.keys(input).every(key => allowed.includes(key));
53
+ }
54
+ function parseCommand(value) {
55
+ if (!record(value))
56
+ return undefined;
57
+ const raw = { ...value };
58
+ if (raw.kind === 'unlink' && identifier(raw.linkId) && keysOnly(raw, ['kind', 'linkId'])) {
59
+ return { kind: 'unlink', linkId: raw.linkId };
60
+ }
61
+ if (!identifier(raw.projectId) || (raw.note !== undefined && !note(raw.note)))
62
+ return undefined;
63
+ const reviewNote = typeof raw.note === 'string' ? raw.note.trim() : '';
64
+ if (raw.kind === 'review' && identifier(raw.memoryId) && reviewState(raw.state) &&
65
+ keysOnly(raw, ['kind', 'projectId', 'memoryId', 'state', 'note'])) {
66
+ return { kind: 'review', projectId: raw.projectId, memoryId: raw.memoryId, state: raw.state, note: reviewNote };
67
+ }
68
+ if (raw.kind === 'link' && identifier(raw.fromId) && identifier(raw.toId) && relationType(raw.type) &&
69
+ raw.fromId !== raw.toId && keysOnly(raw, ['kind', 'projectId', 'fromId', 'toId', 'type', 'note'])) {
70
+ return {
71
+ kind: 'link', projectId: raw.projectId, fromId: raw.fromId, toId: raw.toId,
72
+ type: raw.type, note: reviewNote,
73
+ };
74
+ }
75
+ return undefined;
76
+ }
77
+ function key(projectId, id) {
78
+ return JSON.stringify([projectId, id]);
79
+ }
80
+ function relationKey(link) {
81
+ const symmetric = link.type === 'related' || link.type === 'contradicts';
82
+ const endpoints = symmetric ? [link.fromId, link.toId].sort() : [link.fromId, link.toId];
83
+ return JSON.stringify([link.projectId, link.type, ...endpoints]);
84
+ }
85
+ function graphProblem(links) {
86
+ const ids = new Set();
87
+ const relationKeys = new Set();
88
+ const replacementOf = new Map();
89
+ for (const link of links) {
90
+ const relation = relationKey(link);
91
+ if (ids.has(link.id) || relationKeys.has(relation))
92
+ return 'This memory relation already exists.';
93
+ ids.add(link.id);
94
+ relationKeys.add(relation);
95
+ if (link.type !== 'supersedes')
96
+ continue;
97
+ const target = key(link.projectId, link.toId);
98
+ if (replacementOf.has(target))
99
+ return 'This memory already has a replacement. Remove that relation first.';
100
+ replacementOf.set(target, key(link.projectId, link.fromId));
101
+ }
102
+ const complete = new Set();
103
+ for (const start of replacementOf.keys()) {
104
+ const seen = new Set();
105
+ for (let current = start; current !== undefined && !complete.has(current); current = replacementOf.get(current)) {
106
+ if (seen.has(current))
107
+ return 'A supersession chain cannot contain a cycle.';
108
+ seen.add(current);
109
+ }
110
+ for (const id of seen)
111
+ complete.add(id);
112
+ }
113
+ return undefined;
114
+ }
115
+ function validReview(value) {
116
+ return record(value) && keysOnly(value, ['projectId', 'state', 'note', 'updatedAt']) &&
117
+ identifier(value.projectId) && reviewState(value.state) && note(value.note) && instant(value.updatedAt);
118
+ }
119
+ function validLink(value) {
120
+ return record(value) && keysOnly(value, ['id', 'projectId', 'fromId', 'toId', 'type', 'note', 'createdAt']) &&
121
+ identifier(value.id) && identifier(value.projectId) &&
122
+ identifier(value.fromId) && identifier(value.toId) && value.fromId !== value.toId &&
123
+ relationType(value.type) && note(value.note) && instant(value.createdAt);
124
+ }
125
+ function validHistory(history, revision) {
126
+ if (!Array.isArray(history) || history.length !== Math.min(revision, MEMORY_ANNOTATION_LIMITS.history))
127
+ return false;
128
+ const first = revision - history.length + 1;
129
+ return history.every((event, index) => {
130
+ if (!record(event) || !keysOnly(event, ['revision', 'at', 'projectId', 'command', 'relationId', 'relation', 'previousReview']) ||
131
+ event.revision !== first + index || !instant(event.at) || !identifier(event.projectId))
132
+ return false;
133
+ const command = parseCommand(event.command);
134
+ if (!command)
135
+ return false;
136
+ if (command.kind === 'review') {
137
+ return command.projectId === event.projectId && event.relationId === undefined && event.relation === undefined &&
138
+ (event.previousReview === undefined || (validReview(event.previousReview) && event.previousReview.projectId === event.projectId));
139
+ }
140
+ if (event.previousReview !== undefined || !validLink(event.relation) ||
141
+ event.relation.id !== event.relationId || event.relation.projectId !== event.projectId)
142
+ return false;
143
+ if (command.kind === 'unlink')
144
+ return event.relationId === command.linkId;
145
+ return command.projectId === event.projectId && command.type === event.relation.type &&
146
+ command.fromId === event.relation.fromId && command.toId === event.relation.toId;
147
+ });
148
+ }
149
+ function emptySnapshot() {
150
+ return { schemaVersion: 1, revision: 0, updatedAt: null, reviews: {}, links: [], history: [] };
151
+ }
152
+ function parseSnapshot(raw) {
153
+ try {
154
+ const data = JSON.parse(raw);
155
+ if (record(data) && keysOnly(data, ['schemaVersion', 'revision', 'updatedAt', 'reviews', 'links', 'history']) &&
156
+ data.schemaVersion === 1 && typeof data.revision === 'number' &&
157
+ Number.isSafeInteger(data.revision) && data.revision >= 1 && instant(data.updatedAt) &&
158
+ record(data.reviews) && Object.keys(data.reviews).length <= MEMORY_ANNOTATION_LIMITS.reviews &&
159
+ Object.entries(data.reviews).every(([id, review]) => identifier(id) && validReview(review)) &&
160
+ Array.isArray(data.links) && data.links.length <= MEMORY_ANNOTATION_LIMITS.links &&
161
+ data.links.every(validLink) && !graphProblem(data.links) && validHistory(data.history, data.revision) &&
162
+ data.history[data.history.length - 1].at === data.updatedAt) {
163
+ return {
164
+ schemaVersion: 1, revision: data.revision, updatedAt: data.updatedAt,
165
+ reviews: data.reviews, links: data.links, history: data.history,
166
+ };
167
+ }
168
+ }
169
+ catch { /* Preserve store bytes and report the schema error without content. */ }
170
+ throw new MemoryAnnotationsError('ANNOTATIONS_INVALID_STORE', 'Memory annotations are malformed or use an unsupported schema.');
171
+ }
172
+ /** Use the latest review and replacement relation for the supplied project and memory. */
173
+ export function memoryState(snapshot, projectId, memoryId) {
174
+ if (snapshot.links.some(link => link.type === 'supersedes' && link.projectId === projectId && link.toId === memoryId)) {
175
+ return 'superseded';
176
+ }
177
+ const review = snapshot.reviews[memoryId];
178
+ return review?.projectId === projectId ? review.state : 'active';
179
+ }
180
+ /** Independent local curation state. The original memory store remains schema version 1. */
181
+ export class MemoryAnnotationsStore {
182
+ constructor(options) {
183
+ this.filePath = path.resolve(options.filePath ?? path.join(os.homedir(), '.shadow-brain', MEMORY_ANNOTATIONS_FILENAME));
184
+ this.resolveMemory = options.resolveMemory;
185
+ this.lockTimeout = typeof options.lockTimeoutMs === 'number' && Number.isFinite(options.lockTimeoutMs)
186
+ ? Math.max(0, Math.min(60000, options.lockTimeoutMs))
187
+ : LOCK_TIMEOUT;
188
+ }
189
+ get() {
190
+ let raw;
191
+ try {
192
+ const stat = fs.lstatSync(this.filePath, { throwIfNoEntry: false });
193
+ if (!stat)
194
+ return emptySnapshot();
195
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1) {
196
+ throw new MemoryAnnotationsError('ANNOTATIONS_INVALID_STORE', 'Memory annotations must be a regular local file.');
197
+ }
198
+ if (stat.size > MEMORY_ANNOTATION_LIMITS.bytes) {
199
+ throw new MemoryAnnotationsError('ANNOTATIONS_INVALID_STORE', 'Memory annotation file exceeds its size limit.');
200
+ }
201
+ const fd = fs.openSync(this.filePath, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
202
+ try {
203
+ const current = fs.fstatSync(fd);
204
+ if (!current.isFile() || current.nlink !== 1 || current.size > MEMORY_ANNOTATION_LIMITS.bytes) {
205
+ throw new MemoryAnnotationsError('ANNOTATIONS_INVALID_STORE', 'Memory annotation file changed or exceeds its size limit.');
206
+ }
207
+ raw = fs.readFileSync(fd, 'utf8');
208
+ }
209
+ finally {
210
+ fs.closeSync(fd);
211
+ }
212
+ }
213
+ catch (error) {
214
+ if (error instanceof MemoryAnnotationsError)
215
+ throw error;
216
+ throw new MemoryAnnotationsError('ANNOTATIONS_READ_FAILED', 'Could not read memory annotations.');
217
+ }
218
+ return parseSnapshot(raw);
219
+ }
220
+ async update(raw, expectedRevision) {
221
+ const command = parseCommand(raw);
222
+ if (!command || (expectedRevision !== undefined &&
223
+ (!Number.isSafeInteger(expectedRevision) || expectedRevision < 0))) {
224
+ throw new MemoryAnnotationsError('ANNOTATIONS_INVALID_COMMAND', 'Invalid memory annotation command or revision.');
225
+ }
226
+ try {
227
+ fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
228
+ }
229
+ catch {
230
+ throw new MemoryAnnotationsError('ANNOTATIONS_WRITE_FAILED', 'Could not open the memory annotation directory.');
231
+ }
232
+ const release = await this.acquireLock();
233
+ try {
234
+ const snapshot = this.get();
235
+ if (expectedRevision !== undefined && expectedRevision !== snapshot.revision) {
236
+ throw new MemoryAnnotationsError('ANNOTATIONS_CONFLICT', 'Memory annotations changed. Refresh and retry the update.');
237
+ }
238
+ if (snapshot.revision === Number.MAX_SAFE_INTEGER) {
239
+ throw new MemoryAnnotationsError('ANNOTATIONS_INVALID_STORE', 'Memory annotation revision has reached its limit.');
240
+ }
241
+ const at = new Date().toISOString();
242
+ const revision = snapshot.revision + 1;
243
+ const event = this.apply(snapshot, command, revision, at);
244
+ snapshot.revision = revision;
245
+ snapshot.updatedAt = at;
246
+ snapshot.history = [...snapshot.history, event].slice(-MEMORY_ANNOTATION_LIMITS.history);
247
+ this.replace(snapshot);
248
+ return snapshot;
249
+ }
250
+ finally {
251
+ release();
252
+ }
253
+ }
254
+ requireMemory(id, projectId) {
255
+ const memory = this.resolveMemory(id);
256
+ if (memory?.id !== id || memory.projectId !== projectId) {
257
+ throw new MemoryAnnotationsError('ANNOTATIONS_NOT_FOUND', 'Memory was not found in the selected project.');
258
+ }
259
+ }
260
+ apply(snapshot, command, revision, at) {
261
+ if (command.kind === 'unlink') {
262
+ const index = snapshot.links.findIndex(link => link.id === command.linkId);
263
+ if (index < 0)
264
+ throw new MemoryAnnotationsError('ANNOTATIONS_NOT_FOUND', 'Memory relation was not found.');
265
+ const [removed] = snapshot.links.splice(index, 1);
266
+ return { command, revision, at, projectId: removed.projectId, relationId: removed.id, relation: removed };
267
+ }
268
+ const { projectId } = command;
269
+ if (command.kind === 'review') {
270
+ this.requireMemory(command.memoryId, projectId);
271
+ const previous = snapshot.reviews[command.memoryId];
272
+ if (previous && previous.projectId !== projectId) {
273
+ throw new MemoryAnnotationsError('ANNOTATIONS_CONFLICT', 'Memory identity no longer matches its recorded project.');
274
+ }
275
+ if (!previous && Object.keys(snapshot.reviews).length >= MEMORY_ANNOTATION_LIMITS.reviews) {
276
+ throw new MemoryAnnotationsError('ANNOTATIONS_INVALID_COMMAND', 'Memory review storage limit reached.');
277
+ }
278
+ snapshot.reviews[command.memoryId] = { projectId, state: command.state, note: command.note ?? '', updatedAt: at };
279
+ return { command, revision, at, projectId, ...(previous ? { previousReview: previous } : {}) };
280
+ }
281
+ this.requireMemory(command.fromId, projectId);
282
+ this.requireMemory(command.toId, projectId);
283
+ if (snapshot.links.length >= MEMORY_ANNOTATION_LIMITS.links) {
284
+ throw new MemoryAnnotationsError('ANNOTATIONS_INVALID_COMMAND', 'Memory relation storage limit reached.');
285
+ }
286
+ const relation = {
287
+ id: randomUUID(), projectId, fromId: command.fromId, toId: command.toId,
288
+ type: command.type, note: command.note ?? '', createdAt: at,
289
+ };
290
+ snapshot.links.push(relation);
291
+ const problem = graphProblem(snapshot.links);
292
+ if (problem)
293
+ throw new MemoryAnnotationsError('ANNOTATIONS_INVALID_COMMAND', problem);
294
+ return { command, revision, at, projectId, relationId: relation.id, relation };
295
+ }
296
+ async acquireLock() {
297
+ const lock = this.filePath + '.lock';
298
+ const deadline = performance.now() + this.lockTimeout;
299
+ for (;;) {
300
+ let fd;
301
+ try {
302
+ fd = fs.openSync(lock, 'wx', 0o600);
303
+ }
304
+ catch (error) {
305
+ if (errno(error) !== 'EEXIST')
306
+ throw new MemoryAnnotationsError('ANNOTATIONS_LOCK_FAILED', 'Could not acquire the memory annotation lock.');
307
+ const remaining = deadline - performance.now();
308
+ if (remaining <= 0)
309
+ break;
310
+ await delay(Math.min(remaining, LOCK_POLL));
311
+ if (performance.now() >= deadline)
312
+ break;
313
+ continue;
314
+ }
315
+ let owner;
316
+ try {
317
+ owner = fs.fstatSync(fd, { bigint: true });
318
+ fs.writeFileSync(fd, JSON.stringify({ pid: process.pid, acquiredAt: new Date().toISOString() }));
319
+ fs.closeSync(fd);
320
+ }
321
+ catch {
322
+ try {
323
+ fs.closeSync(fd);
324
+ }
325
+ catch { /* retain the original lock failure */ }
326
+ try {
327
+ if (owner)
328
+ this.unlinkOwnedLock(lock, owner);
329
+ }
330
+ catch { /* preserve an unfamiliar lock */ }
331
+ throw new MemoryAnnotationsError('ANNOTATIONS_LOCK_FAILED', 'Could not initialize the memory annotation lock.');
332
+ }
333
+ const owned = owner;
334
+ return () => this.unlinkOwnedLock(lock, owned);
335
+ }
336
+ throw new MemoryAnnotationsError('ANNOTATIONS_LOCK_TIMEOUT', 'Timed out waiting for the memory annotation lock.');
337
+ }
338
+ unlinkOwnedLock(lock, owner) {
339
+ try {
340
+ const current = fs.lstatSync(lock, { bigint: true, throwIfNoEntry: false });
341
+ if (current?.dev !== owner.dev || current.ino !== owner.ino)
342
+ throw new Error('Lock ownership changed.');
343
+ fs.unlinkSync(lock);
344
+ }
345
+ catch {
346
+ throw new MemoryAnnotationsError('ANNOTATIONS_LOCK_FAILED', 'Could not release the memory annotation lock.');
347
+ }
348
+ }
349
+ replace(snapshot) {
350
+ const payload = JSON.stringify(snapshot, null, 2) + '\n';
351
+ if (Buffer.byteLength(payload) > MEMORY_ANNOTATION_LIMITS.bytes) {
352
+ throw new MemoryAnnotationsError('ANNOTATIONS_INVALID_COMMAND', 'Memory annotation storage limit reached.');
353
+ }
354
+ const temporary = this.filePath + '.' + randomUUID() + '.tmp';
355
+ let created = false;
356
+ try {
357
+ const fd = fs.openSync(temporary, 'wx', 0o600);
358
+ created = true;
359
+ try {
360
+ fs.writeFileSync(fd, payload);
361
+ fs.fsyncSync(fd);
362
+ }
363
+ finally {
364
+ fs.closeSync(fd);
365
+ }
366
+ fs.renameSync(temporary, this.filePath);
367
+ created = false;
368
+ }
369
+ catch {
370
+ throw new MemoryAnnotationsError('ANNOTATIONS_WRITE_FAILED', 'Could not save memory annotations.');
371
+ }
372
+ finally {
373
+ if (created) {
374
+ try {
375
+ fs.unlinkSync(temporary);
376
+ }
377
+ catch { /* retain the original write failure */ }
378
+ }
379
+ }
380
+ }
381
+ }
382
+ export function memoryAnnotationsFor(brain) {
383
+ return new MemoryAnnotationsStore({
384
+ filePath: path.join(path.dirname(brain.dbPath), MEMORY_ANNOTATIONS_FILENAME),
385
+ resolveMemory: id => brain.getEntry(id),
386
+ });
387
+ }
388
+ //# sourceMappingURL=memory-annotations.js.map