coaiajs 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.
Files changed (162) hide show
  1. package/CLAUDE.md +56 -0
  2. package/KINSHIP.md +63 -0
  3. package/README.md +148 -0
  4. package/articles/academic/creative-orientation-vs-problem-solving.md +177 -0
  5. package/articles/academic/jsonl-knowledge-graphs-agent-memory.md +142 -0
  6. package/articles/academic/langfuse-observability-llm-pipelines.md +144 -0
  7. package/articles/academic/medicine-wheel-software-architecture.md +163 -0
  8. package/articles/academic/mmot-autonomous-agents.md +156 -0
  9. package/articles/academic/model-context-protocol-interagent.md +161 -0
  10. package/articles/academic/pde-prompt-decomposition.md +186 -0
  11. package/articles/academic/structural-tension-in-ai-agents.md +134 -0
  12. package/articles/reviews/mcp-protocol-design-review.md +170 -0
  13. package/articles/reviews/observability-ai-systems-review.md +176 -0
  14. package/articles/reviews/prompt-engineering-decomposition-review.md +184 -0
  15. package/articles/surveys/agent-orchestration-survey.md +186 -0
  16. package/articles/surveys/knowledge-graph-storage-survey.md +204 -0
  17. package/articles/surveys/structural-tension-methodology-survey.md +154 -0
  18. package/articles/technical/aws-sdk-v3-polly.md +270 -0
  19. package/articles/technical/commander-cli-framework.md +262 -0
  20. package/articles/technical/dotenv-config-patterns.md +360 -0
  21. package/articles/technical/ioredis-vs-redis.md +142 -0
  22. package/articles/technical/langfuse-js-sdk-vs-rest.md +191 -0
  23. package/articles/technical/mcp-sdk-typescript.md +291 -0
  24. package/articles/technical/octokit-github-api.md +293 -0
  25. package/articles/technical/openai-sdk-modern.md +231 -0
  26. package/articles/technical/yaml-parsing-node.md +266 -0
  27. package/articles/technical/zod-runtime-validation.md +212 -0
  28. package/dist/mcp/config.d.ts +14 -0
  29. package/dist/mcp/config.js +185 -0
  30. package/dist/mcp/server.d.ts +3 -0
  31. package/dist/mcp/server.js +324 -0
  32. package/dist/mcp/tools/coaiapy-tools.d.ts +8 -0
  33. package/dist/mcp/tools/coaiapy-tools.js +326 -0
  34. package/dist/mcp/tools/index.d.ts +3 -0
  35. package/dist/mcp/tools/index.js +3 -0
  36. package/dist/src/audio.d.ts +12 -0
  37. package/dist/src/audio.js +57 -0
  38. package/dist/src/cli-helpers.d.ts +9 -0
  39. package/dist/src/cli-helpers.js +78 -0
  40. package/dist/src/cli.d.ts +3 -0
  41. package/dist/src/cli.js +1086 -0
  42. package/dist/src/config.d.ts +15 -0
  43. package/dist/src/config.js +145 -0
  44. package/dist/src/environment.d.ts +44 -0
  45. package/dist/src/environment.js +146 -0
  46. package/dist/src/github.d.ts +47 -0
  47. package/dist/src/github.js +79 -0
  48. package/dist/src/langfuse/client.d.ts +30 -0
  49. package/dist/src/langfuse/client.js +75 -0
  50. package/dist/src/langfuse/comments.d.ts +16 -0
  51. package/dist/src/langfuse/comments.js +36 -0
  52. package/dist/src/langfuse/datasets.d.ts +19 -0
  53. package/dist/src/langfuse/datasets.js +140 -0
  54. package/dist/src/langfuse/index.d.ts +13 -0
  55. package/dist/src/langfuse/index.js +10 -0
  56. package/dist/src/langfuse/media.d.ts +11 -0
  57. package/dist/src/langfuse/media.js +167 -0
  58. package/dist/src/langfuse/observations.d.ts +18 -0
  59. package/dist/src/langfuse/observations.js +113 -0
  60. package/dist/src/langfuse/prompts.d.ts +14 -0
  61. package/dist/src/langfuse/prompts.js +127 -0
  62. package/dist/src/langfuse/scores.d.ts +54 -0
  63. package/dist/src/langfuse/scores.js +366 -0
  64. package/dist/src/langfuse/traces.d.ts +29 -0
  65. package/dist/src/langfuse/traces.js +225 -0
  66. package/dist/src/llm.d.ts +23 -0
  67. package/dist/src/llm.js +76 -0
  68. package/dist/src/narrative/graph-manager.d.ts +106 -0
  69. package/dist/src/narrative/graph-manager.js +915 -0
  70. package/dist/src/narrative/index.d.ts +9 -0
  71. package/dist/src/narrative/index.js +7 -0
  72. package/dist/src/narrative/markdown-export.d.ts +21 -0
  73. package/dist/src/narrative/markdown-export.js +383 -0
  74. package/dist/src/narrative/tool-definitions.d.ts +21 -0
  75. package/dist/src/narrative/tool-definitions.js +588 -0
  76. package/dist/src/narrative/tool-handlers.d.ts +11 -0
  77. package/dist/src/narrative/tool-handlers.js +462 -0
  78. package/dist/src/narrative/types.d.ts +2 -0
  79. package/dist/src/narrative/types.js +2 -0
  80. package/dist/src/narrative/validation.d.ts +29 -0
  81. package/dist/src/narrative/validation.js +144 -0
  82. package/dist/src/pde/index.d.ts +8 -0
  83. package/dist/src/pde/index.js +8 -0
  84. package/dist/src/pde/mcp-handlers.d.ts +7 -0
  85. package/dist/src/pde/mcp-handlers.js +300 -0
  86. package/dist/src/pde/mcp-tools.d.ts +15 -0
  87. package/dist/src/pde/mcp-tools.js +191 -0
  88. package/dist/src/pde/session-manager.d.ts +32 -0
  89. package/dist/src/pde/session-manager.js +205 -0
  90. package/dist/src/pde/stc-mapper.d.ts +26 -0
  91. package/dist/src/pde/stc-mapper.js +234 -0
  92. package/dist/src/pipeline/index.d.ts +2 -0
  93. package/dist/src/pipeline/index.js +3 -0
  94. package/dist/src/pipeline/template-engine.d.ts +20 -0
  95. package/dist/src/pipeline/template-engine.js +361 -0
  96. package/dist/src/planning/index.d.ts +7 -0
  97. package/dist/src/planning/index.js +7 -0
  98. package/dist/src/planning/mcp-handlers.d.ts +7 -0
  99. package/dist/src/planning/mcp-handlers.js +310 -0
  100. package/dist/src/planning/mcp-tools.d.ts +15 -0
  101. package/dist/src/planning/mcp-tools.js +145 -0
  102. package/dist/src/planning/plan-parser.d.ts +35 -0
  103. package/dist/src/planning/plan-parser.js +506 -0
  104. package/dist/src/redis.d.ts +21 -0
  105. package/dist/src/redis.js +78 -0
  106. package/dist/src/types.d.ts +244 -0
  107. package/dist/src/types.js +8 -0
  108. package/mcp/config.ts +196 -0
  109. package/mcp/server.ts +402 -0
  110. package/mcp/tools/coaiapy-tools.ts +364 -0
  111. package/mcp/tools/index.ts +4 -0
  112. package/package.json +52 -0
  113. package/rispecs/00-coaiajs-platform.spec.md +102 -0
  114. package/rispecs/01-core-config.spec.md +89 -0
  115. package/rispecs/02-redis-module.spec.md +62 -0
  116. package/rispecs/03-langfuse-module.spec.md +101 -0
  117. package/rispecs/04-narrative-engine.spec.md +185 -0
  118. package/rispecs/05-pde-engine.spec.md +112 -0
  119. package/rispecs/06-planning-engine.spec.md +128 -0
  120. package/rispecs/07-pipeline-templates.spec.md +97 -0
  121. package/rispecs/08-cli-interface.spec.md +134 -0
  122. package/rispecs/09-mcp-server.spec.md +140 -0
  123. package/rispecs/10-audio-module.spec.md +63 -0
  124. package/rispecs/KINSHIP.md +56 -0
  125. package/rispecs/README.md +100 -0
  126. package/src/audio.ts +76 -0
  127. package/src/cli-helpers.ts +86 -0
  128. package/src/cli.ts +1223 -0
  129. package/src/config.ts +172 -0
  130. package/src/environment.ts +171 -0
  131. package/src/github.ts +143 -0
  132. package/src/langfuse/client.ts +105 -0
  133. package/src/langfuse/comments.ts +52 -0
  134. package/src/langfuse/datasets.ts +178 -0
  135. package/src/langfuse/index.ts +33 -0
  136. package/src/langfuse/media.ts +193 -0
  137. package/src/langfuse/observations.ts +131 -0
  138. package/src/langfuse/prompts.ts +157 -0
  139. package/src/langfuse/scores.ts +456 -0
  140. package/src/langfuse/traces.ts +276 -0
  141. package/src/llm.ts +106 -0
  142. package/src/narrative/graph-manager.ts +1358 -0
  143. package/src/narrative/index.ts +32 -0
  144. package/src/narrative/markdown-export.ts +535 -0
  145. package/src/narrative/tool-definitions.ts +635 -0
  146. package/src/narrative/tool-handlers.ts +528 -0
  147. package/src/narrative/types.ts +9 -0
  148. package/src/narrative/validation.ts +179 -0
  149. package/src/pde/index.ts +8 -0
  150. package/src/pde/mcp-handlers.ts +359 -0
  151. package/src/pde/mcp-tools.ts +201 -0
  152. package/src/pde/session-manager.ts +248 -0
  153. package/src/pde/stc-mapper.ts +298 -0
  154. package/src/pipeline/index.ts +7 -0
  155. package/src/pipeline/template-engine.ts +398 -0
  156. package/src/planning/index.ts +13 -0
  157. package/src/planning/mcp-handlers.ts +369 -0
  158. package/src/planning/mcp-tools.ts +155 -0
  159. package/src/planning/plan-parser.ts +587 -0
  160. package/src/redis.ts +97 -0
  161. package/src/types.ts +280 -0
  162. package/tsconfig.json +26 -0
@@ -0,0 +1,201 @@
1
+ /**
2
+ * MCP tool definitions for PDE module (10 tools)
3
+ * Ported from coaia-pde/src/mcp-server.ts tool schemas
4
+ */
5
+
6
+ export interface McpToolDef {
7
+ name: string;
8
+ description: string;
9
+ inputSchema: {
10
+ type: 'object';
11
+ properties: Record<string, unknown>;
12
+ required?: string[];
13
+ };
14
+ }
15
+
16
+ export const PDE_MCP_TOOLS: McpToolDef[] = [
17
+ {
18
+ name: 'import_pde_decomposition',
19
+ description: 'Import a .pde/*.json StoredDecomposition and create an STC session from it. Reads the decomposition result, maps it to Structural Tension Chart entities, and persists as .coaia/pde/<UUID>.jsonl.',
20
+ inputSchema: {
21
+ type: 'object',
22
+ properties: {
23
+ pde_id: {
24
+ type: 'string',
25
+ description: 'ID of the PDE decomposition in .pde/ folder'
26
+ },
27
+ workdir: {
28
+ type: 'string',
29
+ description: 'Working directory containing .pde/ folder (default: cwd)'
30
+ }
31
+ },
32
+ required: ['pde_id']
33
+ }
34
+ },
35
+ {
36
+ name: 'create_stc_from_pde',
37
+ description: 'One-shot: provide a DecompositionResult JSON directly and create a Structural Tension Chart session. Use when you already have the decomposition in memory.',
38
+ inputSchema: {
39
+ type: 'object',
40
+ properties: {
41
+ decomposition_result: {
42
+ type: 'object',
43
+ description: 'DecompositionResult JSON object from mcp-pde'
44
+ },
45
+ original_prompt: {
46
+ type: 'string',
47
+ description: 'The original prompt that was decomposed'
48
+ }
49
+ },
50
+ required: ['decomposition_result', 'original_prompt']
51
+ }
52
+ },
53
+ {
54
+ name: 'list_pde_decompositions',
55
+ description: 'List available .pde/*.json decomposition files that can be imported into STC sessions.',
56
+ inputSchema: {
57
+ type: 'object',
58
+ properties: {
59
+ workdir: {
60
+ type: 'string',
61
+ description: 'Working directory containing .pde/ folder (default: cwd)'
62
+ }
63
+ }
64
+ }
65
+ },
66
+ {
67
+ name: 'get_session',
68
+ description: 'Get the current state of a PDE session including the Structural Tension Chart, action steps, and progress.',
69
+ inputSchema: {
70
+ type: 'object',
71
+ properties: {
72
+ session_id: {
73
+ type: 'string',
74
+ description: 'The session UUID'
75
+ }
76
+ },
77
+ required: ['session_id']
78
+ }
79
+ },
80
+ {
81
+ name: 'list_sessions',
82
+ description: 'List all PDE sessions in the current directory.',
83
+ inputSchema: {
84
+ type: 'object',
85
+ properties: {
86
+ status: {
87
+ type: 'string',
88
+ enum: ['active', 'completed', 'abandoned', 'all'],
89
+ description: 'Filter by session status'
90
+ }
91
+ }
92
+ }
93
+ },
94
+ {
95
+ name: 'update_action_progress',
96
+ description: 'Update progress on an action step with a factual observation.',
97
+ inputSchema: {
98
+ type: 'object',
99
+ properties: {
100
+ session_id: {
101
+ type: 'string',
102
+ description: 'The session UUID'
103
+ },
104
+ action_name: {
105
+ type: 'string',
106
+ description: 'The action step entity name (e.g., chart_abc123_action_1)'
107
+ },
108
+ observation: {
109
+ type: 'string',
110
+ description: 'Factual progress observation (what was done, not what will be done)'
111
+ },
112
+ update_current_reality: {
113
+ type: 'boolean',
114
+ description: 'Whether to also add this observation to the chart current reality'
115
+ }
116
+ },
117
+ required: ['session_id', 'action_name', 'observation']
118
+ }
119
+ },
120
+ {
121
+ name: 'mark_action_complete',
122
+ description: 'Mark an action step as complete. The completion flows into current reality.',
123
+ inputSchema: {
124
+ type: 'object',
125
+ properties: {
126
+ session_id: {
127
+ type: 'string',
128
+ description: 'The session UUID'
129
+ },
130
+ action_name: {
131
+ type: 'string',
132
+ description: 'The action step entity name'
133
+ },
134
+ completion_note: {
135
+ type: 'string',
136
+ description: 'Optional note about what was accomplished'
137
+ }
138
+ },
139
+ required: ['session_id', 'action_name']
140
+ }
141
+ },
142
+ {
143
+ name: 'add_action_step',
144
+ description: 'Add a new strategic action step to the chart. Each action step is a secondary choice supporting the primary goal.',
145
+ inputSchema: {
146
+ type: 'object',
147
+ properties: {
148
+ session_id: {
149
+ type: 'string',
150
+ description: 'The session UUID'
151
+ },
152
+ action_description: {
153
+ type: 'string',
154
+ description: 'Description of the strategic action'
155
+ },
156
+ current_reality: {
157
+ type: 'string',
158
+ description: 'Current reality for this specific action step'
159
+ }
160
+ },
161
+ required: ['session_id', 'action_description']
162
+ }
163
+ },
164
+ {
165
+ name: 'update_current_reality',
166
+ description: 'Add observations to the current reality of the chart.',
167
+ inputSchema: {
168
+ type: 'object',
169
+ properties: {
170
+ session_id: {
171
+ type: 'string',
172
+ description: 'The session UUID'
173
+ },
174
+ observations: {
175
+ type: 'array',
176
+ items: { type: 'string' },
177
+ description: 'Factual observations to add to current reality'
178
+ }
179
+ },
180
+ required: ['session_id', 'observations']
181
+ }
182
+ },
183
+ {
184
+ name: 'complete_session',
185
+ description: 'Mark the session as completed when the desired outcome is achieved.',
186
+ inputSchema: {
187
+ type: 'object',
188
+ properties: {
189
+ session_id: {
190
+ type: 'string',
191
+ description: 'The session UUID'
192
+ },
193
+ final_note: {
194
+ type: 'string',
195
+ description: 'Final observation about the completed outcome'
196
+ }
197
+ },
198
+ required: ['session_id']
199
+ }
200
+ },
201
+ ];
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Session Manager — JSONL persistence for PDE sessions in .coaia/pde/<UUID>.jsonl
3
+ * Ported from coaia-pde/src/session-manager.ts
4
+ */
5
+
6
+ import { promises as fs } from 'fs';
7
+ import path from 'path';
8
+ import { v4 as uuidv4 } from 'uuid';
9
+ import type { Entity, Relation, PdeSession, StoredDecomposition } from '../types.js';
10
+
11
+ function serializeEntity(entity: Entity): string {
12
+ return JSON.stringify({ type: 'entity', ...entity });
13
+ }
14
+
15
+ function serializeRelation(relation: Relation): string {
16
+ return JSON.stringify({ type: 'relation', ...relation });
17
+ }
18
+
19
+ export class SessionManager {
20
+ private baseDir: string;
21
+
22
+ constructor(baseDir?: string) {
23
+ this.baseDir = baseDir || path.join(process.cwd(), '.coaia', 'pde');
24
+ }
25
+
26
+ private getSessionPath(sessionId: string): string {
27
+ return path.join(this.baseDir, `${sessionId}.jsonl`);
28
+ }
29
+
30
+ async ensureBaseDir(): Promise<void> {
31
+ await fs.mkdir(this.baseDir, { recursive: true });
32
+ }
33
+
34
+ async initSession(
35
+ originalPrompt: string,
36
+ masterChartId: string,
37
+ pdeDecompositionId?: string
38
+ ): Promise<PdeSession> {
39
+ await this.ensureBaseDir();
40
+
41
+ const sessionId = uuidv4();
42
+ const now = new Date().toISOString();
43
+
44
+ const session: PdeSession = {
45
+ type: 'pde_session',
46
+ sessionId,
47
+ originalPrompt,
48
+ masterChartId,
49
+ pdeDecompositionId,
50
+ createdAt: now,
51
+ updatedAt: now,
52
+ status: 'active'
53
+ };
54
+
55
+ const sessionPath = this.getSessionPath(sessionId);
56
+ await fs.writeFile(sessionPath, JSON.stringify(session) + '\n');
57
+
58
+ return session;
59
+ }
60
+
61
+ async loadSession(sessionId: string): Promise<{
62
+ session: PdeSession;
63
+ entities: Entity[];
64
+ relations: Relation[];
65
+ }> {
66
+ const sessionPath = this.getSessionPath(sessionId);
67
+ const data = await fs.readFile(sessionPath, 'utf-8');
68
+ const lines = data.split('\n').filter(line => line.trim());
69
+
70
+ let session: PdeSession | null = null;
71
+ const entities: Entity[] = [];
72
+ const relations: Relation[] = [];
73
+
74
+ for (const line of lines) {
75
+ const item = JSON.parse(line);
76
+ if (item.type === 'pde_session') {
77
+ session = item as PdeSession;
78
+ } else if (item.type === 'entity') {
79
+ const { type: _discriminator, ...entity } = item;
80
+ entities.push(entity as Entity);
81
+ } else if (item.type === 'relation') {
82
+ const { type: _discriminator, ...relation } = item;
83
+ relations.push(relation as Relation);
84
+ }
85
+ }
86
+
87
+ if (!session) {
88
+ throw new Error(`Session ${sessionId} not found or corrupted`);
89
+ }
90
+
91
+ return { session, entities, relations };
92
+ }
93
+
94
+ async appendEntity(sessionId: string, entity: Entity): Promise<void> {
95
+ const sessionPath = this.getSessionPath(sessionId);
96
+ await fs.appendFile(sessionPath, serializeEntity(entity) + '\n');
97
+ }
98
+
99
+ async appendRelation(sessionId: string, relation: Relation): Promise<void> {
100
+ const sessionPath = this.getSessionPath(sessionId);
101
+ await fs.appendFile(sessionPath, serializeRelation(relation) + '\n');
102
+ }
103
+
104
+ async updateSession(sessionId: string, updates: Partial<PdeSession>): Promise<void> {
105
+ const { session, entities, relations } = await this.loadSession(sessionId);
106
+ const updatedSession = { ...session, ...updates, updatedAt: new Date().toISOString() };
107
+
108
+ const sessionPath = this.getSessionPath(sessionId);
109
+ const lines = [
110
+ JSON.stringify(updatedSession),
111
+ ...entities.map(e => serializeEntity(e)),
112
+ ...relations.map(r => serializeRelation(r))
113
+ ];
114
+ await fs.writeFile(sessionPath, lines.join('\n') + '\n');
115
+ }
116
+
117
+ async updateEntity(sessionId: string, entityName: string, updates: Partial<Entity>): Promise<void> {
118
+ const { session, entities, relations } = await this.loadSession(sessionId);
119
+
120
+ const entityIndex = entities.findIndex(e => e.name === entityName);
121
+ if (entityIndex === -1) {
122
+ throw new Error(`Entity ${entityName} not found in session ${sessionId}`);
123
+ }
124
+
125
+ entities[entityIndex] = { ...entities[entityIndex], ...updates };
126
+ if (entities[entityIndex].metadata) {
127
+ entities[entityIndex].metadata!.updatedAt = new Date().toISOString();
128
+ }
129
+
130
+ const sessionPath = this.getSessionPath(sessionId);
131
+ const lines = [
132
+ JSON.stringify(session),
133
+ ...entities.map(e => serializeEntity(e)),
134
+ ...relations.map(r => serializeRelation(r))
135
+ ];
136
+ await fs.writeFile(sessionPath, lines.join('\n') + '\n');
137
+ }
138
+
139
+ async listSessions(): Promise<PdeSession[]> {
140
+ try {
141
+ await this.ensureBaseDir();
142
+ const files = await fs.readdir(this.baseDir);
143
+ const sessions: PdeSession[] = [];
144
+
145
+ for (const file of files) {
146
+ if (file.endsWith('.jsonl')) {
147
+ try {
148
+ const sessionId = file.replace('.jsonl', '');
149
+ const { session } = await this.loadSession(sessionId);
150
+ sessions.push(session);
151
+ } catch {
152
+ // Skip corrupted files
153
+ }
154
+ }
155
+ }
156
+
157
+ return sessions.sort((a, b) =>
158
+ new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
159
+ );
160
+ } catch {
161
+ return [];
162
+ }
163
+ }
164
+
165
+ async deleteSession(sessionId: string): Promise<void> {
166
+ const sessionPath = this.getSessionPath(sessionId);
167
+ await fs.unlink(sessionPath);
168
+ }
169
+
170
+ async getSessionFilePath(sessionId: string): Promise<string> {
171
+ return this.getSessionPath(sessionId);
172
+ }
173
+
174
+ // =========================================================================
175
+ // PDE Decomposition File Access (.pde/ directory)
176
+ // =========================================================================
177
+
178
+ async listPdeDecompositions(workdir?: string): Promise<Array<{
179
+ id: string;
180
+ timestamp: string;
181
+ prompt: string;
182
+ }>> {
183
+ const pdeDir = path.join(workdir || process.cwd(), '.pde');
184
+ try {
185
+ const entries = await fs.readdir(pdeDir);
186
+ const results: Array<{ id: string; timestamp: string; prompt: string }> = [];
187
+
188
+ for (const entry of entries) {
189
+ if (!entry.endsWith('.json')) continue;
190
+ try {
191
+ const filePath = path.join(pdeDir, entry);
192
+ const data = await fs.readFile(filePath, 'utf-8');
193
+ const stored = JSON.parse(data) as StoredDecomposition;
194
+ if (stored.id && stored.result) {
195
+ results.push({
196
+ id: stored.id,
197
+ timestamp: stored.timestamp,
198
+ prompt: stored.prompt,
199
+ });
200
+ }
201
+ } catch {
202
+ // Skip invalid files
203
+ }
204
+ }
205
+
206
+ return results.sort((a, b) =>
207
+ new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
208
+ );
209
+ } catch {
210
+ return [];
211
+ }
212
+ }
213
+
214
+ async loadPdeDecomposition(id: string, workdir?: string): Promise<StoredDecomposition | null> {
215
+ const pdeDir = path.join(workdir || process.cwd(), '.pde');
216
+
217
+ // Try direct {id}.json file first
218
+ const directPath = path.join(pdeDir, `${id}.json`);
219
+ try {
220
+ const data = await fs.readFile(directPath, 'utf-8');
221
+ return JSON.parse(data) as StoredDecomposition;
222
+ } catch {
223
+ // Not found as direct file — scan for matching id
224
+ }
225
+
226
+ // Scan directory for a .json file whose parsed id matches
227
+ try {
228
+ const entries = await fs.readdir(pdeDir);
229
+ for (const entry of entries) {
230
+ if (!entry.endsWith('.json')) continue;
231
+ try {
232
+ const filePath = path.join(pdeDir, entry);
233
+ const data = await fs.readFile(filePath, 'utf-8');
234
+ const parsed = JSON.parse(data) as StoredDecomposition;
235
+ if (parsed.id === id) return parsed;
236
+ } catch {
237
+ // Skip invalid
238
+ }
239
+ }
240
+ } catch {
241
+ // Directory not found
242
+ }
243
+
244
+ return null;
245
+ }
246
+ }
247
+
248
+ export const sessionManager = new SessionManager();