@lumenflow/memory 1.0.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.
Files changed (55) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +181 -0
  3. package/dist/index.d.ts +16 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +16 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/mem-checkpoint-core.d.ts +91 -0
  8. package/dist/mem-checkpoint-core.d.ts.map +1 -0
  9. package/dist/mem-checkpoint-core.js +175 -0
  10. package/dist/mem-checkpoint-core.js.map +1 -0
  11. package/dist/mem-cleanup-core.d.ts +202 -0
  12. package/dist/mem-cleanup-core.d.ts.map +1 -0
  13. package/dist/mem-cleanup-core.js +364 -0
  14. package/dist/mem-cleanup-core.js.map +1 -0
  15. package/dist/mem-create-core.d.ts +93 -0
  16. package/dist/mem-create-core.d.ts.map +1 -0
  17. package/dist/mem-create-core.js +369 -0
  18. package/dist/mem-create-core.js.map +1 -0
  19. package/dist/mem-id.d.ts +91 -0
  20. package/dist/mem-id.d.ts.map +1 -0
  21. package/dist/mem-id.js +136 -0
  22. package/dist/mem-id.js.map +1 -0
  23. package/dist/mem-init-core.d.ts +91 -0
  24. package/dist/mem-init-core.d.ts.map +1 -0
  25. package/dist/mem-init-core.js +138 -0
  26. package/dist/mem-init-core.js.map +1 -0
  27. package/dist/mem-ready-core.d.ts +56 -0
  28. package/dist/mem-ready-core.d.ts.map +1 -0
  29. package/dist/mem-ready-core.js +232 -0
  30. package/dist/mem-ready-core.js.map +1 -0
  31. package/dist/mem-signal-core.d.ts +132 -0
  32. package/dist/mem-signal-core.d.ts.map +1 -0
  33. package/dist/mem-signal-core.js +249 -0
  34. package/dist/mem-signal-core.js.map +1 -0
  35. package/dist/mem-start-core.d.ts +76 -0
  36. package/dist/mem-start-core.d.ts.map +1 -0
  37. package/dist/mem-start-core.js +133 -0
  38. package/dist/mem-start-core.js.map +1 -0
  39. package/dist/mem-summarize-core.d.ts +105 -0
  40. package/dist/mem-summarize-core.d.ts.map +1 -0
  41. package/dist/mem-summarize-core.js +263 -0
  42. package/dist/mem-summarize-core.js.map +1 -0
  43. package/dist/mem-triage-core.d.ts +127 -0
  44. package/dist/mem-triage-core.d.ts.map +1 -0
  45. package/dist/mem-triage-core.js +332 -0
  46. package/dist/mem-triage-core.js.map +1 -0
  47. package/dist/memory-schema.d.ts +150 -0
  48. package/dist/memory-schema.d.ts.map +1 -0
  49. package/dist/memory-schema.js +149 -0
  50. package/dist/memory-schema.js.map +1 -0
  51. package/dist/memory-store.d.ts +108 -0
  52. package/dist/memory-store.d.ts.map +1 -0
  53. package/dist/memory-store.js +234 -0
  54. package/dist/memory-store.js.map +1 -0
  55. package/package.json +76 -0
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Memory Init Core (WU-1464)
3
+ *
4
+ * Core logic for initializing memory layer in a repository.
5
+ * Creates .beacon/memory/ directory with empty memory.jsonl and config.yaml.
6
+ *
7
+ * @see {@link tools/__tests__/mem-init.test.mjs} - Tests
8
+ * @see {@link tools/lib/memory-store.mjs} - Memory store operations
9
+ * @see {@link tools/lib/memory-schema.mjs} - Memory schema definitions
10
+ */
11
+ /**
12
+ * Memory layer file/directory paths
13
+ */
14
+ export declare const MEMORY_PATHS: {
15
+ /** Memory directory relative to project root */
16
+ MEMORY_DIR: string;
17
+ /** Memory JSONL file name */
18
+ MEMORY_FILE: string;
19
+ /** Config YAML file name */
20
+ CONFIG_FILE: string;
21
+ };
22
+ /**
23
+ * Default memory layer configuration
24
+ *
25
+ * Retention values are in seconds:
26
+ * - ephemeral: 0 (immediate discard)
27
+ * - session: 3600 (1 hour)
28
+ * - wu: 604800 (7 days)
29
+ * - project: -1 (never expire)
30
+ */
31
+ export declare const DEFAULT_CONFIG: {
32
+ /** Config schema version */
33
+ version: number;
34
+ /** Retention policy for each lifecycle */
35
+ retention: {
36
+ /** Ephemeral nodes are discarded immediately after use */
37
+ ephemeral: number;
38
+ /** Session nodes expire after 1 hour */
39
+ session: number;
40
+ /** WU nodes expire after 7 days */
41
+ wu: number;
42
+ /** Project nodes never expire (-1 = infinite) */
43
+ project: number;
44
+ };
45
+ };
46
+ /**
47
+ * Memory initialization paths
48
+ */
49
+ interface InitMemoryPaths {
50
+ memoryDir: string;
51
+ memoryJsonl: string;
52
+ configYaml: string;
53
+ }
54
+ /**
55
+ * Memory initialization created flags
56
+ */
57
+ interface InitMemoryCreated {
58
+ directory: boolean;
59
+ memoryJsonl: boolean;
60
+ configYaml: boolean;
61
+ }
62
+ /**
63
+ * Memory initialization result
64
+ */
65
+ export interface InitMemoryResult {
66
+ success: boolean;
67
+ alreadyInitialized: boolean;
68
+ paths: InitMemoryPaths;
69
+ created: InitMemoryCreated;
70
+ }
71
+ /**
72
+ * Initialize memory layer in a repository.
73
+ *
74
+ * Creates:
75
+ * - .beacon/memory/ directory
76
+ * - memory.jsonl (empty if not exists)
77
+ * - config.yaml (default settings if not exists)
78
+ *
79
+ * Idempotent: Running multiple times does not corrupt existing data.
80
+ *
81
+ * @param baseDir - Base directory of the repository
82
+ * @returns Result object with success, paths, and created flags
83
+ *
84
+ * @example
85
+ * const result = await initMemory(process.cwd());
86
+ * if (result.success) {
87
+ * console.log('Memory initialized at:', result.paths.memoryDir);
88
+ * }
89
+ */
90
+ export declare function initMemory(baseDir: string): Promise<InitMemoryResult>;
91
+ export {};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mem-init-core.d.ts","sourceRoot":"","sources":["../src/mem-init-core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH;;GAEG;AACH,eAAO,MAAM,YAAY;IACvB,gDAAgD;;IAGhD,6BAA6B;;IAG7B,4BAA4B;;CAE7B,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc;IACzB,4BAA4B;;IAG5B,0CAA0C;;QAExC,0DAA0D;;QAE1D,wCAAwC;;QAExC,mCAAmC;;QAEnC,iDAAiD;;;CAGpD,CAAC;AA2BF;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,UAAU,CAAC,OAAO,KAAA;;;;;;;;;;;;;GAqDvC"}
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Memory Init Core (WU-1464)
3
+ *
4
+ * Core logic for initializing memory layer in a repository.
5
+ * Creates .beacon/memory/ directory with empty memory.jsonl and config.yaml.
6
+ *
7
+ * @see {@link tools/__tests__/mem-init.test.mjs} - Tests
8
+ * @see {@link tools/lib/memory-store.mjs} - Memory store operations
9
+ * @see {@link tools/lib/memory-schema.mjs} - Memory schema definitions
10
+ */
11
+ import fs from 'node:fs/promises';
12
+ import path from 'node:path';
13
+ import yaml from 'yaml';
14
+ /**
15
+ * Memory layer file/directory paths
16
+ */
17
+ export const MEMORY_PATHS = {
18
+ /** Memory directory relative to project root */
19
+ MEMORY_DIR: '.beacon/memory',
20
+ /** Memory JSONL file name */
21
+ MEMORY_FILE: 'memory.jsonl',
22
+ /** Config YAML file name */
23
+ CONFIG_FILE: 'config.yaml',
24
+ };
25
+ /**
26
+ * Default memory layer configuration
27
+ *
28
+ * Retention values are in seconds:
29
+ * - ephemeral: 0 (immediate discard)
30
+ * - session: 3600 (1 hour)
31
+ * - wu: 604800 (7 days)
32
+ * - project: -1 (never expire)
33
+ */
34
+ export const DEFAULT_CONFIG = {
35
+ /** Config schema version */
36
+ version: 1,
37
+ /** Retention policy for each lifecycle */
38
+ retention: {
39
+ /** Ephemeral nodes are discarded immediately after use */
40
+ ephemeral: 0,
41
+ /** Session nodes expire after 1 hour */
42
+ session: 3600,
43
+ /** WU nodes expire after 7 days */
44
+ wu: 604800,
45
+ /** Project nodes never expire (-1 = infinite) */
46
+ project: -1,
47
+ },
48
+ };
49
+ /**
50
+ * Check if a file exists
51
+ *
52
+ * @param filePath - Path to check
53
+ * @returns True if file exists
54
+ */
55
+ async function fileExists(filePath) {
56
+ try {
57
+ await fs.access(filePath);
58
+ return true;
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ }
64
+ /**
65
+ * Generate config.yaml content from DEFAULT_CONFIG
66
+ *
67
+ * @returns YAML content string
68
+ */
69
+ function generateConfigYaml() {
70
+ const header = '# Memory layer configuration\n# Generated by pnpm mem:init (WU-1464)\n';
71
+ return header + yaml.stringify(DEFAULT_CONFIG);
72
+ }
73
+ /**
74
+ * Initialize memory layer in a repository.
75
+ *
76
+ * Creates:
77
+ * - .beacon/memory/ directory
78
+ * - memory.jsonl (empty if not exists)
79
+ * - config.yaml (default settings if not exists)
80
+ *
81
+ * Idempotent: Running multiple times does not corrupt existing data.
82
+ *
83
+ * @param baseDir - Base directory of the repository
84
+ * @returns Result object with success, paths, and created flags
85
+ *
86
+ * @example
87
+ * const result = await initMemory(process.cwd());
88
+ * if (result.success) {
89
+ * console.log('Memory initialized at:', result.paths.memoryDir);
90
+ * }
91
+ */
92
+ export async function initMemory(baseDir) {
93
+ const memoryDir = path.join(baseDir, MEMORY_PATHS.MEMORY_DIR);
94
+ const memoryJsonlPath = path.join(memoryDir, MEMORY_PATHS.MEMORY_FILE);
95
+ const configYamlPath = path.join(memoryDir, MEMORY_PATHS.CONFIG_FILE);
96
+ const result = {
97
+ success: false,
98
+ alreadyInitialized: false,
99
+ paths: {
100
+ memoryDir,
101
+ memoryJsonl: memoryJsonlPath,
102
+ configYaml: configYamlPath,
103
+ },
104
+ created: {
105
+ directory: false,
106
+ memoryJsonl: false,
107
+ configYaml: false,
108
+ },
109
+ };
110
+ // Check if already initialized
111
+ const dirExists = await fileExists(memoryDir);
112
+ const memoryExists = await fileExists(memoryJsonlPath);
113
+ const configExists = await fileExists(configYamlPath);
114
+ if (dirExists && memoryExists && configExists) {
115
+ result.alreadyInitialized = true;
116
+ }
117
+ // Create directory if needed
118
+ if (!dirExists) {
119
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool creates .beacon/memory/ directory
120
+ await fs.mkdir(memoryDir, { recursive: true });
121
+ result.created.directory = true;
122
+ }
123
+ // Create empty memory.jsonl if needed
124
+ if (!memoryExists) {
125
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool creates memory.jsonl
126
+ await fs.writeFile(memoryJsonlPath, '', { encoding: 'utf-8' });
127
+ result.created.memoryJsonl = true;
128
+ }
129
+ // Create config.yaml with defaults if needed
130
+ if (!configExists) {
131
+ const configContent = generateConfigYaml();
132
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool creates config.yaml
133
+ await fs.writeFile(configYamlPath, configContent, { encoding: 'utf-8' });
134
+ result.created.configYaml = true;
135
+ }
136
+ result.success = true;
137
+ return result;
138
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mem-init-core.js","sourceRoot":"","sources":["../src/mem-init-core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Memory Ready Core (WU-1468)
3
+ *
4
+ * Deterministic ready-work query for "what next?" oracle.
5
+ * Returns open nodes with no blockers, ordered by priority then createdAt.
6
+ *
7
+ * Ordering algorithm:
8
+ * 1. Priority ASC (P0 first, then P1, P2, P3; nodes without priority last)
9
+ * 2. CreatedAt ASC (oldest first for same priority)
10
+ * 3. ID ASC (stable sort for identical priority and timestamp)
11
+ *
12
+ * A node is "ready" if:
13
+ * - Linked to the specified WU
14
+ * - Not blocked by another node (no `blocks` relationship pointing to it)
15
+ * - No `metadata.blocked_by` array set
16
+ * - Lifecycle is not `ephemeral`
17
+ * - Status is not `closed` (metadata.status !== 'closed')
18
+ *
19
+ * @see {@link tools/mem-ready.mjs} - CLI implementation
20
+ * @see {@link tools/__tests__/mem-ready.test.mjs} - Tests
21
+ */
22
+ import { type MemoryNode } from './memory-schema.js';
23
+ /**
24
+ * Query options for ready nodes
25
+ */
26
+ export interface QueryOptions {
27
+ /** WU ID to query (required) */
28
+ wuId: string;
29
+ /** Filter by node type (optional) */
30
+ type?: string;
31
+ }
32
+ /**
33
+ * Query ready nodes for a WU.
34
+ *
35
+ * Returns unblocked, open nodes linked to the WU in deterministic order:
36
+ * 1. Priority (P0 first, then P1, P2, P3; nodes without priority last)
37
+ * 2. CreatedAt (oldest first for same priority)
38
+ * 3. ID (alphabetical for stable sort)
39
+ *
40
+ * @param baseDir - Base directory containing .beacon/memory
41
+ * @param options - Query options
42
+ * @returns Deterministically ordered ready nodes
43
+ * @throws If WU ID format is invalid or file contains malformed JSON
44
+ *
45
+ * @example
46
+ * const ready = await queryReadyNodes('/path/to/project', { wuId: 'WU-1234' });
47
+ * console.log(`${ready.length} nodes ready for processing`);
48
+ *
49
+ * @example
50
+ * // Filter by type
51
+ * const discoveries = await queryReadyNodes('/path/to/project', {
52
+ * wuId: 'WU-1234',
53
+ * type: 'discovery',
54
+ * });
55
+ */
56
+ export declare function queryReadyNodes(baseDir: string, options: QueryOptions): Promise<MemoryNode[]>;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mem-ready-core.d.ts","sourceRoot":"","sources":["../src/mem-ready-core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAkLH;;;;GAIG;AAEH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,eAAe,CAAC,OAAO,KAAA,EAAE,OAAO,KAAA,gBAwCrD"}
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Memory Ready Core (WU-1468)
3
+ *
4
+ * Deterministic ready-work query for "what next?" oracle.
5
+ * Returns open nodes with no blockers, ordered by priority then createdAt.
6
+ *
7
+ * Ordering algorithm:
8
+ * 1. Priority ASC (P0 first, then P1, P2, P3; nodes without priority last)
9
+ * 2. CreatedAt ASC (oldest first for same priority)
10
+ * 3. ID ASC (stable sort for identical priority and timestamp)
11
+ *
12
+ * A node is "ready" if:
13
+ * - Linked to the specified WU
14
+ * - Not blocked by another node (no `blocks` relationship pointing to it)
15
+ * - No `metadata.blocked_by` array set
16
+ * - Lifecycle is not `ephemeral`
17
+ * - Status is not `closed` (metadata.status !== 'closed')
18
+ *
19
+ * @see {@link tools/mem-ready.mjs} - CLI implementation
20
+ * @see {@link tools/__tests__/mem-ready.test.mjs} - Tests
21
+ */
22
+ import fs from 'node:fs/promises';
23
+ import path from 'node:path';
24
+ import { loadMemory } from './memory-store.js';
25
+ import { MEMORY_PATTERNS } from './memory-schema.js';
26
+ /**
27
+ * Relationships file name
28
+ */
29
+ const RELATIONSHIPS_FILE_NAME = 'relationships.jsonl';
30
+ /**
31
+ * Priority ranking for deterministic ordering.
32
+ * Lower rank = higher priority.
33
+ * P0 is highest priority, nodes without priority are lowest.
34
+ */
35
+ const PRIORITY_RANK = {
36
+ P0: 0,
37
+ P1: 1,
38
+ P2: 2,
39
+ P3: 3,
40
+ };
41
+ /** Default rank for nodes without priority (lowest priority) */
42
+ const DEFAULT_PRIORITY_RANK = 999;
43
+ /**
44
+ * Gets the priority rank for a node.
45
+ * Lower rank = higher priority.
46
+ *
47
+ * @param node - Memory node
48
+ * @returns Priority rank
49
+ */
50
+ function getPriorityRank(node) {
51
+ const priority = node.metadata?.priority;
52
+ if (!priority) {
53
+ return DEFAULT_PRIORITY_RANK;
54
+ }
55
+ return PRIORITY_RANK[priority] ?? DEFAULT_PRIORITY_RANK;
56
+ }
57
+ /**
58
+ * Comparator for deterministic ordering: priority first, then createdAt, then ID.
59
+ *
60
+ * @param a - First node
61
+ * @param b - Second node
62
+ * @returns Comparison result (-1, 0, 1)
63
+ */
64
+ function compareNodes(a, b) {
65
+ // Primary: sort by priority (lower rank first)
66
+ const priorityDiff = getPriorityRank(a) - getPriorityRank(b);
67
+ if (priorityDiff !== 0) {
68
+ return priorityDiff;
69
+ }
70
+ // Secondary: sort by created_at (oldest first)
71
+ const aTime = new Date(a.created_at).getTime();
72
+ const bTime = new Date(b.created_at).getTime();
73
+ if (aTime !== bTime) {
74
+ return aTime - bTime;
75
+ }
76
+ // Tertiary: stable sort by ID for identical priority and timestamp
77
+ return a.id.localeCompare(b.id);
78
+ }
79
+ /**
80
+ * Load relationships from relationships.jsonl
81
+ *
82
+ * @param memoryDir - Memory directory path
83
+ * @returns Array of relationship objects
84
+ */
85
+ async function loadRelationships(memoryDir) {
86
+ const filePath = path.join(memoryDir, RELATIONSHIPS_FILE_NAME);
87
+ try {
88
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool reads known file path
89
+ const content = await fs.readFile(filePath, { encoding: 'utf-8' });
90
+ const lines = content.split('\n');
91
+ const relationships = [];
92
+ for (const line of lines) {
93
+ const trimmed = line.trim();
94
+ if (!trimmed)
95
+ continue;
96
+ try {
97
+ relationships.push(JSON.parse(trimmed));
98
+ }
99
+ catch {
100
+ // Skip malformed lines in relationships file
101
+ continue;
102
+ }
103
+ }
104
+ return relationships;
105
+ }
106
+ catch (err) {
107
+ const error = err;
108
+ if (error.code === 'ENOENT') {
109
+ // File doesn't exist - no relationships
110
+ return [];
111
+ }
112
+ throw error;
113
+ }
114
+ }
115
+ /**
116
+ * Build a set of node IDs that are blocked by relationships
117
+ *
118
+ * @param relationships - Relationship objects
119
+ * @returns Set of blocked node IDs
120
+ */
121
+ function buildBlockedSet(relationships) {
122
+ const blocked = new Set();
123
+ for (const rel of relationships) {
124
+ if (rel.type === 'blocks') {
125
+ // The `to_id` is the blocked node
126
+ blocked.add(rel.to_id);
127
+ }
128
+ }
129
+ return blocked;
130
+ }
131
+ /**
132
+ * Check if a node is blocked
133
+ *
134
+ * @param node - Memory node
135
+ * @param blockedByRelationships - Set of IDs blocked by relationships
136
+ * @returns True if node is blocked
137
+ */
138
+ function isBlocked(node, blockedByRelationships) {
139
+ // Check if blocked by relationship
140
+ if (blockedByRelationships.has(node.id)) {
141
+ return true;
142
+ }
143
+ // Check if blocked by metadata
144
+ const blockedBy = node.metadata?.blocked_by;
145
+ if (Array.isArray(blockedBy) && blockedBy.length > 0) {
146
+ return true;
147
+ }
148
+ return false;
149
+ }
150
+ /**
151
+ * Check if a node is closed (not open for processing)
152
+ *
153
+ * @param node - Memory node
154
+ * @returns True if node is closed
155
+ */
156
+ function isClosed(node) {
157
+ // Ephemeral nodes are considered closed (discarded after use)
158
+ if (node.lifecycle === 'ephemeral') {
159
+ return true;
160
+ }
161
+ // Check explicit closed status in metadata
162
+ if (node.metadata?.status === 'closed') {
163
+ return true;
164
+ }
165
+ return false;
166
+ }
167
+ /**
168
+ * Validate WU ID format
169
+ *
170
+ * @param wuId - WU ID to validate
171
+ * @throws If WU ID format is invalid
172
+ */
173
+ function validateWuId(wuId) {
174
+ if (!MEMORY_PATTERNS.WU_ID.test(wuId)) {
175
+ throw new Error(`Invalid WU ID format: ${wuId}. Expected format: WU-XXX (e.g., WU-1234)`);
176
+ }
177
+ }
178
+ /**
179
+ * Query ready nodes for a WU.
180
+ *
181
+ * Returns unblocked, open nodes linked to the WU in deterministic order:
182
+ * 1. Priority (P0 first, then P1, P2, P3; nodes without priority last)
183
+ * 2. CreatedAt (oldest first for same priority)
184
+ * 3. ID (alphabetical for stable sort)
185
+ *
186
+ * @param baseDir - Base directory containing .beacon/memory
187
+ * @param options - Query options
188
+ * @returns Deterministically ordered ready nodes
189
+ * @throws If WU ID format is invalid or file contains malformed JSON
190
+ *
191
+ * @example
192
+ * const ready = await queryReadyNodes('/path/to/project', { wuId: 'WU-1234' });
193
+ * console.log(`${ready.length} nodes ready for processing`);
194
+ *
195
+ * @example
196
+ * // Filter by type
197
+ * const discoveries = await queryReadyNodes('/path/to/project', {
198
+ * wuId: 'WU-1234',
199
+ * type: 'discovery',
200
+ * });
201
+ */
202
+ export async function queryReadyNodes(baseDir, options) {
203
+ const { wuId, type } = options;
204
+ // Validate WU ID
205
+ validateWuId(wuId);
206
+ const memoryDir = path.join(baseDir, '.beacon', 'memory');
207
+ // Load memory and relationships
208
+ const memory = await loadMemory(memoryDir);
209
+ const relationships = await loadRelationships(memoryDir);
210
+ // Build set of blocked node IDs from relationships
211
+ const blockedByRelationships = buildBlockedSet(relationships);
212
+ // Get nodes for this WU
213
+ const wuNodes = memory.byWu.get(wuId) ?? [];
214
+ // Filter to ready nodes only
215
+ const readyNodes = wuNodes.filter((node) => {
216
+ // Exclude blocked nodes
217
+ if (isBlocked(node, blockedByRelationships)) {
218
+ return false;
219
+ }
220
+ // Exclude closed nodes
221
+ if (isClosed(node)) {
222
+ return false;
223
+ }
224
+ // Apply type filter if specified
225
+ if (type && node.type !== type) {
226
+ return false;
227
+ }
228
+ return true;
229
+ });
230
+ // Sort deterministically
231
+ return readyNodes.sort(compareNodes);
232
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mem-ready-core.js","sourceRoot":"","sources":["../src/mem-ready-core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Memory Signal Core Logic (WU-1473)
3
+ *
4
+ * Core logic for creating coordination signals between parallel agents.
5
+ * Enables sub-100ms agent communication via JSONL append operations.
6
+ *
7
+ * Features:
8
+ * - Append-only writes for sub-100ms performance
9
+ * - WU-scoped signals for focused coordination
10
+ * - Lane-targeted signals for cross-team communication
11
+ * - Read/unread tracking for mem:inbox integration
12
+ *
13
+ * @see {@link tools/mem-signal.mjs} - CLI wrapper
14
+ * @see {@link tools/__tests__/mem-signal.test.mjs} - Tests
15
+ */
16
+ /**
17
+ * Signal file name constant
18
+ */
19
+ export declare const SIGNAL_FILE_NAME = "signals.jsonl";
20
+ /**
21
+ * Signal structure
22
+ */
23
+ export interface Signal {
24
+ /** Unique signal identifier (sig-XXXXXXXX) */
25
+ id: string;
26
+ /** Signal content/message */
27
+ message: string;
28
+ /** ISO 8601 timestamp */
29
+ created_at: string;
30
+ /** Whether signal has been read */
31
+ read: boolean;
32
+ /** Optional WU ID scope */
33
+ wu_id?: string;
34
+ /** Optional target lane */
35
+ lane?: string;
36
+ }
37
+ /**
38
+ * Result of creating a signal
39
+ */
40
+ export interface CreateSignalResult {
41
+ /** Whether signal was created successfully */
42
+ success: boolean;
43
+ /** The created signal object */
44
+ signal: Signal;
45
+ }
46
+ /**
47
+ * Options for creating a signal
48
+ */
49
+ export interface CreateSignalOptions {
50
+ /** Signal message content (required) */
51
+ message: string;
52
+ /** WU ID to scope signal to */
53
+ wuId?: string;
54
+ /** Lane to target signal to */
55
+ lane?: string;
56
+ }
57
+ /**
58
+ * Options for loading signals
59
+ */
60
+ export interface LoadSignalsOptions {
61
+ /** Filter by WU ID */
62
+ wuId?: string;
63
+ /** Filter by lane */
64
+ lane?: string;
65
+ /** Only return unread signals */
66
+ unreadOnly?: boolean;
67
+ /** Only return signals created after this time */
68
+ since?: Date | string;
69
+ }
70
+ /**
71
+ * Result of marking signals as read
72
+ */
73
+ export interface MarkAsReadResult {
74
+ /** Number of signals marked as read */
75
+ markedCount: number;
76
+ }
77
+ /**
78
+ * Creates a coordination signal between parallel agents.
79
+ *
80
+ * Signals are appended to signals.jsonl using append-only writes
81
+ * for sub-100ms performance. Signals can be scoped to a specific
82
+ * WU or targeted at a specific lane.
83
+ *
84
+ * @param {string} baseDir - Project base directory
85
+ * @param {CreateSignalOptions} options - Signal options
86
+ * @returns {Promise<CreateSignalResult>} Result with created signal
87
+ * @throws {Error} If message is missing or WU ID is invalid
88
+ *
89
+ * @example
90
+ * const result = await createSignal('/project', {
91
+ * message: 'Starting feature implementation',
92
+ * wuId: 'WU-1473',
93
+ * lane: 'Operations: Tooling',
94
+ * });
95
+ */
96
+ export declare function createSignal(baseDir: string, options: CreateSignalOptions): Promise<CreateSignalResult>;
97
+ /**
98
+ * Loads signals from the signals file with optional filtering.
99
+ *
100
+ * Signals are returned in chronological order (oldest first).
101
+ * Supports filtering by WU ID, lane, and read status.
102
+ *
103
+ * @param {string} baseDir - Project base directory
104
+ * @param {LoadSignalsOptions} [options={}] - Filter options
105
+ * @returns {Promise<Signal[]>} Array of signals matching filters
106
+ *
107
+ * @example
108
+ * // Load all signals
109
+ * const all = await loadSignals('/project');
110
+ *
111
+ * // Load unread signals for a specific WU
112
+ * const unread = await loadSignals('/project', {
113
+ * wuId: 'WU-1473',
114
+ * unreadOnly: true,
115
+ * });
116
+ */
117
+ export declare function loadSignals(baseDir: string, options?: LoadSignalsOptions): Promise<Signal[]>;
118
+ /**
119
+ * Marks signals as read by updating the signals file.
120
+ *
121
+ * Reads the entire file, updates the read status for matching IDs,
122
+ * and writes back. Only signals that were previously unread are counted.
123
+ *
124
+ * @param baseDir - Project base directory
125
+ * @param signalIds - Array of signal IDs to mark as read
126
+ * @returns Result with count of signals marked
127
+ *
128
+ * @example
129
+ * const result = await markSignalsAsRead('/project', ['sig-abc12345', 'sig-def67890']);
130
+ * console.log(result.markedCount); // 2
131
+ */
132
+ export declare function markSignalsAsRead(baseDir: string, signalIds: string[]): Promise<MarkAsReadResult>;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mem-signal-core.d.ts","sourceRoot":"","sources":["../src/mem-signal-core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH;;GAEG;AACH,eAAO,MAAM,gBAAgB,kBAAkB,CAAC;AA8GhD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,YAAY,CAAC,OAAO,KAAA,EAAE,OAAO,KAAA;;;;;;;;GA4ClD;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,WAAW,CAAC,OAAO,KAAA,EAAE,OAAO,KAAK,gBA2CtD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,KAAA,EAAE,SAAS,KAAA;;GAkCzD"}