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.
- package/CLAUDE.md +56 -0
- package/KINSHIP.md +63 -0
- package/README.md +148 -0
- package/articles/academic/creative-orientation-vs-problem-solving.md +177 -0
- package/articles/academic/jsonl-knowledge-graphs-agent-memory.md +142 -0
- package/articles/academic/langfuse-observability-llm-pipelines.md +144 -0
- package/articles/academic/medicine-wheel-software-architecture.md +163 -0
- package/articles/academic/mmot-autonomous-agents.md +156 -0
- package/articles/academic/model-context-protocol-interagent.md +161 -0
- package/articles/academic/pde-prompt-decomposition.md +186 -0
- package/articles/academic/structural-tension-in-ai-agents.md +134 -0
- package/articles/reviews/mcp-protocol-design-review.md +170 -0
- package/articles/reviews/observability-ai-systems-review.md +176 -0
- package/articles/reviews/prompt-engineering-decomposition-review.md +184 -0
- package/articles/surveys/agent-orchestration-survey.md +186 -0
- package/articles/surveys/knowledge-graph-storage-survey.md +204 -0
- package/articles/surveys/structural-tension-methodology-survey.md +154 -0
- package/articles/technical/aws-sdk-v3-polly.md +270 -0
- package/articles/technical/commander-cli-framework.md +262 -0
- package/articles/technical/dotenv-config-patterns.md +360 -0
- package/articles/technical/ioredis-vs-redis.md +142 -0
- package/articles/technical/langfuse-js-sdk-vs-rest.md +191 -0
- package/articles/technical/mcp-sdk-typescript.md +291 -0
- package/articles/technical/octokit-github-api.md +293 -0
- package/articles/technical/openai-sdk-modern.md +231 -0
- package/articles/technical/yaml-parsing-node.md +266 -0
- package/articles/technical/zod-runtime-validation.md +212 -0
- package/dist/mcp/config.d.ts +14 -0
- package/dist/mcp/config.js +185 -0
- package/dist/mcp/server.d.ts +3 -0
- package/dist/mcp/server.js +324 -0
- package/dist/mcp/tools/coaiapy-tools.d.ts +8 -0
- package/dist/mcp/tools/coaiapy-tools.js +326 -0
- package/dist/mcp/tools/index.d.ts +3 -0
- package/dist/mcp/tools/index.js +3 -0
- package/dist/src/audio.d.ts +12 -0
- package/dist/src/audio.js +57 -0
- package/dist/src/cli-helpers.d.ts +9 -0
- package/dist/src/cli-helpers.js +78 -0
- package/dist/src/cli.d.ts +3 -0
- package/dist/src/cli.js +1086 -0
- package/dist/src/config.d.ts +15 -0
- package/dist/src/config.js +145 -0
- package/dist/src/environment.d.ts +44 -0
- package/dist/src/environment.js +146 -0
- package/dist/src/github.d.ts +47 -0
- package/dist/src/github.js +79 -0
- package/dist/src/langfuse/client.d.ts +30 -0
- package/dist/src/langfuse/client.js +75 -0
- package/dist/src/langfuse/comments.d.ts +16 -0
- package/dist/src/langfuse/comments.js +36 -0
- package/dist/src/langfuse/datasets.d.ts +19 -0
- package/dist/src/langfuse/datasets.js +140 -0
- package/dist/src/langfuse/index.d.ts +13 -0
- package/dist/src/langfuse/index.js +10 -0
- package/dist/src/langfuse/media.d.ts +11 -0
- package/dist/src/langfuse/media.js +167 -0
- package/dist/src/langfuse/observations.d.ts +18 -0
- package/dist/src/langfuse/observations.js +113 -0
- package/dist/src/langfuse/prompts.d.ts +14 -0
- package/dist/src/langfuse/prompts.js +127 -0
- package/dist/src/langfuse/scores.d.ts +54 -0
- package/dist/src/langfuse/scores.js +366 -0
- package/dist/src/langfuse/traces.d.ts +29 -0
- package/dist/src/langfuse/traces.js +225 -0
- package/dist/src/llm.d.ts +23 -0
- package/dist/src/llm.js +76 -0
- package/dist/src/narrative/graph-manager.d.ts +106 -0
- package/dist/src/narrative/graph-manager.js +915 -0
- package/dist/src/narrative/index.d.ts +9 -0
- package/dist/src/narrative/index.js +7 -0
- package/dist/src/narrative/markdown-export.d.ts +21 -0
- package/dist/src/narrative/markdown-export.js +383 -0
- package/dist/src/narrative/tool-definitions.d.ts +21 -0
- package/dist/src/narrative/tool-definitions.js +588 -0
- package/dist/src/narrative/tool-handlers.d.ts +11 -0
- package/dist/src/narrative/tool-handlers.js +462 -0
- package/dist/src/narrative/types.d.ts +2 -0
- package/dist/src/narrative/types.js +2 -0
- package/dist/src/narrative/validation.d.ts +29 -0
- package/dist/src/narrative/validation.js +144 -0
- package/dist/src/pde/index.d.ts +8 -0
- package/dist/src/pde/index.js +8 -0
- package/dist/src/pde/mcp-handlers.d.ts +7 -0
- package/dist/src/pde/mcp-handlers.js +300 -0
- package/dist/src/pde/mcp-tools.d.ts +15 -0
- package/dist/src/pde/mcp-tools.js +191 -0
- package/dist/src/pde/session-manager.d.ts +32 -0
- package/dist/src/pde/session-manager.js +205 -0
- package/dist/src/pde/stc-mapper.d.ts +26 -0
- package/dist/src/pde/stc-mapper.js +234 -0
- package/dist/src/pipeline/index.d.ts +2 -0
- package/dist/src/pipeline/index.js +3 -0
- package/dist/src/pipeline/template-engine.d.ts +20 -0
- package/dist/src/pipeline/template-engine.js +361 -0
- package/dist/src/planning/index.d.ts +7 -0
- package/dist/src/planning/index.js +7 -0
- package/dist/src/planning/mcp-handlers.d.ts +7 -0
- package/dist/src/planning/mcp-handlers.js +310 -0
- package/dist/src/planning/mcp-tools.d.ts +15 -0
- package/dist/src/planning/mcp-tools.js +145 -0
- package/dist/src/planning/plan-parser.d.ts +35 -0
- package/dist/src/planning/plan-parser.js +506 -0
- package/dist/src/redis.d.ts +21 -0
- package/dist/src/redis.js +78 -0
- package/dist/src/types.d.ts +244 -0
- package/dist/src/types.js +8 -0
- package/mcp/config.ts +196 -0
- package/mcp/server.ts +402 -0
- package/mcp/tools/coaiapy-tools.ts +364 -0
- package/mcp/tools/index.ts +4 -0
- package/package.json +52 -0
- package/rispecs/00-coaiajs-platform.spec.md +102 -0
- package/rispecs/01-core-config.spec.md +89 -0
- package/rispecs/02-redis-module.spec.md +62 -0
- package/rispecs/03-langfuse-module.spec.md +101 -0
- package/rispecs/04-narrative-engine.spec.md +185 -0
- package/rispecs/05-pde-engine.spec.md +112 -0
- package/rispecs/06-planning-engine.spec.md +128 -0
- package/rispecs/07-pipeline-templates.spec.md +97 -0
- package/rispecs/08-cli-interface.spec.md +134 -0
- package/rispecs/09-mcp-server.spec.md +140 -0
- package/rispecs/10-audio-module.spec.md +63 -0
- package/rispecs/KINSHIP.md +56 -0
- package/rispecs/README.md +100 -0
- package/src/audio.ts +76 -0
- package/src/cli-helpers.ts +86 -0
- package/src/cli.ts +1223 -0
- package/src/config.ts +172 -0
- package/src/environment.ts +171 -0
- package/src/github.ts +143 -0
- package/src/langfuse/client.ts +105 -0
- package/src/langfuse/comments.ts +52 -0
- package/src/langfuse/datasets.ts +178 -0
- package/src/langfuse/index.ts +33 -0
- package/src/langfuse/media.ts +193 -0
- package/src/langfuse/observations.ts +131 -0
- package/src/langfuse/prompts.ts +157 -0
- package/src/langfuse/scores.ts +456 -0
- package/src/langfuse/traces.ts +276 -0
- package/src/llm.ts +106 -0
- package/src/narrative/graph-manager.ts +1358 -0
- package/src/narrative/index.ts +32 -0
- package/src/narrative/markdown-export.ts +535 -0
- package/src/narrative/tool-definitions.ts +635 -0
- package/src/narrative/tool-handlers.ts +528 -0
- package/src/narrative/types.ts +9 -0
- package/src/narrative/validation.ts +179 -0
- package/src/pde/index.ts +8 -0
- package/src/pde/mcp-handlers.ts +359 -0
- package/src/pde/mcp-tools.ts +201 -0
- package/src/pde/session-manager.ts +248 -0
- package/src/pde/stc-mapper.ts +298 -0
- package/src/pipeline/index.ts +7 -0
- package/src/pipeline/template-engine.ts +398 -0
- package/src/planning/index.ts +13 -0
- package/src/planning/mcp-handlers.ts +369 -0
- package/src/planning/mcp-tools.ts +155 -0
- package/src/planning/plan-parser.ts +587 -0
- package/src/redis.ts +97 -0
- package/src/types.ts +280 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan Parser — Transforms markdown plans into structural tension charts
|
|
3
|
+
* Ported from coaia-planning/src/plan-parser.ts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { promises as fs } from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import type {
|
|
9
|
+
Entity,
|
|
10
|
+
Relation,
|
|
11
|
+
StructuralElement,
|
|
12
|
+
StructuralTensionPlan,
|
|
13
|
+
DecompositionResult,
|
|
14
|
+
} from '../types.js';
|
|
15
|
+
|
|
16
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
17
|
+
// PATTERN RECOGNITION
|
|
18
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
19
|
+
|
|
20
|
+
const DESIRED_OUTCOME_PATTERNS: RegExp[] = [
|
|
21
|
+
// Explicit markers
|
|
22
|
+
/^(?:##?\s*)?(?:desired\s+outcome|goal|objective|end\s+result|vision|target):\s*(.+)/im,
|
|
23
|
+
/\*\*(?:desired\s+outcome|goal|objective)\*\*:\s*(.+)/im,
|
|
24
|
+
// Creative orientation language
|
|
25
|
+
/(?:we\s+want\s+to\s+create|aim\s+to\s+manifest|intend\s+to\s+bring\s+into\s+being)\s+(.+)/i,
|
|
26
|
+
/(?:the\s+end\s+result\s+is|what\s+we're\s+creating\s+is)\s+(.+)/i,
|
|
27
|
+
// State of being descriptions
|
|
28
|
+
/(?:when\s+complete|upon\s+completion)[,:]?\s*(.+)/i,
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const CURRENT_REALITY_PATTERNS: RegExp[] = [
|
|
32
|
+
// Explicit markers
|
|
33
|
+
/^(?:##?\s*)?(?:current\s+reality|current\s+state|where\s+we\s+are|starting\s+point):\s*(.+)/im,
|
|
34
|
+
/\*\*(?:current\s+reality|current\s+state)\*\*:\s*(.+)/im,
|
|
35
|
+
// Factual assessment language
|
|
36
|
+
/(?:currently|at\s+present|as\s+of\s+now)[,:]?\s*(.+)/i,
|
|
37
|
+
/(?:the\s+current\s+situation\s+is|we\s+currently\s+have)\s+(.+)/i,
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const ACTION_STEP_PATTERNS: RegExp[] = [
|
|
41
|
+
// Phase markers (these telescope)
|
|
42
|
+
/^(?:##?\s*)?phase\s+(\d+)[:\s]+(.+)/im,
|
|
43
|
+
/^(?:##?\s*)?step\s+(\d+)[:\s]+(.+)/im,
|
|
44
|
+
// Numbered items
|
|
45
|
+
/^(\d+)\.\s+(.+)$/gm,
|
|
46
|
+
// Checklist items
|
|
47
|
+
/^-\s*\[([ x])\]\s+(.+)$/gm,
|
|
48
|
+
// Action-oriented subsections
|
|
49
|
+
/^###?\s+(.+)$/gm,
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
53
|
+
// PARSING FUNCTIONS
|
|
54
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Parse a plan file as a structural tension chart
|
|
58
|
+
*/
|
|
59
|
+
export async function parsePlan(planPath: string): Promise<StructuralTensionPlan> {
|
|
60
|
+
const content = await fs.readFile(planPath, 'utf-8');
|
|
61
|
+
return parsePlanContent(content, planPath);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Parse plan content directly
|
|
66
|
+
*/
|
|
67
|
+
export function parsePlanContent(
|
|
68
|
+
content: string,
|
|
69
|
+
filePath: string = 'inline',
|
|
70
|
+
telescopeLevel: number = 0
|
|
71
|
+
): StructuralTensionPlan {
|
|
72
|
+
const lines = content.split('\n');
|
|
73
|
+
|
|
74
|
+
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
75
|
+
const title = titleMatch ? titleMatch[1] : path.basename(filePath, '.md');
|
|
76
|
+
|
|
77
|
+
const desiredOutcome = extractDesiredOutcome(content, lines);
|
|
78
|
+
const currentReality = extractCurrentReality(content, lines);
|
|
79
|
+
const actionSteps = extractActionSteps(content, lines);
|
|
80
|
+
const observations = extractObservations(content, lines, desiredOutcome, currentReality, actionSteps);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
title,
|
|
84
|
+
filePath,
|
|
85
|
+
desiredOutcome,
|
|
86
|
+
currentReality,
|
|
87
|
+
actionSteps,
|
|
88
|
+
observations,
|
|
89
|
+
metadata: {
|
|
90
|
+
parsedAt: new Date().toISOString(),
|
|
91
|
+
telescopeLevel,
|
|
92
|
+
completionStatus: 'pending',
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Convert structural tension plan to Entity[]/Relation[] (JSONL-compatible)
|
|
99
|
+
*/
|
|
100
|
+
export function planToSTC(
|
|
101
|
+
plan: StructuralTensionPlan,
|
|
102
|
+
chartIdPrefix: string = 'plan'
|
|
103
|
+
): { entities: Entity[]; relations: Relation[] } {
|
|
104
|
+
const chartId = `${chartIdPrefix}_${sanitizeName(plan.title)}`;
|
|
105
|
+
const entities: Entity[] = [];
|
|
106
|
+
const relations: Relation[] = [];
|
|
107
|
+
const now = new Date().toISOString();
|
|
108
|
+
|
|
109
|
+
// Chart entity
|
|
110
|
+
entities.push({
|
|
111
|
+
name: `${chartId}_chart`,
|
|
112
|
+
entityType: 'structural_tension_chart',
|
|
113
|
+
observations: [`Parsed from: ${plan.filePath}`],
|
|
114
|
+
metadata: {
|
|
115
|
+
chartId,
|
|
116
|
+
level: plan.metadata.telescopeLevel,
|
|
117
|
+
createdAt: now,
|
|
118
|
+
updatedAt: now,
|
|
119
|
+
dueDate: plan.metadata.dueDate,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Desired outcome
|
|
124
|
+
if (plan.desiredOutcome) {
|
|
125
|
+
const doName = `${chartId}_desired_outcome`;
|
|
126
|
+
entities.push({
|
|
127
|
+
name: doName,
|
|
128
|
+
entityType: 'desired_outcome',
|
|
129
|
+
observations: [plan.desiredOutcome.content],
|
|
130
|
+
metadata: {
|
|
131
|
+
chartId,
|
|
132
|
+
confidence: plan.desiredOutcome.confidence,
|
|
133
|
+
createdAt: now,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
relations.push({
|
|
137
|
+
from: `${chartId}_chart`,
|
|
138
|
+
to: doName,
|
|
139
|
+
relationType: 'contains',
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Current reality
|
|
144
|
+
if (plan.currentReality) {
|
|
145
|
+
const crName = `${chartId}_current_reality`;
|
|
146
|
+
entities.push({
|
|
147
|
+
name: crName,
|
|
148
|
+
entityType: 'current_reality',
|
|
149
|
+
observations: [plan.currentReality.content],
|
|
150
|
+
metadata: {
|
|
151
|
+
chartId,
|
|
152
|
+
confidence: plan.currentReality.confidence,
|
|
153
|
+
createdAt: now,
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
relations.push({
|
|
157
|
+
from: `${chartId}_chart`,
|
|
158
|
+
to: crName,
|
|
159
|
+
relationType: 'contains',
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Structural tension relation
|
|
163
|
+
if (plan.desiredOutcome) {
|
|
164
|
+
relations.push({
|
|
165
|
+
from: crName,
|
|
166
|
+
to: `${chartId}_desired_outcome`,
|
|
167
|
+
relationType: 'creates_tension_with',
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Action steps (each is a potential telescoped STC)
|
|
173
|
+
plan.actionSteps.forEach((step, index) => {
|
|
174
|
+
const actionName = `${chartId}_action_${index + 1}`;
|
|
175
|
+
entities.push({
|
|
176
|
+
name: actionName,
|
|
177
|
+
entityType: 'action_step',
|
|
178
|
+
observations: [step.content],
|
|
179
|
+
metadata: {
|
|
180
|
+
chartId,
|
|
181
|
+
order: index + 1,
|
|
182
|
+
completionStatus: false,
|
|
183
|
+
confidence: step.confidence,
|
|
184
|
+
createdAt: now,
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
relations.push({
|
|
189
|
+
from: `${chartId}_chart`,
|
|
190
|
+
to: actionName,
|
|
191
|
+
relationType: 'contains',
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
if (plan.desiredOutcome) {
|
|
195
|
+
relations.push({
|
|
196
|
+
from: actionName,
|
|
197
|
+
to: `${chartId}_desired_outcome`,
|
|
198
|
+
relationType: 'advances_toward',
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// If action telescopes, recursively convert
|
|
203
|
+
if (step.telescopesInto) {
|
|
204
|
+
const nestedSTC = planToSTC(step.telescopesInto, actionName);
|
|
205
|
+
entities.push(...nestedSTC.entities);
|
|
206
|
+
relations.push(...nestedSTC.relations);
|
|
207
|
+
|
|
208
|
+
relations.push({
|
|
209
|
+
from: actionName,
|
|
210
|
+
to: `${actionName}_chart`,
|
|
211
|
+
relationType: 'telescopes_into',
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
return { entities, relations };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Export Entity[]/Relation[] to JSONL format
|
|
221
|
+
*/
|
|
222
|
+
export function exportToJSONL(stc: { entities: Entity[]; relations: Relation[] }): string {
|
|
223
|
+
const lines: string[] = [];
|
|
224
|
+
|
|
225
|
+
for (const entity of stc.entities) {
|
|
226
|
+
lines.push(JSON.stringify({ type: 'entity', ...entity }));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
for (const relation of stc.relations) {
|
|
230
|
+
lines.push(JSON.stringify({ type: 'relation', ...relation }));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return lines.join('\n');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* PDE bridge: Convert DecompositionResult into Entity[]/Relation[]
|
|
238
|
+
*/
|
|
239
|
+
export function decompositionResultToPlan(
|
|
240
|
+
decomposition: DecompositionResult,
|
|
241
|
+
originalPrompt?: string
|
|
242
|
+
): { entities: Entity[]; relations: Relation[] } {
|
|
243
|
+
const chartId = `pde_${Date.now()}`;
|
|
244
|
+
const entities: Entity[] = [];
|
|
245
|
+
const relations: Relation[] = [];
|
|
246
|
+
const now = new Date().toISOString();
|
|
247
|
+
|
|
248
|
+
// Collect direction observations for chart entity
|
|
249
|
+
const directionObservations: string[] = [];
|
|
250
|
+
for (const [dir, items] of Object.entries(decomposition.directions)) {
|
|
251
|
+
for (const item of items) {
|
|
252
|
+
directionObservations.push(`[${dir.toUpperCase()}] ${item.text}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Chart entity
|
|
257
|
+
entities.push({
|
|
258
|
+
name: `${chartId}_chart`,
|
|
259
|
+
entityType: 'structural_tension_chart',
|
|
260
|
+
observations: [
|
|
261
|
+
`PDE decomposition of: ${originalPrompt || `${decomposition.primary.action} ${decomposition.primary.target}`}`,
|
|
262
|
+
`Primary confidence: ${decomposition.primary.confidence}`,
|
|
263
|
+
`Urgency: ${decomposition.primary.urgency}`,
|
|
264
|
+
...directionObservations,
|
|
265
|
+
],
|
|
266
|
+
metadata: {
|
|
267
|
+
chartId,
|
|
268
|
+
level: 0,
|
|
269
|
+
createdAt: now,
|
|
270
|
+
updatedAt: now,
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Desired outcome from primary intent + expected outputs
|
|
275
|
+
const doName = `${chartId}_desired_outcome`;
|
|
276
|
+
const doObservations = [
|
|
277
|
+
`${decomposition.primary.action}: ${decomposition.primary.target}`,
|
|
278
|
+
...decomposition.outputs.artifacts.map(a => `Artifact: ${a}`),
|
|
279
|
+
...decomposition.outputs.updates.map(u => `Update: ${u}`),
|
|
280
|
+
...decomposition.outputs.communications.map(c => `Communication: ${c}`),
|
|
281
|
+
];
|
|
282
|
+
entities.push({
|
|
283
|
+
name: doName,
|
|
284
|
+
entityType: 'desired_outcome',
|
|
285
|
+
observations: doObservations,
|
|
286
|
+
metadata: {
|
|
287
|
+
chartId,
|
|
288
|
+
confidence: decomposition.primary.confidence,
|
|
289
|
+
createdAt: now,
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
relations.push({
|
|
293
|
+
from: `${chartId}_chart`,
|
|
294
|
+
to: doName,
|
|
295
|
+
relationType: 'contains',
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Current reality from context requirements
|
|
299
|
+
const crName = `${chartId}_current_reality`;
|
|
300
|
+
const crObservations = [
|
|
301
|
+
...decomposition.context.assumptions,
|
|
302
|
+
...decomposition.context.files_needed.map(f => `File needed: ${f}`),
|
|
303
|
+
...decomposition.context.tools_required.map(t => `Tool required: ${t}`),
|
|
304
|
+
];
|
|
305
|
+
if (crObservations.length === 0) {
|
|
306
|
+
crObservations.push('No explicit current reality captured from decomposition context');
|
|
307
|
+
}
|
|
308
|
+
entities.push({
|
|
309
|
+
name: crName,
|
|
310
|
+
entityType: 'current_reality',
|
|
311
|
+
observations: crObservations,
|
|
312
|
+
metadata: {
|
|
313
|
+
chartId,
|
|
314
|
+
createdAt: now,
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
relations.push({
|
|
318
|
+
from: `${chartId}_chart`,
|
|
319
|
+
to: crName,
|
|
320
|
+
relationType: 'contains',
|
|
321
|
+
});
|
|
322
|
+
relations.push({
|
|
323
|
+
from: crName,
|
|
324
|
+
to: doName,
|
|
325
|
+
relationType: 'creates_tension_with',
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Action steps from actionStack
|
|
329
|
+
decomposition.actionStack.forEach((action, index) => {
|
|
330
|
+
const actionName = `${chartId}_action_${index + 1}`;
|
|
331
|
+
entities.push({
|
|
332
|
+
name: actionName,
|
|
333
|
+
entityType: 'action_step',
|
|
334
|
+
observations: [action.text],
|
|
335
|
+
metadata: {
|
|
336
|
+
chartId,
|
|
337
|
+
order: index + 1,
|
|
338
|
+
completionStatus: action.completed || false,
|
|
339
|
+
direction: action.direction,
|
|
340
|
+
createdAt: now,
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
relations.push({
|
|
345
|
+
from: `${chartId}_chart`,
|
|
346
|
+
to: actionName,
|
|
347
|
+
relationType: 'contains',
|
|
348
|
+
});
|
|
349
|
+
relations.push({
|
|
350
|
+
from: actionName,
|
|
351
|
+
to: doName,
|
|
352
|
+
relationType: 'advances_toward',
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// Dependency relations — match by text against other actions
|
|
356
|
+
if (action.dependency) {
|
|
357
|
+
const depIndex = decomposition.actionStack.findIndex(a => a.text === action.dependency);
|
|
358
|
+
if (depIndex >= 0) {
|
|
359
|
+
relations.push({
|
|
360
|
+
from: actionName,
|
|
361
|
+
to: `${chartId}_action_${depIndex + 1}`,
|
|
362
|
+
relationType: 'depends_on',
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// Secondary intents as observation entities
|
|
369
|
+
decomposition.secondary.forEach((intent, index) => {
|
|
370
|
+
const secName = `${chartId}_secondary_${index + 1}`;
|
|
371
|
+
entities.push({
|
|
372
|
+
name: secName,
|
|
373
|
+
entityType: 'observation',
|
|
374
|
+
observations: [
|
|
375
|
+
`${intent.action}: ${intent.target}`,
|
|
376
|
+
intent.implicit ? '(implicit intent)' : '(explicit intent)',
|
|
377
|
+
],
|
|
378
|
+
metadata: {
|
|
379
|
+
chartId,
|
|
380
|
+
confidence: intent.confidence,
|
|
381
|
+
createdAt: now,
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
relations.push({
|
|
385
|
+
from: `${chartId}_chart`,
|
|
386
|
+
to: secName,
|
|
387
|
+
relationType: 'contains',
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// Ambiguities as observation entities
|
|
392
|
+
decomposition.ambiguities.forEach((amb, index) => {
|
|
393
|
+
const ambName = `${chartId}_ambiguity_${index + 1}`;
|
|
394
|
+
entities.push({
|
|
395
|
+
name: ambName,
|
|
396
|
+
entityType: 'observation',
|
|
397
|
+
observations: [
|
|
398
|
+
`Ambiguity: ${amb.text}`,
|
|
399
|
+
`Suggestion: ${amb.suggestion}`,
|
|
400
|
+
],
|
|
401
|
+
metadata: {
|
|
402
|
+
chartId,
|
|
403
|
+
createdAt: now,
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
relations.push({
|
|
407
|
+
from: `${chartId}_chart`,
|
|
408
|
+
to: ambName,
|
|
409
|
+
relationType: 'contains',
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
return { entities, relations };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
417
|
+
// INTERNAL HELPERS
|
|
418
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
419
|
+
|
|
420
|
+
function extractDesiredOutcome(content: string, lines: string[]): StructuralElement | null {
|
|
421
|
+
for (const pattern of DESIRED_OUTCOME_PATTERNS) {
|
|
422
|
+
const match = content.match(pattern);
|
|
423
|
+
if (match && match[1]) {
|
|
424
|
+
const lineNum = findLineNumber(lines, match[0]);
|
|
425
|
+
return {
|
|
426
|
+
type: 'desired_outcome',
|
|
427
|
+
content: match[1].trim(),
|
|
428
|
+
confidence: 0.9,
|
|
429
|
+
sourceLines: { start: lineNum, end: lineNum },
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Fallback: Look for "Desired Outcome" section
|
|
435
|
+
const sectionMatch = content.match(/##\s*Desired\s+Outcome\s*\n([\s\S]*?)(?=\n##|\n#|$)/i);
|
|
436
|
+
if (sectionMatch) {
|
|
437
|
+
const sectionContent = sectionMatch[1].trim();
|
|
438
|
+
const firstParagraph = sectionContent.split('\n\n')[0];
|
|
439
|
+
const lineNum = findLineNumber(lines, sectionMatch[0]);
|
|
440
|
+
return {
|
|
441
|
+
type: 'desired_outcome',
|
|
442
|
+
content: firstParagraph,
|
|
443
|
+
confidence: 0.8,
|
|
444
|
+
sourceLines: { start: lineNum, end: lineNum + firstParagraph.split('\n').length },
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function extractCurrentReality(content: string, lines: string[]): StructuralElement | null {
|
|
452
|
+
for (const pattern of CURRENT_REALITY_PATTERNS) {
|
|
453
|
+
const match = content.match(pattern);
|
|
454
|
+
if (match && match[1]) {
|
|
455
|
+
const lineNum = findLineNumber(lines, match[0]);
|
|
456
|
+
return {
|
|
457
|
+
type: 'current_reality',
|
|
458
|
+
content: match[1].trim(),
|
|
459
|
+
confidence: 0.9,
|
|
460
|
+
sourceLines: { start: lineNum, end: lineNum },
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Fallback: Look for "Current Reality" or "Current State" section
|
|
466
|
+
const sectionMatch = content.match(/##\s*Current\s+(?:Reality|State)\s*\n([\s\S]*?)(?=\n##|\n#|$)/i);
|
|
467
|
+
if (sectionMatch) {
|
|
468
|
+
const sectionContent = sectionMatch[1].trim();
|
|
469
|
+
const firstParagraph = sectionContent.split('\n\n')[0];
|
|
470
|
+
const lineNum = findLineNumber(lines, sectionMatch[0]);
|
|
471
|
+
return {
|
|
472
|
+
type: 'current_reality',
|
|
473
|
+
content: firstParagraph,
|
|
474
|
+
confidence: 0.8,
|
|
475
|
+
sourceLines: { start: lineNum, end: lineNum + firstParagraph.split('\n').length },
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function extractActionSteps(content: string, lines: string[]): StructuralElement[] {
|
|
483
|
+
const steps: StructuralElement[] = [];
|
|
484
|
+
|
|
485
|
+
// Look for Phase sections (highest priority — these telescope)
|
|
486
|
+
const phasePattern = /^##\s*Phase\s+(\d+)[:\s]*(.+)?$/gim;
|
|
487
|
+
let phaseMatch;
|
|
488
|
+
while ((phaseMatch = phasePattern.exec(content)) !== null) {
|
|
489
|
+
const phaseNum = phaseMatch[1];
|
|
490
|
+
const phaseTitle = phaseMatch[2]?.trim() || `Phase ${phaseNum}`;
|
|
491
|
+
const lineNum = findLineNumber(lines, phaseMatch[0]);
|
|
492
|
+
|
|
493
|
+
// Extract phase content for telescoping
|
|
494
|
+
const phaseStart = phaseMatch.index + phaseMatch[0].length;
|
|
495
|
+
const remainingContent = content.slice(phaseStart);
|
|
496
|
+
const nextPhaseMatch = remainingContent.match(/^##\s*(?:Phase\s+\d+|[^#])/m);
|
|
497
|
+
const phaseEnd = nextPhaseMatch
|
|
498
|
+
? phaseStart + nextPhaseMatch.index!
|
|
499
|
+
: content.length;
|
|
500
|
+
const phaseContent = content.slice(phaseStart, phaseEnd).trim();
|
|
501
|
+
|
|
502
|
+
const step: StructuralElement = {
|
|
503
|
+
type: 'action_step',
|
|
504
|
+
content: phaseTitle,
|
|
505
|
+
confidence: 1.0,
|
|
506
|
+
sourceLines: { start: lineNum, end: lineNum },
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
// Telescope: parse phase content as nested STC
|
|
510
|
+
if (phaseContent.length > 20) {
|
|
511
|
+
step.telescopesInto = parsePlanContent(phaseContent, `phase_${phaseNum}`, 1);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
steps.push(step);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// If no phases, look for numbered action items
|
|
518
|
+
if (steps.length === 0) {
|
|
519
|
+
const numberedPattern = /^(\d+)\.\s+(.+)$/gm;
|
|
520
|
+
let numMatch;
|
|
521
|
+
while ((numMatch = numberedPattern.exec(content)) !== null) {
|
|
522
|
+
const lineNum = findLineNumber(lines, numMatch[0]);
|
|
523
|
+
steps.push({
|
|
524
|
+
type: 'action_step',
|
|
525
|
+
content: numMatch[2].trim(),
|
|
526
|
+
confidence: 0.7,
|
|
527
|
+
sourceLines: { start: lineNum, end: lineNum },
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Also check for checklist items as action steps
|
|
533
|
+
const checklistPattern = /^-\s*\[([ x])\]\s+(.+)$/gm;
|
|
534
|
+
let checkMatch;
|
|
535
|
+
while ((checkMatch = checklistPattern.exec(content)) !== null) {
|
|
536
|
+
const lineNum = findLineNumber(lines, checkMatch[0]);
|
|
537
|
+
|
|
538
|
+
// Only add if not already captured
|
|
539
|
+
const exists = steps.some(s => s.content === checkMatch![2].trim());
|
|
540
|
+
if (!exists) {
|
|
541
|
+
steps.push({
|
|
542
|
+
type: 'action_step',
|
|
543
|
+
content: checkMatch[2].trim(),
|
|
544
|
+
confidence: 0.6,
|
|
545
|
+
sourceLines: { start: lineNum, end: lineNum },
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return steps;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function extractObservations(
|
|
554
|
+
content: string,
|
|
555
|
+
_lines: string[],
|
|
556
|
+
_desiredOutcome: StructuralElement | null,
|
|
557
|
+
_currentReality: StructuralElement | null,
|
|
558
|
+
_actionSteps: StructuralElement[]
|
|
559
|
+
): string[] {
|
|
560
|
+
const observations: string[] = [];
|
|
561
|
+
|
|
562
|
+
const obsPattern = /(?:note|observation|insight):\s*(.+)/gi;
|
|
563
|
+
let obsMatch;
|
|
564
|
+
while ((obsMatch = obsPattern.exec(content)) !== null) {
|
|
565
|
+
observations.push(obsMatch[1].trim());
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return observations;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function findLineNumber(lines: string[], searchStr: string): number {
|
|
572
|
+
const searchLine = searchStr.split('\n')[0];
|
|
573
|
+
for (let i = 0; i < lines.length; i++) {
|
|
574
|
+
if (lines[i].includes(searchLine.trim().substring(0, 30))) {
|
|
575
|
+
return i + 1;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return 1;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function sanitizeName(str: string): string {
|
|
582
|
+
return str
|
|
583
|
+
.toLowerCase()
|
|
584
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
585
|
+
.replace(/^_|_$/g, '')
|
|
586
|
+
.substring(0, 50);
|
|
587
|
+
}
|
package/src/redis.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// coaiajs/src/redis.ts — Redis key-value operations
|
|
2
|
+
// Parity with coaiapy's tash/fetch pattern
|
|
3
|
+
|
|
4
|
+
import IORedis from 'ioredis';
|
|
5
|
+
import { getConfig } from './config.js';
|
|
6
|
+
import type { CoaiaConfig } from './types.js';
|
|
7
|
+
|
|
8
|
+
const Redis = IORedis.default ?? IORedis;
|
|
9
|
+
type RedisClient = InstanceType<typeof Redis>;
|
|
10
|
+
|
|
11
|
+
let _client: RedisClient | null = null;
|
|
12
|
+
|
|
13
|
+
function buildClient(cfg: CoaiaConfig): RedisClient {
|
|
14
|
+
const r = cfg.redis;
|
|
15
|
+
|
|
16
|
+
// Explicit URL takes priority
|
|
17
|
+
if (r?.url) {
|
|
18
|
+
return new Redis(r.url, { lazyConnect: true });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Upstash REST-style URL (ioredis can connect via rediss:// scheme)
|
|
22
|
+
if (r?.upstashUrl) {
|
|
23
|
+
const url = r.upstashUrl.replace(/^https:\/\//, 'rediss://');
|
|
24
|
+
return new Redis(url, {
|
|
25
|
+
password: r.upstashToken,
|
|
26
|
+
lazyConnect: true,
|
|
27
|
+
tls: {},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Host / port fallback
|
|
32
|
+
return new Redis({
|
|
33
|
+
host: r?.host ?? '127.0.0.1',
|
|
34
|
+
port: r?.port ?? 6379,
|
|
35
|
+
password: r?.password,
|
|
36
|
+
lazyConnect: true,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Get or create the lazy Redis client. */
|
|
41
|
+
export function getClient(): RedisClient {
|
|
42
|
+
if (!_client) {
|
|
43
|
+
_client = buildClient(getConfig());
|
|
44
|
+
}
|
|
45
|
+
return _client;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Store a key-value pair with optional TTL (seconds). */
|
|
49
|
+
export async function tash(
|
|
50
|
+
key: string,
|
|
51
|
+
value: string,
|
|
52
|
+
ttl?: number,
|
|
53
|
+
): Promise<void> {
|
|
54
|
+
const client = getClient();
|
|
55
|
+
if (ttl && ttl > 0) {
|
|
56
|
+
await client.set(key, value, 'EX', ttl);
|
|
57
|
+
} else {
|
|
58
|
+
await client.set(key, value);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Fetch a value by key. Returns null if not found. */
|
|
63
|
+
export async function fetch(key: string): Promise<string | null> {
|
|
64
|
+
const client = getClient();
|
|
65
|
+
return client.get(key);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Delete a key. Returns number of keys removed. */
|
|
69
|
+
export async function del(key: string): Promise<number> {
|
|
70
|
+
const client = getClient();
|
|
71
|
+
return client.del(key);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** List keys matching a pattern. */
|
|
75
|
+
export async function keys(pattern: string): Promise<string[]> {
|
|
76
|
+
const client = getClient();
|
|
77
|
+
return client.keys(pattern);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Check if a key exists. */
|
|
81
|
+
export async function exists(key: string): Promise<boolean> {
|
|
82
|
+
const client = getClient();
|
|
83
|
+
return (await client.exists(key)) === 1;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Gracefully disconnect. */
|
|
87
|
+
export async function disconnect(): Promise<void> {
|
|
88
|
+
if (_client) {
|
|
89
|
+
await _client.quit();
|
|
90
|
+
_client = null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Reset client (testing). */
|
|
95
|
+
export function resetClient(): void {
|
|
96
|
+
_client = null;
|
|
97
|
+
}
|