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,231 @@
1
+ # OpenAI Node.js SDK: Technical Assessment for CoAiA.js
2
+
3
+ > Package selection brief — Unified LLM client for chat completions, streaming, and Whisper transcription replacing coaiapy's raw HTTP calls
4
+
5
+ ## Summary & Recommendation
6
+
7
+ **Use `openai` v6.x SDK** (currently 6.27.0). The official SDK provides type-safe chat completions with streaming, Whisper audio transcription, and image generation — all features coaiajs needs. The SDK handles authentication, retry logic, rate limiting, and model-specific serialization. Combined with `@langfuse/openai` for automatic tracing, this eliminates all raw HTTP request code.
8
+
9
+ **Pin:** `"openai": "^6.0.0"`
10
+
11
+ ## What We're Replacing
12
+
13
+ Coaiapy uses raw HTTP requests for OpenAI API calls, scattered across multiple modules:
14
+
15
+ ```python
16
+ # coaiapy pattern — manual HTTP to OpenAI
17
+ import requests
18
+ import json
19
+
20
+ def call_openai(messages, model="gpt-4"):
21
+ headers = {
22
+ "Authorization": f"Bearer {os.getenv('OPENAI_API_KEY')}",
23
+ "Content-Type": "application/json"
24
+ }
25
+ response = requests.post(
26
+ "https://api.openai.com/v1/chat/completions",
27
+ headers=headers,
28
+ json={"model": model, "messages": messages}
29
+ )
30
+ return response.json()["choices"][0]["message"]["content"]
31
+ ```
32
+
33
+ This pattern: no streaming, no automatic retry, no type safety, no token counting, no multimodal support, and requires manual header management per request.
34
+
35
+ ## Options Compared
36
+
37
+ | Feature | openai SDK v6 | Raw fetch/requests | LangChain OpenAI |
38
+ |---------|--------------|-------------------|------------------|
39
+ | Type safety | Full TypeScript types | Manual interfaces | Via LangChain types |
40
+ | Streaming | `stream: true` + async iterator | Manual SSE parsing | LangChain callbacks |
41
+ | Whisper transcription | `client.audio.transcriptions.create()` | Manual multipart form | Not supported |
42
+ | Image generation | `client.images.generate()` | Manual API call | Via LangChain |
43
+ | Rate limit handling | Built-in retry with backoff | Manual retry loops | Via LangChain |
44
+ | Token counting | Response includes usage | Manual counting | Via callbacks |
45
+ | Langfuse integration | `@langfuse/openai` wrapper | Manual trace creation | `CallbackHandler` |
46
+ | Realtime API | WebSocket support built-in | Manual WebSocket | N/A |
47
+ | Bundle overhead | ~50KB | 0 | ~200KB+ |
48
+ | Maintenance | Official, rapid updates | None | LangChain release cycle |
49
+
50
+ ## API Overview
51
+
52
+ ### Client Setup
53
+
54
+ ```typescript
55
+ import OpenAI from 'openai';
56
+ import { loadConfig } from '../config.js';
57
+
58
+ const config = loadConfig();
59
+ const openai = new OpenAI({
60
+ apiKey: config.openaiApiKey,
61
+ // Optional: custom base URL for Azure OpenAI or local models
62
+ baseURL: config.openaiBaseUrl,
63
+ });
64
+ ```
65
+
66
+ ### Chat Completions (Standard)
67
+
68
+ ```typescript
69
+ async function chatCompletion(
70
+ messages: OpenAI.ChatCompletionMessageParam[],
71
+ model = 'gpt-5.4'
72
+ ): Promise<string> {
73
+ const response = await openai.chat.completions.create({
74
+ model,
75
+ messages,
76
+ temperature: 0.7,
77
+ });
78
+ return response.choices[0]?.message?.content ?? '';
79
+ }
80
+
81
+ // Usage in coaiajs
82
+ const analysis = await chatCompletion([
83
+ { role: 'system', content: 'You are a structural tension chart analyst.' },
84
+ { role: 'user', content: `Analyze this chart: ${JSON.stringify(chart)}` },
85
+ ]);
86
+ ```
87
+
88
+ ### Streaming Chat Completions
89
+
90
+ ```typescript
91
+ async function* streamChat(
92
+ messages: OpenAI.ChatCompletionMessageParam[],
93
+ model = 'gpt-5.4'
94
+ ): AsyncGenerator<string> {
95
+ const stream = await openai.chat.completions.create({
96
+ model,
97
+ messages,
98
+ stream: true,
99
+ });
100
+
101
+ for await (const chunk of stream) {
102
+ const content = chunk.choices[0]?.delta?.content;
103
+ if (content) yield content;
104
+ }
105
+ }
106
+
107
+ // Usage in CLI for real-time output
108
+ async function streamToTerminal(messages: OpenAI.ChatCompletionMessageParam[]) {
109
+ for await (const chunk of streamChat(messages)) {
110
+ process.stdout.write(chunk);
111
+ }
112
+ process.stdout.write('\n');
113
+ }
114
+ ```
115
+
116
+ ### Whisper Transcription (replacing voice-to-text pipeline)
117
+
118
+ ```typescript
119
+ import fs from 'fs';
120
+
121
+ async function transcribeAudio(filePath: string, language = 'en'): Promise<string> {
122
+ const response = await openai.audio.transcriptions.create({
123
+ file: fs.createReadStream(filePath),
124
+ model: 'whisper-1',
125
+ language,
126
+ response_format: 'text',
127
+ });
128
+ return response;
129
+ }
130
+
131
+ // Verbose transcription with timestamps
132
+ async function transcribeVerbose(filePath: string) {
133
+ const response = await openai.audio.transcriptions.create({
134
+ file: fs.createReadStream(filePath),
135
+ model: 'whisper-1',
136
+ response_format: 'verbose_json',
137
+ timestamp_granularities: ['segment'],
138
+ });
139
+ return response; // includes segments with start/end timestamps
140
+ }
141
+ ```
142
+
143
+ ### Image Generation (for chart visualization)
144
+
145
+ ```typescript
146
+ async function generateChartImage(description: string): Promise<string> {
147
+ const response = await openai.images.generate({
148
+ model: 'dall-e-3',
149
+ prompt: `Structural tension chart visualization: ${description}`,
150
+ n: 1,
151
+ size: '1024x1024',
152
+ response_format: 'url',
153
+ });
154
+ return response.data[0]?.url ?? '';
155
+ }
156
+ ```
157
+
158
+ ### With Langfuse Tracing (automatic observability)
159
+
160
+ ```typescript
161
+ import { observeOpenAI } from '@langfuse/openai';
162
+ import OpenAI from 'openai';
163
+ import { getLangfuse } from '../langfuse/client.js';
164
+
165
+ // Wrap OpenAI client for automatic trace capture
166
+ const tracedOpenai = observeOpenAI(new OpenAI(), {
167
+ langfuse: getLangfuse(),
168
+ generationName: 'coaia-generation',
169
+ });
170
+
171
+ // Every API call is automatically traced in Langfuse
172
+ // with model, tokens, latency, cost tracking
173
+ const response = await tracedOpenai.chat.completions.create({
174
+ model: 'gpt-5.4',
175
+ messages: [{ role: 'user', content: 'Evaluate this structural tension...' }],
176
+ });
177
+ ```
178
+
179
+ ### Structured Output with Zod
180
+
181
+ ```typescript
182
+ import { z } from 'zod';
183
+ import { zodResponseFormat } from 'openai/helpers/zod';
184
+
185
+ const ChartAnalysis = z.object({
186
+ tension_score: z.number().min(0).max(10),
187
+ recommendations: z.array(z.string()),
188
+ next_action: z.string(),
189
+ });
190
+
191
+ const response = await openai.beta.chat.completions.parse({
192
+ model: 'gpt-5.4',
193
+ messages: [{ role: 'user', content: `Analyze: ${JSON.stringify(chart)}` }],
194
+ response_format: zodResponseFormat(ChartAnalysis, 'chart_analysis'),
195
+ });
196
+
197
+ const analysis = response.choices[0]?.message?.parsed;
198
+ // analysis is fully typed as { tension_score: number; recommendations: string[]; next_action: string }
199
+ ```
200
+
201
+ ## Integration Plan
202
+
203
+ 1. **Core client:** `src/openai/client.ts` — singleton, config-driven, with Langfuse wrapper
204
+ 2. **Chat module:** `src/openai/chat.ts` — standard + streaming completions
205
+ 3. **Audio module:** `src/openai/audio.ts` — Whisper transcription (replaces voice pipeline)
206
+ 4. **Structured output:** `src/openai/structured.ts` — Zod-validated responses
207
+ 5. **MCP tool integration:** Tools that call OpenAI for analysis, decomposition, evaluation
208
+ 6. **Pipeline steps:** `src/pipeline/generation-step.ts` — traced generation within pipelines
209
+
210
+ ## Version & Ecosystem
211
+
212
+ | Metric | Value |
213
+ |--------|-------|
214
+ | Current version | 6.27.0 (March 2026) |
215
+ | Weekly downloads | ~15M+ |
216
+ | TypeScript | Full native types |
217
+ | Node.js compat | ≥20 LTS |
218
+ | Runtime support | Node.js, Deno, Bun, edge |
219
+ | Streaming | SSE async iterator |
220
+ | Structured output | Zod helper built-in |
221
+ | Models supported | GPT-5.x, GPT-4.x, Whisper, DALL-E, Realtime |
222
+ | Langfuse integration | `@langfuse/openai` drop-in wrapper |
223
+ | License | Apache-2.0 |
224
+
225
+ ## References
226
+
227
+ - npm: https://www.npmjs.com/package/openai
228
+ - GitHub: https://github.com/openai/openai-node
229
+ - API docs: https://developers.openai.com/api/docs/libraries/
230
+ - DeepWiki: https://deepwiki.com/openai/openai-node
231
+ - Releases: https://github.com/openai/openai-node/releases
@@ -0,0 +1,266 @@
1
+ # YAML Parsing in Node.js: Technical Assessment for CoAiA.js
2
+
3
+ > Package selection brief — YAML parser for pipeline templates, session files, and config loading replacing coaiapy's PyYAML
4
+
5
+ ## Summary & Recommendation
6
+
7
+ **Use `yaml` v2.x** (the npm package named simply `yaml`). Despite `js-yaml` having higher download numbers, the modern `yaml` package offers streaming support for large files, comment preservation (critical for human-edited pipeline templates), and a cleaner API. CoAiA pipeline templates are user-authored YAML that benefits from round-trip fidelity — comments survive parse→stringify cycles.
8
+
9
+ **Pin:** `"yaml": "^2.6.0"`
10
+
11
+ ## What We're Replacing
12
+
13
+ Coaiapy uses PyYAML for pipeline template loading:
14
+
15
+ ```python
16
+ # coaiapy/pipeline.py — PyYAML pattern
17
+ import yaml
18
+
19
+ # Load pipeline template
20
+ for template_file in search_path.glob("*.yaml"):
21
+ with open(yaml_file, 'r') as f:
22
+ data = yaml.safe_load(f) # Parse YAML → dict
23
+
24
+ # Pipeline template format:
25
+ # name: "trace-session"
26
+ # version: "1.0"
27
+ # variables:
28
+ # - name: session_id
29
+ # type: string
30
+ # required: true
31
+ # steps:
32
+ # - name: create_trace
33
+ # observation_type: SPAN
34
+ ```
35
+
36
+ PyYAML's `safe_load` works well; the JavaScript equivalent needs the same safety guarantees plus TypeScript types.
37
+
38
+ ## Options Compared
39
+
40
+ | Feature | yaml v2.6 | js-yaml v4.1 |
41
+ |---------|-----------|-------------|
42
+ | Weekly npm downloads | ~80M | ~130M |
43
+ | GitHub stars | ~1,600 | ~6,500 |
44
+ | Parse speed (typical files) | Good | Faster (~10%) |
45
+ | Stringify speed | Faster | Slower |
46
+ | Streaming support | ✅ Parse & stringify | ❌ Memory only |
47
+ | Comment preservation | ✅ Round-trip fidelity | ❌ Comments lost |
48
+ | Anchor/alias support | ✅ Full | ✅ Full |
49
+ | Custom tags | ✅ Flexible API | ✅ Schema-based |
50
+ | TypeScript | Native types | `@types/js-yaml` |
51
+ | YAML 1.2 compliance | ✅ Full | ⚠️ Mostly |
52
+ | Security (CVEs) | None known | CVE-2025-64718 (fixed in 4.1.1) |
53
+ | API style | `parse(str)`, `stringify(obj)` | `load(str)`, `dump(obj)` |
54
+ | Bundle size | ~45KB | ~30KB |
55
+ | Dependencies | 0 | 0 |
56
+
57
+ ## API Overview
58
+
59
+ ### Basic Parse & Stringify
60
+
61
+ ```typescript
62
+ import { parse, stringify } from 'yaml';
63
+ import { readFile, writeFile } from 'fs/promises';
64
+
65
+ // Parse YAML file
66
+ async function loadYaml<T>(filePath: string): Promise<T> {
67
+ const content = await readFile(filePath, 'utf-8');
68
+ return parse(content) as T;
69
+ }
70
+
71
+ // Stringify to YAML
72
+ async function saveYaml(filePath: string, data: unknown): Promise<void> {
73
+ const yamlStr = stringify(data, {
74
+ indent: 2,
75
+ lineWidth: 120,
76
+ });
77
+ await writeFile(filePath, yamlStr, 'utf-8');
78
+ }
79
+ ```
80
+
81
+ ### Pipeline Template Loading (replacing pipeline.py)
82
+
83
+ ```typescript
84
+ import { parse } from 'yaml';
85
+ import { readFile } from 'fs/promises';
86
+ import { glob } from 'glob';
87
+ import { z } from 'zod';
88
+
89
+ // Schema (validated with Zod after YAML parse)
90
+ const PipelineTemplateSchema = z.object({
91
+ name: z.string().min(1),
92
+ version: z.string().default('1.0'),
93
+ variables: z.array(z.object({
94
+ name: z.string().min(1),
95
+ type: z.enum(['string', 'number', 'boolean', 'list']).default('string'),
96
+ required: z.boolean().default(true),
97
+ default: z.unknown().optional(),
98
+ choices: z.array(z.unknown()).optional(),
99
+ })).default([]),
100
+ steps: z.array(z.object({
101
+ name: z.string().min(1),
102
+ observation_type: z.enum(['EVENT', 'SPAN', 'GENERATION']).default('EVENT'),
103
+ conditional: z.string().optional(),
104
+ })).min(1),
105
+ });
106
+
107
+ type PipelineTemplate = z.infer<typeof PipelineTemplateSchema>;
108
+
109
+ async function loadPipelineTemplates(templateDir: string): Promise<Map<string, PipelineTemplate>> {
110
+ const templates = new Map<string, PipelineTemplate>();
111
+ const files = await glob('*.yaml', { cwd: templateDir, absolute: true });
112
+
113
+ for (const file of files) {
114
+ const raw = parse(await readFile(file, 'utf-8'));
115
+ const template = PipelineTemplateSchema.parse(raw);
116
+ templates.set(template.name, template);
117
+ }
118
+
119
+ return templates;
120
+ }
121
+ ```
122
+
123
+ ### Comment-Preserving Round-Trip (unique to `yaml` package)
124
+
125
+ ```typescript
126
+ import { parseDocument, stringify } from 'yaml';
127
+ import { readFile, writeFile } from 'fs/promises';
128
+
129
+ // Load, modify, save — preserving user comments
130
+ async function updateTemplateVersion(filePath: string, newVersion: string): Promise<void> {
131
+ const content = await readFile(filePath, 'utf-8');
132
+ const doc = parseDocument(content); // Preserves comments, anchors, formatting
133
+
134
+ doc.set('version', newVersion);
135
+
136
+ await writeFile(filePath, doc.toString(), 'utf-8');
137
+ // Original comments and formatting are preserved!
138
+ }
139
+
140
+ // Example input:
141
+ // # Pipeline for session tracing
142
+ // name: trace-session
143
+ // version: "1.0" # Bump this on changes
144
+ //
145
+ // After update, the comment "# Bump this on changes" survives
146
+ ```
147
+
148
+ ### Session File Persistence
149
+
150
+ ```typescript
151
+ import { parse, stringify } from 'yaml';
152
+ import { readFile, writeFile } from 'fs/promises';
153
+
154
+ interface SessionFile {
155
+ id: string;
156
+ created: string;
157
+ traces: { id: string; name: string; status: string }[];
158
+ charts: { id: string; outcome: string }[];
159
+ metadata: Record<string, unknown>;
160
+ }
161
+
162
+ async function saveSession(filePath: string, session: SessionFile): Promise<void> {
163
+ await writeFile(filePath, stringify(session, {
164
+ indent: 2,
165
+ lineWidth: 120,
166
+ sortMapEntries: false, // Preserve insertion order
167
+ }), 'utf-8');
168
+ }
169
+
170
+ async function loadSession(filePath: string): Promise<SessionFile> {
171
+ const content = await readFile(filePath, 'utf-8');
172
+ return parse(content) as SessionFile;
173
+ }
174
+ ```
175
+
176
+ ### Streaming for Large Files
177
+
178
+ ```typescript
179
+ import { parseAllDocuments } from 'yaml';
180
+ import { createReadStream } from 'fs';
181
+
182
+ // Stream-parse a multi-document YAML file
183
+ async function parseMultiDoc(filePath: string) {
184
+ const content = await readFile(filePath, 'utf-8');
185
+ const docs = parseAllDocuments(content);
186
+
187
+ for (const doc of docs) {
188
+ if (doc.errors.length > 0) {
189
+ console.error(`Parse errors in ${filePath}:`, doc.errors);
190
+ continue;
191
+ }
192
+ yield doc.toJSON();
193
+ }
194
+ }
195
+ ```
196
+
197
+ ### Config File Loading
198
+
199
+ ```typescript
200
+ import { parse } from 'yaml';
201
+ import { readFile } from 'fs/promises';
202
+ import { existsSync } from 'fs';
203
+
204
+ // Load coaia.json or coaia.yaml config
205
+ async function loadConfigFile(configPath: string): Promise<Record<string, unknown>> {
206
+ if (!existsSync(configPath)) return {};
207
+
208
+ const content = await readFile(configPath, 'utf-8');
209
+
210
+ if (configPath.endsWith('.yaml') || configPath.endsWith('.yml')) {
211
+ return parse(content) ?? {};
212
+ }
213
+ if (configPath.endsWith('.json')) {
214
+ return JSON.parse(content);
215
+ }
216
+
217
+ // Try YAML first (superset of JSON)
218
+ try {
219
+ return parse(content) ?? {};
220
+ } catch {
221
+ return JSON.parse(content);
222
+ }
223
+ }
224
+ ```
225
+
226
+ ## Integration Plan
227
+
228
+ 1. **Core utility:** `src/yaml.ts` — typed `loadYaml<T>()` and `saveYaml()` wrappers
229
+ 2. **Pipeline templates:** `src/pipeline/template-loader.ts` — YAML template discovery + Zod validation
230
+ 3. **Session files:** `src/session/file.ts` — YAML session persistence
231
+ 4. **Config loading:** `src/config.ts` — support `.yaml` config alongside `.json`
232
+ 5. **Round-trip editing:** Use `parseDocument()` for user-facing template modifications
233
+ 6. **Multi-doc:** Support `---` separated YAML documents for batch operations
234
+
235
+ ## Why Not js-yaml?
236
+
237
+ 1. **Comment preservation**: Pipeline templates are human-authored; losing comments on round-trip is unacceptable
238
+ 2. **Security**: CVE-2025-64718 (prototype pollution) was a recent concern; `yaml` has a clean record
239
+ 3. **YAML 1.2**: Full compliance matters for interop with other tools
240
+ 4. **Streaming**: Future-proofing for large narrative JSONL files converted to/from YAML
241
+ 5. **TypeScript**: Native types without DefinitelyTyped dependency
242
+
243
+ The ~10% parse speed advantage of js-yaml is irrelevant for config/template files (sub-millisecond either way).
244
+
245
+ ## Version & Ecosystem
246
+
247
+ | Metric | Value |
248
+ |--------|-------|
249
+ | Current version | 2.6.x (2026) |
250
+ | Weekly downloads | ~80M |
251
+ | TypeScript | Native types |
252
+ | Dependencies | 0 |
253
+ | YAML spec | 1.2 compliant |
254
+ | Comment round-trip | ✅ Full support |
255
+ | Streaming | ✅ Parse & stringify |
256
+ | Node.js compat | ≥14 (we target ≥20) |
257
+ | License | ISC |
258
+
259
+ ## References
260
+
261
+ - npm: https://www.npmjs.com/package/yaml
262
+ - GitHub: https://github.com/eemeli/yaml
263
+ - js-yaml: https://www.npmjs.com/package/js-yaml
264
+ - Comparison: https://npm-compare.com/js-yaml,yaml
265
+ - Performance: https://github.com/eemeli/yaml/discussions/358
266
+ - CVE-2025-64718: https://www.cvedetails.com/cve/CVE-2025-64718/
@@ -0,0 +1,212 @@
1
+ # Zod Runtime Validation: Technical Assessment for CoAiA.js
2
+
3
+ > Package selection brief — TypeScript-first schema validation replacing coaia-narrative's custom 181-line validation.ts
4
+
5
+ ## Summary & Recommendation
6
+
7
+ **Use `zod` v4.x** as the unified validation layer for all coaiajs modules. Zod replaces coaia-narrative's custom `validation.ts` (181 lines of hand-rolled recursive validation) with a standard, composable, type-inferring schema library. The MCP SDK already requires Zod as a peer dependency for tool argument schemas — using it everywhere eliminates dual validation code.
8
+
9
+ **Pin:** `"zod": "^4.0.0"` (v4.3.6 stable, required by `@modelcontextprotocol/sdk`)
10
+
11
+ ## What We're Replacing
12
+
13
+ coaia-narrative has a custom `validation.ts` implementing recursive schema validation:
14
+
15
+ ```typescript
16
+ // coaia-narrative/src/validation.ts — 181 lines of custom validation
17
+ type ValidationType = 'string' | 'number' | 'boolean' | 'array' | 'object' | 'date' | 'enum';
18
+
19
+ interface ValidationRule {
20
+ type: ValidationType;
21
+ required?: boolean;
22
+ minLength?: number;
23
+ maxLength?: number;
24
+ pattern?: RegExp;
25
+ minValue?: number;
26
+ maxValue?: number;
27
+ enumValues?: (string | number)[];
28
+ items?: ValidationRule; // array items schema
29
+ properties?: Record<string, ValidationRule>; // object properties
30
+ }
31
+
32
+ export function validate(args: any, schema: ValidationSchema): { valid: boolean; error?: string }
33
+ ```
34
+
35
+ Pre-built schemas: `stringArray()`, `entityArray()`, `relationArray()`, `isoDate()`, `nonEmptyString()`.
36
+
37
+ This works but: no type inference, no composition, no transform/coerce, no JSON Schema export, and every new validation type requires manual code.
38
+
39
+ ## Options Compared
40
+
41
+ | Feature | Zod v4 | Custom validation.ts | io-ts | Yup |
42
+ |---------|--------|---------------------|-------|-----|
43
+ | Type inference | ✅ Automatic `z.infer<>` | ❌ Manual types | ✅ (verbose) | ⚠️ Partial |
44
+ | Schema composition | ✅ `.merge()`, `.extend()`, `.pick()` | ❌ Manual nesting | ✅ | ✅ |
45
+ | MCP SDK compat | ✅ Required peer dep | ❌ N/A | ❌ | ❌ |
46
+ | JSON Schema export | ✅ `toJSONSchema()` | ❌ N/A | ❌ | ❌ |
47
+ | Transform/coerce | ✅ `.transform()`, `.coerce` | ❌ N/A | ❌ | ✅ |
48
+ | Error messages | ✅ Structured `ZodError` | ⚠️ Single string | ⚠️ Verbose | ✅ |
49
+ | Bundle size | ~2KB gzip (core) | ~3KB | ~15KB | ~12KB |
50
+ | Weekly downloads | ~100M+ | N/A | ~8M | ~15M |
51
+ | Performance (v4) | 14x faster strings, 7x faster arrays vs v3 | Adequate | Slower | Slower |
52
+
53
+ ## API Overview
54
+
55
+ ### Replacing Custom Schemas
56
+
57
+ ```typescript
58
+ import { z } from 'zod';
59
+
60
+ // Replaces: ValidationSchemas.nonEmptyString()
61
+ const NonEmptyString = z.string().min(1);
62
+
63
+ // Replaces: ValidationSchemas.isoDate()
64
+ const IsoDate = z.string().datetime();
65
+
66
+ // Replaces: ValidationSchemas.stringArray(minLength)
67
+ const StringArray = (min = 0) => z.array(z.string()).min(min);
68
+
69
+ // Replaces: ValidationSchemas.entityArray()
70
+ const Entity = z.object({
71
+ name: z.string().min(1),
72
+ entityType: z.string().min(1),
73
+ observations: z.array(z.string()).default([]),
74
+ });
75
+ const EntityArray = z.array(Entity).min(1);
76
+
77
+ // Replaces: ValidationSchemas.relationArray()
78
+ const Relation = z.object({
79
+ from: z.string().min(1),
80
+ to: z.string().min(1),
81
+ relationType: z.string().min(1),
82
+ });
83
+ const RelationArray = z.array(Relation);
84
+ ```
85
+
86
+ ### Structural Tension Chart Schema
87
+
88
+ ```typescript
89
+ const ChartSchema = z.object({
90
+ id: z.string().min(1),
91
+ desiredOutcome: z.string().min(1),
92
+ currentReality: z.string().min(1),
93
+ dueDate: z.string().datetime().optional(),
94
+ actionSteps: z.array(z.object({
95
+ title: z.string().min(1),
96
+ status: z.enum(['pending', 'in_progress', 'done', 'blocked']),
97
+ currentReality: z.string().optional(),
98
+ })).default([]),
99
+ createdAt: z.string().datetime(),
100
+ updatedAt: z.string().datetime(),
101
+ });
102
+
103
+ type Chart = z.infer<typeof ChartSchema>; // TypeScript type auto-generated
104
+ ```
105
+
106
+ ### MCP Tool Argument Validation
107
+
108
+ ```typescript
109
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
110
+ import { z } from 'zod';
111
+
112
+ const server = new McpServer({ name: 'coaiajs', version: '1.0.0' });
113
+
114
+ // Zod schemas ARE the MCP tool input schemas — zero duplication
115
+ server.tool(
116
+ 'create_chart',
117
+ 'Create a structural tension chart',
118
+ {
119
+ desiredOutcome: z.string().describe('What you want to CREATE'),
120
+ currentReality: z.string().describe('Factual assessment of current state'),
121
+ dueDate: z.string().datetime().optional().describe('Target date (ISO 8601)'),
122
+ actionSteps: z.array(z.string()).optional().describe('Initial action steps'),
123
+ },
124
+ async ({ desiredOutcome, currentReality, dueDate, actionSteps }) => {
125
+ // Arguments are already validated and typed
126
+ const chart = await createChart({ desiredOutcome, currentReality, dueDate, actionSteps });
127
+ return { content: [{ type: 'text', text: JSON.stringify(chart) }] };
128
+ }
129
+ );
130
+ ```
131
+
132
+ ### Pipeline Template Validation
133
+
134
+ ```typescript
135
+ // Replaces coaiapy's pipeline.py validate_variables()
136
+ const PipelineVariable = z.object({
137
+ name: z.string().min(1),
138
+ type: z.enum(['string', 'number', 'boolean', 'list']).default('string'),
139
+ required: z.boolean().default(true),
140
+ default: z.unknown().optional(),
141
+ choices: z.array(z.unknown()).optional(),
142
+ });
143
+
144
+ const PipelineTemplate = z.object({
145
+ name: z.string().min(1),
146
+ version: z.string().default('1.0'),
147
+ variables: z.array(PipelineVariable).default([]),
148
+ steps: z.array(z.object({
149
+ name: z.string().min(1),
150
+ observation_type: z.enum(['EVENT', 'SPAN', 'GENERATION']).default('EVENT'),
151
+ conditional: z.string().optional(),
152
+ })).min(1),
153
+ });
154
+
155
+ // Validate and get typed result
156
+ function loadTemplate(raw: unknown): PipelineTemplate {
157
+ return PipelineTemplate.parse(raw); // throws ZodError on invalid
158
+ }
159
+ ```
160
+
161
+ ### Error Handling
162
+
163
+ ```typescript
164
+ import { z } from 'zod';
165
+
166
+ function validateSafe<T>(schema: z.ZodSchema<T>, data: unknown): { valid: true; data: T } | { valid: false; error: string } {
167
+ const result = schema.safeParse(data);
168
+ if (result.success) {
169
+ return { valid: true, data: result.data };
170
+ }
171
+ // Structured error messages
172
+ const messages = result.error.issues.map(i => `${i.path.join('.')}: ${i.message}`);
173
+ return { valid: false, error: messages.join('; ') };
174
+ }
175
+ ```
176
+
177
+ ## Integration Plan
178
+
179
+ 1. **Shared schemas:** `src/schemas/` directory with reusable Zod schemas
180
+ - `chart.ts` — ChartSchema, ActionStepSchema
181
+ - `entity.ts` — Entity, Relation schemas (replaces validation.ts)
182
+ - `pipeline.ts` — PipelineTemplate, PipelineVariable
183
+ - `session.ts` — Session, Trace schemas
184
+ 2. **MCP tools:** Direct Zod schemas in tool definitions (no separate validation layer)
185
+ 3. **Config validation:** Zod for config file/env var validation at startup
186
+ 4. **JSON Schema export:** `toJSONSchema()` for documentation and interop
187
+ 5. **Migration:** Delete coaia-narrative's `validation.ts`, import from `@coaiajs/schemas`
188
+
189
+ ### @zod/mini for Future Frontend
190
+
191
+ If coaiajs adds a web UI, use `@zod/mini` (~1.9KB) for client-side validation with the same schemas.
192
+
193
+ ## Version & Ecosystem
194
+
195
+ | Metric | Value |
196
+ |--------|-------|
197
+ | Current version | 4.3.6 (Jan 2026) |
198
+ | Weekly downloads | ~100M+ |
199
+ | TypeScript | First-class, type inference is the core value |
200
+ | Bundle size | ~2KB gzip (core), ~1.9KB (@zod/mini) |
201
+ | MCP SDK | Required peer dependency |
202
+ | JSON Schema | `toJSONSchema()` built-in |
203
+ | Node.js compat | Any (zero native deps) |
204
+ | License | MIT |
205
+
206
+ ## References
207
+
208
+ - npm: https://www.npmjs.com/package/zod
209
+ - GitHub: https://github.com/colinhacks/zod
210
+ - v4 announcement: https://www.infoq.com/news/2025/08/zod-v4-available/
211
+ - What's new in v4: https://basicutils.com/learn/zod/whats-new-in-zod-v4
212
+ - MCP SDK peer dep: https://www.npmjs.com/package/@modelcontextprotocol/sdk