coaiajs 0.3.0 → 0.3.2

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 (179) hide show
  1. package/KINSHIP.md +1 -1
  2. package/README.md +191 -75
  3. package/dist/mcp/config.d.ts.map +1 -0
  4. package/dist/mcp/config.js +0 -2
  5. package/dist/mcp/config.js.map +1 -0
  6. package/dist/mcp/prompts.d.ts.map +1 -0
  7. package/dist/mcp/prompts.js.map +1 -0
  8. package/dist/mcp/resources.d.ts.map +1 -0
  9. package/dist/mcp/resources.js.map +1 -0
  10. package/dist/mcp/server.d.ts.map +1 -0
  11. package/dist/mcp/server.js +1 -1
  12. package/dist/mcp/server.js.map +1 -0
  13. package/dist/mcp/tools/coaiapy-tools.d.ts.map +1 -0
  14. package/dist/mcp/tools/coaiapy-tools.js.map +1 -0
  15. package/dist/mcp/tools/index.d.ts.map +1 -0
  16. package/dist/mcp/tools/index.js.map +1 -0
  17. package/dist/src/audio.d.ts.map +1 -0
  18. package/dist/src/audio.js.map +1 -0
  19. package/dist/src/cli-helpers.d.ts.map +1 -0
  20. package/dist/src/cli-helpers.js.map +1 -0
  21. package/dist/src/cli.d.ts.map +1 -0
  22. package/dist/src/cli.js +1 -0
  23. package/dist/src/cli.js.map +1 -0
  24. package/dist/src/config.d.ts.map +1 -0
  25. package/dist/src/config.js.map +1 -0
  26. package/dist/src/environment.d.ts.map +1 -0
  27. package/dist/src/environment.js.map +1 -0
  28. package/dist/src/github.d.ts.map +1 -0
  29. package/dist/src/github.js.map +1 -0
  30. package/dist/src/index.d.ts +1 -0
  31. package/dist/src/index.d.ts.map +1 -0
  32. package/dist/src/index.js +1 -0
  33. package/dist/src/index.js.map +1 -0
  34. package/dist/src/langfuse/client.d.ts.map +1 -0
  35. package/dist/src/langfuse/client.js.map +1 -0
  36. package/dist/src/langfuse/comments.d.ts.map +1 -0
  37. package/dist/src/langfuse/comments.js.map +1 -0
  38. package/dist/src/langfuse/datasets.d.ts.map +1 -0
  39. package/dist/src/langfuse/datasets.js.map +1 -0
  40. package/dist/src/langfuse/index.d.ts +1 -0
  41. package/dist/src/langfuse/index.d.ts.map +1 -0
  42. package/dist/src/langfuse/index.js.map +1 -0
  43. package/dist/src/langfuse/media.d.ts.map +1 -0
  44. package/dist/src/langfuse/media.js.map +1 -0
  45. package/dist/src/langfuse/observations.d.ts.map +1 -0
  46. package/dist/src/langfuse/observations.js.map +1 -0
  47. package/dist/src/langfuse/prompts.d.ts +5 -1
  48. package/dist/src/langfuse/prompts.d.ts.map +1 -0
  49. package/dist/src/langfuse/prompts.js +16 -3
  50. package/dist/src/langfuse/prompts.js.map +1 -0
  51. package/dist/src/langfuse/scores.d.ts.map +1 -0
  52. package/dist/src/langfuse/scores.js.map +1 -0
  53. package/dist/src/langfuse/traces.d.ts.map +1 -0
  54. package/dist/src/langfuse/traces.js.map +1 -0
  55. package/dist/src/llm.d.ts.map +1 -0
  56. package/dist/src/llm.js.map +1 -0
  57. package/dist/src/narrative/graph-manager.d.ts.map +1 -0
  58. package/dist/src/narrative/graph-manager.js.map +1 -0
  59. package/dist/src/narrative/index.d.ts.map +1 -0
  60. package/dist/src/narrative/index.js.map +1 -0
  61. package/dist/src/narrative/markdown-export.d.ts.map +1 -0
  62. package/dist/src/narrative/markdown-export.js.map +1 -0
  63. package/dist/src/narrative/tool-definitions.d.ts.map +1 -0
  64. package/dist/src/narrative/tool-definitions.js.map +1 -0
  65. package/dist/src/narrative/tool-handlers.d.ts.map +1 -0
  66. package/dist/src/narrative/tool-handlers.js.map +1 -0
  67. package/dist/src/narrative/types.d.ts.map +1 -0
  68. package/dist/src/narrative/types.js.map +1 -0
  69. package/dist/src/narrative/validation.d.ts.map +1 -0
  70. package/dist/src/narrative/validation.js.map +1 -0
  71. package/dist/src/pde/index.d.ts.map +1 -0
  72. package/dist/src/pde/index.js.map +1 -0
  73. package/dist/src/pde/mcp-handlers.d.ts.map +1 -0
  74. package/dist/src/pde/mcp-handlers.js.map +1 -0
  75. package/dist/src/pde/mcp-tools.d.ts.map +1 -0
  76. package/dist/src/pde/mcp-tools.js.map +1 -0
  77. package/dist/src/pde/session-manager.d.ts.map +1 -0
  78. package/dist/src/pde/session-manager.js.map +1 -0
  79. package/dist/src/pde/stc-mapper.d.ts.map +1 -0
  80. package/dist/src/pde/stc-mapper.js.map +1 -0
  81. package/dist/src/pipeline/index.d.ts.map +1 -0
  82. package/dist/src/pipeline/index.js.map +1 -0
  83. package/dist/src/pipeline/template-engine.d.ts.map +1 -0
  84. package/dist/src/pipeline/template-engine.js.map +1 -0
  85. package/dist/src/planning/index.d.ts.map +1 -0
  86. package/dist/src/planning/index.js.map +1 -0
  87. package/dist/src/planning/mcp-handlers.d.ts.map +1 -0
  88. package/dist/src/planning/mcp-handlers.js.map +1 -0
  89. package/dist/src/planning/mcp-tools.d.ts.map +1 -0
  90. package/dist/src/planning/mcp-tools.js.map +1 -0
  91. package/dist/src/planning/plan-parser.d.ts.map +1 -0
  92. package/dist/src/planning/plan-parser.js.map +1 -0
  93. package/dist/src/redis.d.ts.map +1 -0
  94. package/dist/src/redis.js.map +1 -0
  95. package/dist/src/types.d.ts.map +1 -0
  96. package/dist/src/types.js.map +1 -0
  97. package/dist/src/version.d.ts.map +1 -0
  98. package/dist/src/version.js.map +1 -0
  99. package/llms-full.txt +361 -0
  100. package/llms.txt +43 -0
  101. package/package.json +79 -12
  102. package/.claude/settings.local.json +0 -17
  103. package/.claude-plugin/marketplace.json +0 -26
  104. package/articles/academic/NOTES.md +0 -1
  105. package/articles/academic/README.md +0 -1
  106. package/articles/academic/creative-orientation-vs-problem-solving.md +0 -177
  107. package/articles/academic/jsonl-knowledge-graphs-agent-memory.md +0 -142
  108. package/articles/academic/langfuse-observability-llm-pipelines.md +0 -144
  109. package/articles/academic/medicine-wheel-software-architecture.md +0 -163
  110. package/articles/academic/mmot-autonomous-agents.md +0 -156
  111. package/articles/academic/model-context-protocol-interagent.md +0 -161
  112. package/articles/academic/pde-prompt-decomposition.md +0 -186
  113. package/articles/academic/structural-tension-in-ai-agents.md +0 -134
  114. package/articles/reviews/mcp-protocol-design-review.md +0 -170
  115. package/articles/reviews/observability-ai-systems-review.md +0 -176
  116. package/articles/reviews/prompt-engineering-decomposition-review.md +0 -184
  117. package/articles/surveys/agent-orchestration-survey.md +0 -186
  118. package/articles/surveys/knowledge-graph-storage-survey.md +0 -204
  119. package/articles/surveys/structural-tension-methodology-survey.md +0 -154
  120. package/articles/technical/aws-sdk-v3-polly.md +0 -270
  121. package/articles/technical/commander-cli-framework.md +0 -262
  122. package/articles/technical/dotenv-config-patterns.md +0 -360
  123. package/articles/technical/ioredis-vs-redis.md +0 -142
  124. package/articles/technical/langfuse-js-sdk-vs-rest.md +0 -191
  125. package/articles/technical/mcp-sdk-typescript.md +0 -291
  126. package/articles/technical/octokit-github-api.md +0 -293
  127. package/articles/technical/openai-sdk-modern.md +0 -231
  128. package/articles/technical/yaml-parsing-node.md +0 -266
  129. package/articles/technical/zod-runtime-validation.md +0 -212
  130. package/coaiajs-langfuse-claude-plugin/.claude-plugin/marketplace.json +0 -16
  131. package/coaiajs-langfuse-claude-plugin/.claude-plugin/plugin.json +0 -10
  132. package/coaiajs-langfuse-claude-plugin/.mcp.json +0 -8
  133. package/coaiajs-langfuse-claude-plugin/README.md +0 -60
  134. package/mcp/config.ts +0 -225
  135. package/mcp/prompts.ts +0 -131
  136. package/mcp/resources.ts +0 -84
  137. package/mcp/server.ts +0 -519
  138. package/mcp/tools/coaiapy-tools.ts +0 -366
  139. package/mcp/tools/index.ts +0 -4
  140. package/src/audio.ts +0 -76
  141. package/src/cli-helpers.ts +0 -86
  142. package/src/cli.ts +0 -1261
  143. package/src/config.ts +0 -207
  144. package/src/environment.ts +0 -171
  145. package/src/github.ts +0 -143
  146. package/src/index.ts +0 -82
  147. package/src/langfuse/client.ts +0 -105
  148. package/src/langfuse/comments.ts +0 -52
  149. package/src/langfuse/datasets.ts +0 -178
  150. package/src/langfuse/index.ts +0 -37
  151. package/src/langfuse/media.ts +0 -193
  152. package/src/langfuse/observations.ts +0 -155
  153. package/src/langfuse/prompts.ts +0 -157
  154. package/src/langfuse/scores.ts +0 -456
  155. package/src/langfuse/traces.ts +0 -353
  156. package/src/llm.ts +0 -106
  157. package/src/narrative/graph-manager.ts +0 -1358
  158. package/src/narrative/index.ts +0 -188
  159. package/src/narrative/markdown-export.ts +0 -535
  160. package/src/narrative/tool-definitions.ts +0 -635
  161. package/src/narrative/tool-handlers.ts +0 -528
  162. package/src/narrative/types.ts +0 -9
  163. package/src/narrative/validation.ts +0 -179
  164. package/src/pde/index.ts +0 -34
  165. package/src/pde/mcp-handlers.ts +0 -359
  166. package/src/pde/mcp-tools.ts +0 -201
  167. package/src/pde/session-manager.ts +0 -248
  168. package/src/pde/stc-mapper.ts +0 -298
  169. package/src/pipeline/index.ts +0 -7
  170. package/src/pipeline/template-engine.ts +0 -398
  171. package/src/planning/index.ts +0 -32
  172. package/src/planning/mcp-handlers.ts +0 -369
  173. package/src/planning/mcp-tools.ts +0 -155
  174. package/src/planning/plan-parser.ts +0 -587
  175. package/src/redis.ts +0 -111
  176. package/src/types.ts +0 -281
  177. package/src/version.ts +0 -39
  178. package/test/config.test.mjs +0 -93
  179. package/tsconfig.json +0 -26
@@ -1,298 +0,0 @@
1
- /**
2
- * STC Mapper — Transforms mcp-pde DecompositionResult into Entity[]/Relation[]
3
- * Ported from coaia-pde/src/stc-mapper.ts
4
- */
5
-
6
- import { v4 as uuidv4 } from 'uuid';
7
- import type {
8
- DecompositionResult,
9
- Entity,
10
- Relation,
11
- EntityMetadata,
12
- } from '../types.js';
13
- import { URGENCY_DAYS } from '../types.js';
14
-
15
- export class StcMapper {
16
-
17
- /**
18
- * Core method: Transform a DecompositionResult into Entity[]/Relation[].
19
- */
20
- mapDecompositionToChart(
21
- result: DecompositionResult,
22
- originalPrompt: string,
23
- options?: { pdeId?: string; dueDate?: string }
24
- ): { entities: Entity[]; relations: Relation[]; chartId: string } {
25
- const chartId = `chart_${uuidv4().substring(0, 8)}`;
26
- const now = new Date().toISOString();
27
- const dueDate = options?.dueDate || this.calculateDueDate(result.primary.urgency);
28
- const fourDirections = this.mapDirectionsToFourDirections(result.directions);
29
-
30
- const entities: Entity[] = [];
31
- const relations: Relation[] = [];
32
-
33
- // 1. Master chart entity
34
- entities.push({
35
- name: `${chartId}_chart`,
36
- entityType: 'structural_tension_chart',
37
- observations: [`Master chart for: ${result.primary.action} ${result.primary.target}`],
38
- metadata: {
39
- chartId,
40
- dueDate,
41
- level: 0,
42
- phase: 'germination',
43
- createdAt: now,
44
- updatedAt: now,
45
- pdeId: options?.pdeId,
46
- fourDirections,
47
- }
48
- });
49
-
50
- // 2. Desired outcome entity
51
- const desiredOutcomeObs = [this.formatDesiredOutcome(result.primary)];
52
- for (const a of result.outputs.artifacts) desiredOutcomeObs.push(`Artifact: ${a}`);
53
- for (const u of result.outputs.updates) desiredOutcomeObs.push(`Update: ${u}`);
54
- for (const c of result.outputs.communications) desiredOutcomeObs.push(`Communication: ${c}`);
55
-
56
- entities.push({
57
- name: `${chartId}_desired_outcome`,
58
- entityType: 'desired_outcome',
59
- observations: desiredOutcomeObs,
60
- metadata: {
61
- chartId,
62
- confidence: result.primary.confidence,
63
- createdAt: now,
64
- updatedAt: now,
65
- }
66
- });
67
-
68
- // 3. Current reality entity
69
- entities.push({
70
- name: `${chartId}_current_reality`,
71
- entityType: 'current_reality',
72
- observations: this.formatCurrentReality(result.context, result.ambiguities),
73
- metadata: {
74
- chartId,
75
- createdAt: now,
76
- updatedAt: now,
77
- }
78
- });
79
-
80
- // 4. Action step entities from secondary[] + actionStack[]
81
- const totalActionCount = result.secondary.length + result.actionStack.length;
82
- const actionDueDates = this.distributeActionDates(new Date(), new Date(dueDate), totalActionCount);
83
- let actionIndex = 0;
84
-
85
- for (const sec of result.secondary) {
86
- const actionName = `${chartId}_action_${++actionIndex}`;
87
-
88
- entities.push({
89
- name: actionName,
90
- entityType: 'action_step',
91
- observations: [`${sec.action} ${sec.target}`],
92
- metadata: {
93
- chartId,
94
- dueDate: actionDueDates[actionIndex - 1]?.toISOString(),
95
- completionStatus: false,
96
- level: 1,
97
- parentChart: chartId,
98
- implicit: sec.implicit,
99
- confidence: sec.confidence,
100
- createdAt: now,
101
- updatedAt: now,
102
- }
103
- });
104
-
105
- relations.push({
106
- from: `${chartId}_chart`,
107
- to: actionName,
108
- relationType: 'has_action_step',
109
- metadata: { createdAt: now }
110
- });
111
-
112
- relations.push({
113
- from: actionName,
114
- to: `${chartId}_desired_outcome`,
115
- relationType: 'advances_toward',
116
- metadata: { createdAt: now }
117
- });
118
-
119
- if (sec.dependency) {
120
- this.addDependencyRelation(entities, relations, actionName, sec.dependency, now);
121
- }
122
- }
123
-
124
- for (const item of result.actionStack) {
125
- const actionName = `${chartId}_action_${++actionIndex}`;
126
-
127
- entities.push({
128
- name: actionName,
129
- entityType: 'action_step',
130
- observations: [item.text],
131
- metadata: {
132
- chartId,
133
- dueDate: actionDueDates[actionIndex - 1]?.toISOString(),
134
- completionStatus: item.completed || false,
135
- level: 1,
136
- parentChart: chartId,
137
- direction: item.direction,
138
- createdAt: now,
139
- updatedAt: now,
140
- }
141
- });
142
-
143
- relations.push({
144
- from: `${chartId}_chart`,
145
- to: actionName,
146
- relationType: 'has_action_step',
147
- metadata: { createdAt: now }
148
- });
149
-
150
- relations.push({
151
- from: actionName,
152
- to: `${chartId}_desired_outcome`,
153
- relationType: 'advances_toward',
154
- metadata: { createdAt: now }
155
- });
156
-
157
- if (item.dependency) {
158
- this.addDependencyRelation(entities, relations, actionName, item.dependency, now);
159
- }
160
- }
161
-
162
- // 5. Structural relations
163
- relations.push(
164
- {
165
- from: `${chartId}_chart`,
166
- to: `${chartId}_desired_outcome`,
167
- relationType: 'has_desired_outcome',
168
- metadata: { createdAt: now }
169
- },
170
- {
171
- from: `${chartId}_chart`,
172
- to: `${chartId}_current_reality`,
173
- relationType: 'has_current_reality',
174
- metadata: { createdAt: now }
175
- },
176
- {
177
- from: `${chartId}_current_reality`,
178
- to: `${chartId}_desired_outcome`,
179
- relationType: 'creates_tension_with',
180
- metadata: { createdAt: now }
181
- }
182
- );
183
-
184
- return { entities, relations, chartId };
185
- }
186
-
187
- private addDependencyRelation(
188
- entities: Entity[],
189
- relations: Relation[],
190
- fromName: string,
191
- dependency: string,
192
- now: string
193
- ): void {
194
- const depEntity = entities.find(
195
- e => e.entityType === 'action_step' && e.observations[0]?.includes(dependency)
196
- );
197
- if (depEntity) {
198
- relations.push({
199
- from: fromName,
200
- to: depEntity.name,
201
- relationType: 'depends_on',
202
- metadata: { createdAt: now, context: dependency }
203
- });
204
- }
205
- }
206
-
207
- private formatDesiredOutcome(primary: { action: string; target: string }): string {
208
- const { action, target } = primary;
209
-
210
- const resultPhrases: Record<string, string> = {
211
- create: `Completed ${target}`,
212
- build: `Functional ${target}`,
213
- implement: `Working ${target}`,
214
- write: `Finished ${target}`,
215
- design: `Complete design for ${target}`,
216
- develop: `Deployed ${target}`,
217
- analyze: `Clear understanding of ${target}`,
218
- integrate: `Fully integrated ${target}`,
219
- test: `Validated ${target}`,
220
- fix: `Resolved ${target}`,
221
- refactor: `Refactored ${target}`,
222
- };
223
-
224
- for (const [verb, phrase] of Object.entries(resultPhrases)) {
225
- if (action.startsWith(verb)) {
226
- return phrase;
227
- }
228
- }
229
-
230
- return `Completed: ${action} ${target}`;
231
- }
232
-
233
- private formatCurrentReality(
234
- context: DecompositionResult['context'],
235
- ambiguities: DecompositionResult['ambiguities']
236
- ): string[] {
237
- const observations: string[] = [];
238
-
239
- if (context.files_needed.length > 0) {
240
- observations.push(`Files to reference: ${context.files_needed.join(', ')}`);
241
- }
242
-
243
- if (context.tools_required.length > 0) {
244
- observations.push(`Tools available: ${context.tools_required.join(', ')}`);
245
- }
246
-
247
- for (const assumption of context.assumptions) {
248
- observations.push(`Assumption: ${assumption}`);
249
- }
250
-
251
- for (const ambiguity of ambiguities) {
252
- observations.push(`⚠️ Ambiguity: ${ambiguity.text} — Suggestion: ${ambiguity.suggestion}`);
253
- }
254
-
255
- if (observations.length === 0) {
256
- observations.push('Starting point: No prior work on this goal');
257
- }
258
-
259
- return observations;
260
- }
261
-
262
- private mapDirectionsToFourDirections(
263
- directions: DecompositionResult['directions']
264
- ): EntityMetadata['fourDirections'] {
265
- const join = (items: Array<{ text: string }>) =>
266
- items.length > 0 ? items.map(i => i.text).join('; ') : null;
267
-
268
- return {
269
- north_vision: join(directions.north || []),
270
- east_intention: join(directions.east || []),
271
- south_emotion: join(directions.south || []),
272
- west_introspection: join(directions.west || []),
273
- };
274
- }
275
-
276
- private calculateDueDate(urgency: string): string {
277
- const days = URGENCY_DAYS[urgency] || URGENCY_DAYS['session'];
278
- const date = new Date();
279
- date.setDate(date.getDate() + days);
280
- return date.toISOString();
281
- }
282
-
283
- private distributeActionDates(start: Date, end: Date, count: number): Date[] {
284
- if (count === 0) return [];
285
-
286
- const totalTime = end.getTime() - start.getTime();
287
- const interval = totalTime / (count + 1);
288
-
289
- const dates: Date[] = [];
290
- for (let i = 1; i <= count; i++) {
291
- dates.push(new Date(start.getTime() + interval * i));
292
- }
293
-
294
- return dates;
295
- }
296
- }
297
-
298
- export const stcMapper = new StcMapper();
@@ -1,7 +0,0 @@
1
- // coaiajs/src/pipeline/index.ts — Barrel export
2
-
3
- export {
4
- TemplateLoader,
5
- TemplateRenderer,
6
- MobileTemplateEngine,
7
- } from './template-engine.js';
@@ -1,398 +0,0 @@
1
- // coaiajs/src/pipeline/template-engine.ts — Pipeline template engine
2
- // Port of coaiapy's pipeline.py + mobile_template.py
3
-
4
- import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync } from 'node:fs';
5
- import { join, extname } from 'node:path';
6
- import { homedir } from 'node:os';
7
- import { v4 as uuidv4 } from 'uuid';
8
- import * as yaml from 'js-yaml';
9
- import type { PipelineTemplate, PipelineStep, PipelineVariable } from '../types.js';
10
-
11
- // ─── MobileTemplateEngine ───────────────────────────────────────────
12
- // Lightweight template engine replacing Jinja2 for TypeScript
13
-
14
- const VARIABLE_RE = /\{\{([^}]+)\}\}/g;
15
- const CONDITION_RE = /\{%\s*if\s+([^%]+)%\}(.*?)\{%\s*endif\s*%\}/gs;
16
-
17
- type BuiltinFn = () => string;
18
-
19
- const BUILTIN_FUNCTIONS: Record<string, BuiltinFn> = {
20
- uuid4: () => uuidv4(),
21
- now: () => new Date().toISOString(),
22
- timestamp: () => new Date().toISOString(),
23
- mobile_id: () => `mobile_${uuidv4().slice(0, 8)}`,
24
- touch_timestamp: () => new Date().toLocaleTimeString('en-US', { hour12: false }),
25
- };
26
-
27
- export class MobileTemplateEngine {
28
- renderString(text: string, variables: Record<string, string>): string {
29
- if (!text) return '';
30
- let result = processConditionals(text, variables);
31
- result = processVariables(result, variables);
32
- return result;
33
- }
34
-
35
- renderDict(
36
- data: Record<string, unknown>,
37
- variables: Record<string, string>,
38
- ): Record<string, unknown> {
39
- if (!data || typeof data !== 'object') return data;
40
-
41
- const result: Record<string, unknown> = {};
42
- for (const [key, value] of Object.entries(data)) {
43
- if (typeof value === 'string') {
44
- result[key] = this.renderString(value, variables);
45
- } else if (Array.isArray(value)) {
46
- result[key] = this.renderList(value, variables);
47
- } else if (value !== null && typeof value === 'object') {
48
- result[key] = this.renderDict(value as Record<string, unknown>, variables);
49
- } else {
50
- result[key] = value;
51
- }
52
- }
53
- return result;
54
- }
55
-
56
- private renderList(data: unknown[], variables: Record<string, string>): unknown[] {
57
- return data.map((item) => {
58
- if (typeof item === 'string') return this.renderString(item, variables);
59
- if (Array.isArray(item)) return this.renderList(item, variables);
60
- if (item !== null && typeof item === 'object') {
61
- return this.renderDict(item as Record<string, unknown>, variables);
62
- }
63
- return item;
64
- });
65
- }
66
- }
67
-
68
- // ─── Template Processing ────────────────────────────────────────────
69
-
70
- function processVariables(content: string, variables: Record<string, string>): string {
71
- return content.replace(VARIABLE_RE, (_match, expr: string) => {
72
- const trimmed = expr.trim();
73
-
74
- // Function calls: uuid4(), now(), etc.
75
- if (trimmed.endsWith('()')) {
76
- const fnName = trimmed.slice(0, -2);
77
- if (fnName in BUILTIN_FUNCTIONS) return BUILTIN_FUNCTIONS[fnName]();
78
- }
79
-
80
- // Filters: variable|title, variable|upper
81
- if (trimmed.includes('|')) {
82
- const [varName, filterName] = trimmed.split('|').map((s) => s.trim());
83
- const val = variables[varName];
84
- if (val !== undefined) return applyFilter(val, filterName);
85
- return `[${varName}]`;
86
- }
87
-
88
- // "variable or default" expressions
89
- if (trimmed.includes(' or ')) {
90
- const parts = trimmed.split(' or ').map((p) => p.trim());
91
- for (const part of parts) {
92
- const unquoted = part.replace(/^['"]|['"]$/g, '');
93
- if (part in variables && variables[part] != null) return variables[part];
94
- if (part.startsWith("'") || part.startsWith('"')) return unquoted;
95
- }
96
- return parts[parts.length - 1].replace(/^['"]|['"]$/g, '');
97
- }
98
-
99
- // Simple lookup
100
- if (trimmed in variables) return variables[trimmed] ?? '';
101
- return `[${trimmed}]`;
102
- });
103
- }
104
-
105
- function processConditionals(content: string, variables: Record<string, string>): string {
106
- return content.replace(CONDITION_RE, (_match, condition: string, body: string) => {
107
- return evaluateCondition(condition.trim(), variables) ? body : '';
108
- });
109
- }
110
-
111
- function evaluateCondition(condition: string, variables: Record<string, string>): boolean {
112
- if (condition.startsWith('not ')) {
113
- const varName = condition.slice(4).trim();
114
- return !isTruthy(variables[varName]);
115
- }
116
- if (condition.includes('==')) {
117
- const [left, right] = condition.split('==').map((s) => s.trim().replace(/^['"]|['"]$/g, ''));
118
- return String(variables[left] ?? left) === right;
119
- }
120
- if (condition.includes('!=')) {
121
- const [left, right] = condition.split('!=').map((s) => s.trim().replace(/^['"]|['"]$/g, ''));
122
- return String(variables[left] ?? left) !== right;
123
- }
124
- return isTruthy(variables[condition]);
125
- }
126
-
127
- function isTruthy(value: unknown): boolean {
128
- if (value == null || value === '' || (Array.isArray(value) && value.length === 0)) return false;
129
- if (typeof value === 'boolean') return value;
130
- if (typeof value === 'string') return !['false', '0', 'no', 'off', 'disabled'].includes(value.toLowerCase());
131
- return Boolean(value);
132
- }
133
-
134
- function applyFilter(value: string, filter: string): string {
135
- switch (filter) {
136
- case 'title': return value.replace(/\b\w/g, (c) => c.toUpperCase());
137
- case 'upper': return value.toUpperCase();
138
- case 'lower': return value.toLowerCase();
139
- case 'capitalize': return value.charAt(0).toUpperCase() + value.slice(1);
140
- case 'strip': return value.trim();
141
- default: return value;
142
- }
143
- }
144
-
145
- // ─── TemplateLoader ─────────────────────────────────────────────────
146
-
147
- export class TemplateLoader {
148
- private searchPaths: string[];
149
-
150
- constructor() {
151
- this.searchPaths = this.getSearchPaths();
152
- }
153
-
154
- private getSearchPaths(): string[] {
155
- const paths: string[] = [];
156
- const projectPath = join(process.cwd(), '.coaia', 'templates');
157
- if (existsSync(projectPath)) paths.push(projectPath);
158
-
159
- const userPath = join(homedir(), '.coaia', 'templates');
160
- if (existsSync(userPath)) paths.push(userPath);
161
-
162
- return paths;
163
- }
164
-
165
- listTemplates(includePath = false): Array<Record<string, unknown>> {
166
- const templates: Array<Record<string, unknown>> = [];
167
- const seen = new Set<string>();
168
-
169
- for (const searchPath of this.searchPaths) {
170
- if (!existsSync(searchPath)) continue;
171
-
172
- for (const file of readdirSync(searchPath)) {
173
- const ext = extname(file);
174
- if (ext !== '.json' && ext !== '.yaml' && ext !== '.yml') continue;
175
-
176
- const stem = file.replace(ext, '');
177
- if (seen.has(stem)) continue;
178
-
179
- try {
180
- const template = this.loadTemplate(stem);
181
- if (template) {
182
- const info: Record<string, unknown> = {
183
- name: template.name,
184
- description: template.description,
185
- version: template.version,
186
- author: template.author,
187
- };
188
- if (includePath) info.path = join(searchPath, file);
189
- templates.push(info);
190
- seen.add(stem);
191
- }
192
- } catch {
193
- continue;
194
- }
195
- }
196
- }
197
-
198
- return templates;
199
- }
200
-
201
- loadTemplate(name: string): PipelineTemplate | null {
202
- for (const searchPath of this.searchPaths) {
203
- // Try JSON
204
- const jsonFile = join(searchPath, `${name}.json`);
205
- if (existsSync(jsonFile)) {
206
- try {
207
- const data = JSON.parse(readFileSync(jsonFile, 'utf-8'));
208
- return parsePipelineTemplate(data);
209
- } catch { continue; }
210
- }
211
-
212
- // Try YAML
213
- for (const ext of ['.yaml', '.yml']) {
214
- const yamlFile = join(searchPath, `${name}${ext}`);
215
- if (existsSync(yamlFile)) {
216
- try {
217
- const data = yaml.load(readFileSync(yamlFile, 'utf-8')) as Record<string, unknown>;
218
- return parsePipelineTemplate(data);
219
- } catch { continue; }
220
- }
221
- }
222
- }
223
- return null;
224
- }
225
-
226
- saveTemplate(template: PipelineTemplate, location: 'user' | 'project' = 'user'): void {
227
- const savePath = location === 'project'
228
- ? join(process.cwd(), '.coaia', 'templates')
229
- : join(homedir(), '.coaia', 'templates');
230
-
231
- mkdirSync(savePath, { recursive: true });
232
-
233
- const data = serializePipelineTemplate(template);
234
- if (!data.createdAt) data.createdAt = new Date().toISOString();
235
-
236
- const filePath = join(savePath, `${template.name}.json`);
237
- writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
238
- }
239
- }
240
-
241
- // ─── TemplateRenderer ───────────────────────────────────────────────
242
-
243
- export class TemplateRenderer {
244
- private engine = new MobileTemplateEngine();
245
-
246
- renderTemplate(
247
- template: PipelineTemplate,
248
- variables: Record<string, string>,
249
- ): PipelineStep[] {
250
- const errors = validateVariables(template, variables);
251
- if (errors.length) throw new Error(`Template validation failed: ${errors.join('; ')}`);
252
-
253
- const finalVars = mergeDefaults(template, variables);
254
- const rendered: PipelineStep[] = [];
255
-
256
- for (const step of template.steps) {
257
- if (step.conditional) {
258
- const expanded = this.engine.renderString(step.conditional, finalVars);
259
- if (!expanded.trim()) continue;
260
- }
261
-
262
- rendered.push({
263
- name: this.engine.renderString(step.name, finalVars),
264
- observationType: step.observationType,
265
- description: step.description ? this.engine.renderString(step.description, finalVars) : undefined,
266
- parent: step.parent ? this.engine.renderString(step.parent, finalVars) : undefined,
267
- variables: step.variables
268
- ? this.engine.renderDict(step.variables as Record<string, unknown>, finalVars) as Record<string, string>
269
- : undefined,
270
- metadata: step.metadata
271
- ? this.engine.renderDict(step.metadata, finalVars) as Record<string, unknown>
272
- : undefined,
273
- });
274
- }
275
-
276
- return rendered;
277
- }
278
-
279
- renderWithJudgeIntegration(
280
- template: PipelineTemplate,
281
- variables: Record<string, string>,
282
- enableJudge = false,
283
- ): PipelineStep[] {
284
- const observations = this.renderTemplate(template, variables);
285
-
286
- if (!enableJudge) return observations;
287
-
288
- return observations.map((obs) => {
289
- const name = (obs.name ?? '').toLowerCase();
290
- const desc = (obs.description ?? '').toLowerCase();
291
- const indicators = ['judge', 'evaluation', 'assess', 'scoring', 'rating'];
292
- const isJudge = indicators.some((i) => name.includes(i) || desc.includes(i));
293
-
294
- if (isJudge) {
295
- return {
296
- ...obs,
297
- metadata: {
298
- ...obs.metadata,
299
- judge_integration_ready: true,
300
- judge_api_placeholder: true,
301
- },
302
- };
303
- }
304
- return obs;
305
- });
306
- }
307
- }
308
-
309
- // ─── Helpers ────────────────────────────────────────────────────────
310
-
311
- function parsePipelineTemplate(data: Record<string, unknown>): PipelineTemplate {
312
- return {
313
- name: String(data.name),
314
- version: String(data.version ?? '1.0'),
315
- description: String(data.description ?? ''),
316
- author: data.author as string | undefined,
317
- createdAt: data.created_at as string | undefined ?? data.createdAt as string | undefined,
318
- variables: ((data.variables ?? []) as Array<Record<string, unknown>>).map((v) => ({
319
- name: String(v.name),
320
- type: String(v.type ?? 'string'),
321
- required: v.required !== false,
322
- default: v.default as string | undefined,
323
- description: v.description as string | undefined,
324
- choices: v.choices as string[] | undefined,
325
- })),
326
- steps: ((data.steps ?? []) as Array<Record<string, unknown>>).map((s) => ({
327
- name: String(s.name),
328
- observationType: String(s.observation_type ?? s.observationType ?? 'EVENT'),
329
- description: s.description as string | undefined,
330
- parent: s.parent as string | undefined,
331
- variables: (s.variables ?? {}) as Record<string, string>,
332
- metadata: (s.metadata ?? {}) as Record<string, unknown>,
333
- conditional: s.conditional as string | undefined,
334
- })),
335
- extends: data.extends as string | undefined,
336
- metadata: (data.metadata ?? {}) as Record<string, unknown>,
337
- };
338
- }
339
-
340
- function serializePipelineTemplate(t: PipelineTemplate): Record<string, unknown> {
341
- return {
342
- name: t.name,
343
- version: t.version,
344
- description: t.description,
345
- author: t.author,
346
- createdAt: t.createdAt,
347
- extends: t.extends,
348
- metadata: t.metadata,
349
- variables: (t.variables ?? []).map((v: PipelineVariable) => ({
350
- name: v.name,
351
- type: v.type,
352
- required: v.required,
353
- default: v.default,
354
- description: v.description,
355
- choices: v.choices,
356
- })),
357
- steps: (t.steps ?? []).map((s: PipelineStep) => ({
358
- name: s.name,
359
- observation_type: s.observationType,
360
- description: s.description,
361
- parent: s.parent,
362
- variables: s.variables,
363
- metadata: s.metadata,
364
- conditional: s.conditional,
365
- })),
366
- };
367
- }
368
-
369
- function validateVariables(
370
- template: PipelineTemplate,
371
- provided: Record<string, string>,
372
- ): string[] {
373
- const errors: string[] = [];
374
- for (const v of template.variables ?? []) {
375
- if (v.required && v.default == null && !(v.name in provided)) {
376
- errors.push(`Required variable '${v.name}' not provided`);
377
- }
378
- if (v.choices && v.name in provided && !v.choices.includes(provided[v.name])) {
379
- errors.push(`Variable '${v.name}' must be one of: ${v.choices.join(', ')}`);
380
- }
381
- }
382
- return errors;
383
- }
384
-
385
- function mergeDefaults(
386
- template: PipelineTemplate,
387
- provided: Record<string, string>,
388
- ): Record<string, string> {
389
- const result: Record<string, string> = {};
390
- for (const v of template.variables ?? []) {
391
- if (v.name in provided) {
392
- result[v.name] = provided[v.name];
393
- } else if (v.default != null) {
394
- result[v.name] = String(v.default);
395
- }
396
- }
397
- return result;
398
- }