coaia-visualizer 1.4.2 → 1.5.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 (47) hide show
  1. package/.dockerignore +9 -0
  2. package/Dockerfile.app +50 -0
  3. package/Dockerfile.test +24 -0
  4. package/LIVE_MODE_DESIGN.md +435 -0
  5. package/MCP_TESTING_COMPLETE.md +302 -0
  6. package/MCP_TESTING_IMPLEMENTATION_SUMMARY.md +317 -0
  7. package/MCP_TESTING_SETUP.md +268 -0
  8. package/NAMING.md +218 -0
  9. package/QUICK_START_MCP_TESTING.md +236 -0
  10. package/WS__issue_8__coaia-visualizer__260207.code-workspace +45 -0
  11. package/app/api/audio/[filename]/route.ts +37 -0
  12. package/app/api/charts/[id]/route.ts +48 -35
  13. package/app/api/watch/route.ts +42 -0
  14. package/app/page.tsx +103 -53
  15. package/cli.ts +56 -3
  16. package/components/add-master-chart.tsx +230 -0
  17. package/components/chart-detail-editable.tsx +27 -16
  18. package/components/chart-list.tsx +13 -1
  19. package/components/create-chart-form.tsx +248 -0
  20. package/components/data-stats.tsx +9 -7
  21. package/components/live-indicator.tsx +14 -0
  22. package/components/ui/dialog.tsx +143 -0
  23. package/components/ui/label.tsx +24 -0
  24. package/direct-test.sh +180 -0
  25. package/dist/cli.js +52 -3
  26. package/docker-compose.test.yml +69 -0
  27. package/hooks/use-live-polling.ts +45 -0
  28. package/jgwill.coaia-visualizer-8--496dca71-d476-4ac9-ba9f-376add118dd8--260208.txt +2612 -0
  29. package/lib/chart-editor.ts +281 -68
  30. package/mcp/Dockerfile +21 -0
  31. package/mcp/README.md +25 -6
  32. package/mcp/src/api-client.ts +15 -3
  33. package/mcp/src/index.ts +17 -2
  34. package/mcp/src/tools/index.ts +21 -1
  35. package/mcp/test_mcp/.gemini/settings.json +18 -0
  36. package/mcp-config.json +14 -0
  37. package/package.json +2 -2
  38. package/run-mcp-tests.sh +99 -0
  39. package/samples/tradingchart.jsonl +31 -0
  40. package/test-data/test-master.jsonl +11 -0
  41. package/test-run.log +101 -0
  42. package/test-scripts/README.md +239 -0
  43. package/test-scripts/run-all-tests.sh +38 -0
  44. package/test-scripts/test-01-basic-operations.sh +87 -0
  45. package/test-scripts/test-02-telescope-creation.sh +91 -0
  46. package/test-scripts/test-03-navigation.sh +87 -0
  47. package/validate-mcp.sh +136 -0
@@ -1,11 +1,18 @@
1
1
  // lib/chart-editor.ts - Chart editing logic
2
2
 
3
- import type { Chart, EntityRecord, RelationRecord, ParsedData, JSONLRecord } from './types'
3
+ import type { EntityRecord, RelationRecord, ParsedData } from "./types"
4
4
 
5
5
  export interface ChartUpdate {
6
- type: 'update_desired_outcome' | 'update_current_reality' | 'add_observation' |
7
- 'update_action' | 'add_action' | 'delete_action' | 'toggle_action_completion' |
8
- 'update_due_date' | 'update_action_due_date'
6
+ type:
7
+ | "update_desired_outcome"
8
+ | "update_current_reality"
9
+ | "add_observation"
10
+ | "update_action"
11
+ | "add_action"
12
+ | "delete_action"
13
+ | "toggle_action_completion"
14
+ | "update_due_date"
15
+ | "update_action_due_date"
9
16
  chartId: string
10
17
  data: any
11
18
  }
@@ -23,7 +30,7 @@ export class ChartEditor {
23
30
  updateDesiredOutcome(chartId: string, newText: string): void {
24
31
  const desiredOutcomeName = `${chartId}_desired_outcome`
25
32
  const entity = this.entities.get(desiredOutcomeName)
26
-
33
+
27
34
  if (entity) {
28
35
  entity.observations = [newText]
29
36
  entity.metadata.updatedAt = new Date().toISOString()
@@ -34,7 +41,7 @@ export class ChartEditor {
34
41
  addCurrentRealityObservation(chartId: string, observation: string): void {
35
42
  const currentRealityName = `${chartId}_current_reality`
36
43
  const entity = this.entities.get(currentRealityName)
37
-
44
+
38
45
  if (entity) {
39
46
  entity.observations.push(observation)
40
47
  entity.metadata.updatedAt = new Date().toISOString()
@@ -45,12 +52,12 @@ export class ChartEditor {
45
52
  updateCurrentReality(chartId: string, newObservations: string[]): void {
46
53
  const currentRealityName = `${chartId}_current_reality`
47
54
  const entity = this.entities.get(currentRealityName)
48
-
55
+
49
56
  if (!entity) {
50
57
  throw new Error(`Chart ${chartId} current reality not found`)
51
58
  }
52
59
 
53
- const uniqueObservations = newObservations.filter(obs => !entity.observations.includes(obs))
60
+ const uniqueObservations = newObservations.filter((obs) => !entity.observations.includes(obs))
54
61
  entity.observations.push(...uniqueObservations)
55
62
  entity.metadata.updatedAt = new Date().toISOString()
56
63
  this.entities.set(currentRealityName, entity)
@@ -59,7 +66,7 @@ export class ChartEditor {
59
66
  updateCurrentRealityObservation(chartId: string, index: number, newText: string): void {
60
67
  const currentRealityName = `${chartId}_current_reality`
61
68
  const entity = this.entities.get(currentRealityName)
62
-
69
+
63
70
  if (entity && entity.observations[index] !== undefined) {
64
71
  entity.observations[index] = newText
65
72
  entity.metadata.updatedAt = new Date().toISOString()
@@ -70,7 +77,7 @@ export class ChartEditor {
70
77
  deleteCurrentRealityObservation(chartId: string, index: number): void {
71
78
  const currentRealityName = `${chartId}_current_reality`
72
79
  const entity = this.entities.get(currentRealityName)
73
-
80
+
74
81
  if (entity) {
75
82
  entity.observations.splice(index, 1)
76
83
  entity.metadata.updatedAt = new Date().toISOString()
@@ -82,10 +89,10 @@ export class ChartEditor {
82
89
  parentChartId: string,
83
90
  actionStepTitle: string,
84
91
  currentReality: string,
85
- dueDate?: string
92
+ dueDate?: string,
86
93
  ): { chartId: string; actionStepName: string } {
87
94
  const parentChart = this.entities.get(parentChartId)
88
- if (!parentChart || parentChart.entityType !== 'structural_tension_chart') {
95
+ if (!parentChart || parentChart.entityType !== "structural_tension_chart") {
89
96
  throw new Error(`Parent chart ${parentChartId} not found`)
90
97
  }
91
98
 
@@ -97,7 +104,9 @@ export class ChartEditor {
97
104
  }
98
105
 
99
106
  if (!currentReality) {
100
- throw new Error(`Current reality required for action step "${actionStepTitle}". Provide honest assessment of actual current state relative to this action step.`)
107
+ throw new Error(
108
+ `Current reality required for action step "${actionStepTitle}". Provide honest assessment of actual current state relative to this action step.`,
109
+ )
101
110
  }
102
111
 
103
112
  let telescopedDueDate = dueDate
@@ -108,88 +117,91 @@ export class ChartEditor {
108
117
  telescopedDueDate = midpoint.toISOString()
109
118
  }
110
119
 
111
- const existingCharts = Array.from(this.entities.values())
112
- .filter(e => e.entityType === 'structural_tension_chart')
120
+ const existingCharts = Array.from(this.entities.values()).filter((e) => e.entityType === "structural_tension_chart")
113
121
  const maxId = existingCharts.reduce((max, e) => {
114
- const id = parseInt(e.metadata?.chartId?.replace('chart_', '') || '0')
122
+ const id = Number.parseInt(e.metadata?.chartId?.replace("chart_", "") || "0")
115
123
  return Math.max(max, id)
116
124
  }, 0)
117
125
  const newChartId = `chart_${maxId + 1}`
118
126
  const timestamp = new Date().toISOString()
119
127
 
120
- this.entities.set(newChartId, {
121
- type: 'entity',
122
- name: newChartId,
123
- entityType: 'structural_tension_chart',
128
+ // Extract chart ID from parentChartId (entity name) for parentChart reference
129
+ const parentChartId_normalized = parentChartId.replace("_chart", "")
130
+
131
+ const chartEntityName = `${newChartId}_chart`
132
+ this.entities.set(chartEntityName, {
133
+ type: "entity",
134
+ name: chartEntityName,
135
+ entityType: "structural_tension_chart",
124
136
  observations: [`Telescoped chart for: ${actionStepTitle}`],
125
137
  metadata: {
126
138
  chartId: newChartId,
127
139
  level: parentLevel + 1,
128
- parentChart: parentChartId,
140
+ parentChart: parentChartId_normalized,
129
141
  dueDate: telescopedDueDate,
130
142
  completionStatus: false,
131
143
  createdAt: timestamp,
132
- updatedAt: timestamp
133
- }
144
+ updatedAt: timestamp,
145
+ },
134
146
  })
135
147
 
136
148
  const desiredOutcomeName = `${newChartId}_desired_outcome`
137
149
  this.entities.set(desiredOutcomeName, {
138
- type: 'entity',
150
+ type: "entity",
139
151
  name: desiredOutcomeName,
140
- entityType: 'desired_outcome',
152
+ entityType: "desired_outcome",
141
153
  observations: [actionStepTitle],
142
154
  metadata: {
143
155
  chartId: newChartId,
144
156
  createdAt: timestamp,
145
- updatedAt: timestamp
146
- }
157
+ updatedAt: timestamp,
158
+ },
147
159
  })
148
160
 
149
161
  const currentRealityName = `${newChartId}_current_reality`
150
162
  this.entities.set(currentRealityName, {
151
- type: 'entity',
163
+ type: "entity",
152
164
  name: currentRealityName,
153
- entityType: 'current_reality',
165
+ entityType: "current_reality",
154
166
  observations: [currentReality],
155
167
  metadata: {
156
168
  chartId: newChartId,
157
169
  createdAt: timestamp,
158
- updatedAt: timestamp
159
- }
170
+ updatedAt: timestamp,
171
+ },
160
172
  })
161
173
 
162
174
  this.relations.push({
163
- type: 'relation',
175
+ type: "relation",
164
176
  from: newChartId,
165
177
  to: desiredOutcomeName,
166
- relationType: 'contains',
167
- metadata: { createdAt: timestamp }
178
+ relationType: "contains",
179
+ metadata: { createdAt: timestamp },
168
180
  })
169
181
 
170
182
  this.relations.push({
171
- type: 'relation',
183
+ type: "relation",
172
184
  from: newChartId,
173
185
  to: currentRealityName,
174
- relationType: 'contains',
175
- metadata: { createdAt: timestamp }
186
+ relationType: "contains",
187
+ metadata: { createdAt: timestamp },
176
188
  })
177
189
 
178
190
  this.relations.push({
179
- type: 'relation',
191
+ type: "relation",
180
192
  from: currentRealityName,
181
193
  to: desiredOutcomeName,
182
- relationType: 'creates_tension_with',
183
- metadata: { createdAt: timestamp }
194
+ relationType: "creates_tension_with",
195
+ metadata: { createdAt: timestamp },
184
196
  })
185
197
 
186
198
  const parentDesiredOutcome = `${parentChartId}_desired_outcome`
187
199
  this.relations.push({
188
- type: 'relation',
200
+ type: "relation",
189
201
  from: desiredOutcomeName,
190
202
  to: parentDesiredOutcome,
191
- relationType: 'advances_toward',
192
- metadata: { createdAt: timestamp }
203
+ relationType: "advances_toward",
204
+ metadata: { createdAt: timestamp },
193
205
  })
194
206
 
195
207
  return { chartId: newChartId, actionStepName: desiredOutcomeName }
@@ -197,8 +209,8 @@ export class ChartEditor {
197
209
 
198
210
  updateActionStep(chartId: string, actionName: string, description: string): void {
199
211
  const entity = this.entities.get(actionName)
200
-
201
- if (entity && (entity.entityType === 'action_step' || entity.entityType === 'desired_outcome')) {
212
+
213
+ if (entity && (entity.entityType === "action_step" || entity.entityType === "desired_outcome")) {
202
214
  entity.observations = [description]
203
215
  entity.metadata.updatedAt = new Date().toISOString()
204
216
  this.entities.set(actionName, entity)
@@ -207,7 +219,7 @@ export class ChartEditor {
207
219
 
208
220
  markActionStepComplete(actionStepName: string): void {
209
221
  const actionStep = this.entities.get(actionStepName)
210
- if (!actionStep || (actionStep.entityType !== 'action_step' && actionStep.entityType !== 'desired_outcome')) {
222
+ if (!actionStep || (actionStep.entityType !== "action_step" && actionStep.entityType !== "desired_outcome")) {
211
223
  throw new Error(`Action step ${actionStepName} not found`)
212
224
  }
213
225
 
@@ -233,7 +245,7 @@ export class ChartEditor {
233
245
  if (parentChartId) {
234
246
  const parentCurrentRealityName = `${parentChartId}_current_reality`
235
247
  const parentCurrentReality = this.entities.get(parentCurrentRealityName)
236
-
248
+
237
249
  if (parentCurrentReality) {
238
250
  const completionMessage = `Completed: ${actionStep.observations[0]}`
239
251
  if (!parentCurrentReality.observations.includes(completionMessage)) {
@@ -247,14 +259,10 @@ export class ChartEditor {
247
259
  }
248
260
  }
249
261
 
250
- updateActionProgress(
251
- actionStepName: string,
252
- progressObservation: string,
253
- updateCurrentReality?: boolean
254
- ): void {
262
+ updateActionProgress(actionStepName: string, progressObservation: string, updateCurrentReality?: boolean): void {
255
263
  const actionStep = this.entities.get(actionStepName)
256
-
257
- if (!actionStep || (actionStep.entityType !== 'action_step' && actionStep.entityType !== 'desired_outcome')) {
264
+
265
+ if (!actionStep || (actionStep.entityType !== "action_step" && actionStep.entityType !== "desired_outcome")) {
258
266
  throw new Error(`Action step ${actionStepName} not found`)
259
267
  }
260
268
 
@@ -267,11 +275,11 @@ export class ChartEditor {
267
275
  if (updateCurrentReality && actionStep.metadata?.chartId) {
268
276
  const chartEntity = this.entities.get(actionStep.metadata.chartId)
269
277
  const parentChartId = chartEntity?.metadata?.parentChart
270
-
278
+
271
279
  if (parentChartId) {
272
280
  const parentCurrentRealityName = `${parentChartId}_current_reality`
273
281
  const parentCurrentReality = this.entities.get(parentCurrentRealityName)
274
-
282
+
275
283
  if (parentCurrentReality) {
276
284
  const progressMessage = `Progress on ${actionStep.observations[0]}: ${progressObservation}`
277
285
  if (!parentCurrentReality.observations.includes(progressMessage)) {
@@ -288,8 +296,8 @@ export class ChartEditor {
288
296
 
289
297
  toggleActionCompletion(actionName: string): void {
290
298
  const entity = this.entities.get(actionName)
291
-
292
- if (entity && (entity.entityType === 'action_step' || entity.entityType === 'desired_outcome')) {
299
+
300
+ if (entity && (entity.entityType === "action_step" || entity.entityType === "desired_outcome")) {
293
301
  entity.metadata.completionStatus = !entity.metadata.completionStatus
294
302
  entity.metadata.updatedAt = new Date().toISOString()
295
303
  if (entity.metadata.completionStatus) {
@@ -303,8 +311,8 @@ export class ChartEditor {
303
311
 
304
312
  updateActionDueDate(actionName: string, dueDate: string | null): void {
305
313
  const entity = this.entities.get(actionName)
306
-
307
- if (entity && entity.entityType === 'action_step') {
314
+
315
+ if (entity && entity.entityType === "action_step") {
308
316
  if (dueDate) {
309
317
  entity.metadata.dueDate = dueDate
310
318
  } else {
@@ -320,14 +328,143 @@ export class ChartEditor {
320
328
  this.entities.delete(actionName)
321
329
 
322
330
  // Remove related relations
323
- this.relations = this.relations.filter(r => r.from !== actionName && r.to !== actionName)
331
+ this.relations = this.relations.filter((r) => r.from !== actionName && r.to !== actionName)
332
+ }
333
+
334
+ createTelescopedChartFromAction(parentChartId: string, actionName: string): string {
335
+ const actionEntity = this.entities.get(actionName)
336
+ if (!actionEntity || (actionEntity.entityType !== "action_step" && actionEntity.entityType !== "desired_outcome")) {
337
+ throw new Error(`Action ${actionName} not found`)
338
+ }
339
+
340
+ const parentChart = this.entities.get(parentChartId)
341
+ if (!parentChart || parentChart.entityType !== "structural_tension_chart") {
342
+ throw new Error(`Parent chart ${parentChartId} not found`)
343
+ }
344
+
345
+ const actionDescription = actionEntity.observations[0] || "Untitled Action"
346
+ const parentLevel = parentChart.metadata?.level || 0
347
+ const parentDueDate = parentChart.metadata?.dueDate
348
+
349
+ if (!parentDueDate) {
350
+ throw new Error(`Parent chart ${parentChartId} has no due date`)
351
+ }
352
+
353
+ // Calculate telescoped due date (midpoint between now and parent due date)
354
+ const now = new Date()
355
+ const parentEnd = new Date(parentDueDate)
356
+ const midpoint = new Date(now.getTime() + (parentEnd.getTime() - now.getTime()) / 2)
357
+ const telescopedDueDate = midpoint.toISOString()
358
+
359
+ // Generate new chart ID
360
+ const existingCharts = Array.from(this.entities.values()).filter((e) => e.entityType === "structural_tension_chart")
361
+ const maxId = existingCharts.reduce((max, e) => {
362
+ const id = Number.parseInt(e.metadata?.chartId?.replace("chart_", "") || "0")
363
+ return Math.max(max, id)
364
+ }, 0)
365
+ const newChartId = `chart_${maxId + 1}`
366
+ const timestamp = new Date().toISOString()
367
+
368
+ // Extract chart ID from parentChartId (entity name) for parentChart reference
369
+ const parentChartId_normalized = parentChartId.replace("_chart", "")
370
+
371
+ // Create new chart
372
+ const chartEntityName = `${newChartId}_chart`
373
+ this.entities.set(chartEntityName, {
374
+ type: "entity",
375
+ name: chartEntityName,
376
+ entityType: "structural_tension_chart",
377
+ observations: [`Telescoped chart for: ${actionDescription}`],
378
+ metadata: {
379
+ chartId: newChartId,
380
+ level: parentLevel + 1,
381
+ parentChart: parentChartId_normalized,
382
+ dueDate: telescopedDueDate,
383
+ completionStatus: false,
384
+ createdAt: timestamp,
385
+ updatedAt: timestamp,
386
+ },
387
+ })
388
+
389
+ // Create desired outcome (use action description)
390
+ const desiredOutcomeName = `${newChartId}_desired_outcome`
391
+ this.entities.set(desiredOutcomeName, {
392
+ type: "entity",
393
+ name: desiredOutcomeName,
394
+ entityType: "desired_outcome",
395
+ observations: [actionDescription],
396
+ metadata: {
397
+ chartId: newChartId,
398
+ createdAt: timestamp,
399
+ updatedAt: timestamp,
400
+ },
401
+ })
402
+
403
+ // Create current reality (needs initial observation)
404
+ const currentRealityName = `${newChartId}_current_reality`
405
+ this.entities.set(currentRealityName, {
406
+ type: "entity",
407
+ name: currentRealityName,
408
+ entityType: "current_reality",
409
+ observations: ["Starting work on this action step"],
410
+ metadata: {
411
+ chartId: newChartId,
412
+ createdAt: timestamp,
413
+ updatedAt: timestamp,
414
+ },
415
+ })
416
+
417
+ // Create relations
418
+ this.relations.push({
419
+ type: "relation",
420
+ from: newChartId,
421
+ to: desiredOutcomeName,
422
+ relationType: "contains",
423
+ metadata: { createdAt: timestamp },
424
+ })
425
+
426
+ this.relations.push({
427
+ type: "relation",
428
+ from: newChartId,
429
+ to: currentRealityName,
430
+ relationType: "contains",
431
+ metadata: { createdAt: timestamp },
432
+ })
433
+
434
+ this.relations.push({
435
+ type: "relation",
436
+ from: currentRealityName,
437
+ to: desiredOutcomeName,
438
+ relationType: "creates_tension_with",
439
+ metadata: { createdAt: timestamp },
440
+ })
441
+
442
+ // Link new chart's desired outcome to parent desired outcome
443
+ const parentDesiredOutcome = `${parentChartId}_desired_outcome`
444
+ this.relations.push({
445
+ type: "relation",
446
+ from: desiredOutcomeName,
447
+ to: parentDesiredOutcome,
448
+ relationType: "advances_toward",
449
+ metadata: { createdAt: timestamp },
450
+ })
451
+
452
+ // Mark the original action as a telescoped chart
453
+ if (actionEntity.metadata) {
454
+ actionEntity.metadata.isTelescopedChart = true
455
+ actionEntity.metadata.telescopedChartId = newChartId
456
+ actionEntity.metadata.updatedAt = timestamp
457
+ this.entities.set(actionName, actionEntity)
458
+ }
459
+
460
+ return newChartId
324
461
  }
325
462
 
326
463
  updateChartDueDate(chartId: string, dueDate: string | null): void {
327
464
  const chartName = chartId
328
465
  const entity = this.entities.get(chartName)
329
-
330
- if (entity && entity.entityType === 'structural_tension_chart') {
466
+
467
+ if (entity && entity.entityType === "structural_tension_chart") {
331
468
  if (dueDate) {
332
469
  entity.metadata.dueDate = dueDate
333
470
  } else {
@@ -351,13 +488,89 @@ export class ChartEditor {
351
488
  lines.push(JSON.stringify(relation))
352
489
  }
353
490
 
354
- return lines.join('\n') + '\n'
491
+ return lines.join("\n") + "\n"
355
492
  }
356
493
 
357
494
  getUpdatedData(): ParsedData {
358
- // Re-parse and organize the updated data
359
- const { parseJSONL, organizeData } = require('./jsonl-parser')
360
- const records = this.exportToJSONL().split('\n').filter(l => l.trim()).map(l => JSON.parse(l))
361
- return organizeData(records)
495
+ const chartsMap = new Map<string, any>()
496
+
497
+ // Organize entities into charts
498
+ for (const [name, entity] of this.entities.entries()) {
499
+ if (entity.entityType === "structural_tension_chart") {
500
+ const chartId = entity.metadata?.chartId || name
501
+ chartsMap.set(chartId, {
502
+ id: chartId,
503
+ chartEntity: entity,
504
+ desiredOutcome: undefined,
505
+ currentReality: undefined,
506
+ actions: [],
507
+ narrativeBeats: [],
508
+ subCharts: [],
509
+ level: entity.metadata?.level || 0,
510
+ parentChart: entity.metadata?.parentChart,
511
+ relations: [],
512
+ })
513
+ }
514
+ }
515
+
516
+ // Populate chart components
517
+ for (const [name, entity] of this.entities.entries()) {
518
+ const chartId = entity.metadata?.chartId
519
+ if (!chartId) continue
520
+
521
+ const chart = chartsMap.get(chartId)
522
+ if (!chart) continue
523
+
524
+ switch (entity.entityType) {
525
+ case "desired_outcome":
526
+ chart.desiredOutcome = entity
527
+ break
528
+ case "current_reality":
529
+ chart.currentReality = entity
530
+ break
531
+ case "action_step":
532
+ chart.actions.push(entity)
533
+ break
534
+ case "narrative_beat":
535
+ chart.narrativeBeats.push(entity)
536
+ break
537
+ }
538
+ }
539
+
540
+ // Add relevant relations to each chart
541
+ for (const relation of this.relations) {
542
+ for (const [chartId, chart] of chartsMap.entries()) {
543
+ if (
544
+ relation.from.startsWith(chartId) ||
545
+ relation.to.startsWith(chartId) ||
546
+ relation.from === chartId ||
547
+ relation.to === chartId
548
+ ) {
549
+ chart.relations.push(relation)
550
+ }
551
+ }
552
+ }
553
+
554
+ const chartsArray: any[] = Array.from(chartsMap.values())
555
+
556
+ // Build hierarchy - add subcharts to parent charts
557
+ for (const chart of chartsArray) {
558
+ const parentChartId = chart.parentChart
559
+ if (parentChartId) {
560
+ const parentChart = chartsArray.find((c) => c.id === parentChartId)
561
+ if (parentChart) {
562
+ parentChart.subCharts.push(chart)
563
+ }
564
+ }
565
+ }
566
+
567
+ const rootCharts = chartsArray.filter((c) => !c.parentChart)
568
+
569
+ return {
570
+ entities: this.entities,
571
+ relations: this.relations,
572
+ charts: chartsArray,
573
+ rootCharts: rootCharts,
574
+ }
362
575
  }
363
576
  }
package/mcp/Dockerfile ADDED
@@ -0,0 +1,21 @@
1
+ FROM node:20-alpine
2
+
3
+ WORKDIR /app
4
+
5
+ # Copy package files
6
+ COPY package.json package-lock.json* ./
7
+
8
+ # Install dependencies
9
+ RUN npm install
10
+
11
+ # Copy source and build
12
+ COPY . .
13
+ RUN npm run build
14
+
15
+ # Create data directory
16
+ RUN mkdir -p /data
17
+
18
+ ENV NODE_ENV=production
19
+
20
+ # MCP servers use stdio for communication
21
+ CMD ["node", "dist/index.js"]
package/mcp/README.md CHANGED
@@ -123,22 +123,41 @@ Update a chart's desired outcome, current reality, or due date.
123
123
  \`\`\`
124
124
 
125
125
  ### add_action_step
126
- Add a new action step to a chart.
126
+ Add a new action step to a chart as a telescoped structural tension chart.
127
127
 
128
128
  **Parameters:**
129
- - `chartId` (required): Chart ID
130
- - `description` (required): Action description
131
- - `dueDate` (optional): ISO date string
129
+ - `parentChartId` (required): The parent chart ID
130
+ - `actionStepTitle` (required): Action step title (becomes desired outcome)
131
+ - `currentReality` (required): Honest assessment of actual current state relative to this action step
132
+ - `dueDate` (optional): ISO date string (auto-distributed if not provided)
132
133
 
133
134
  **Example:**
134
135
  \`\`\`json
135
136
  {
136
- "chartId": "chart_1",
137
- "description": "Write API documentation",
137
+ "parentChartId": "chart_1",
138
+ "actionStepTitle": "Write API documentation",
139
+ "currentReality": "No documentation exists yet",
138
140
  "dueDate": "2026-01-15"
139
141
  }
140
142
  \`\`\`
141
143
 
144
+ ### create_telescoped_chart
145
+ Create a telescoped (drill-down) chart from an EXISTING action step. This converts an existing action into a detailed sub-chart for deeper work breakdown.
146
+
147
+ **Parameters:**
148
+ - `chartId` (required): The parent chart ID containing the action
149
+ - `actionName` (required): The action entity name to telescope (e.g., "chart_1_action_1")
150
+
151
+ **Example:**
152
+ \`\`\`json
153
+ {
154
+ "chartId": "chart_1",
155
+ "actionName": "chart_1_action_1"
156
+ }
157
+ \`\`\`
158
+
159
+ **Note:** This is different from `add_action_step`. Use `add_action_step` when creating a NEW action that should be a telescoped chart. Use `create_telescoped_chart` to telescope an EXISTING action into a sub-chart.
160
+
142
161
  ### update_action_step
143
162
  Update an existing action step.
144
163
 
@@ -51,7 +51,7 @@ export class CoaiaVisualizerClient {
51
51
 
52
52
  private async fetchJson<T>(path: string, options: RequestInit = {}): Promise<T> {
53
53
  const response = await this.fetch(path, options)
54
-
54
+
55
55
  if (!response.ok) {
56
56
  const error: any = await response.json().catch(() => ({ error: 'Unknown error' }))
57
57
  throw new Error(`API Error (${response.status}): ${error.error || error.message || 'Unknown error'}`)
@@ -70,7 +70,7 @@ export class CoaiaVisualizerClient {
70
70
  const params = new URLSearchParams()
71
71
  if (options?.level !== undefined) params.set('level', options.level.toString())
72
72
  if (options?.rootOnly) params.set('rootOnly', 'true')
73
-
73
+
74
74
  const query = params.toString()
75
75
  return this.fetchJson(`/api/charts${query ? `?${query}` : ''}`)
76
76
  }
@@ -121,6 +121,18 @@ export class CoaiaVisualizerClient {
121
121
  })
122
122
  }
123
123
 
124
+ /**
125
+ * Create telescoped chart from existing action
126
+ */
127
+ async createTelescopedChart(chartId: string, actionName: string): Promise<{ chart: Chart; updates: string[]; message: string; backup: string }> {
128
+ return this.fetchJson(`/api/charts/${chartId}`, {
129
+ method: 'POST',
130
+ body: JSON.stringify({
131
+ createTelescopedChart: { actionName },
132
+ }),
133
+ })
134
+ }
135
+
124
136
  /**
125
137
  * Delete a chart
126
138
  */
@@ -144,7 +156,7 @@ export class CoaiaVisualizerClient {
144
156
  if (query.level !== undefined) params.set('level', query.level.toString())
145
157
  if (query.completed !== undefined) params.set('completed', query.completed.toString())
146
158
  if (query.hasActions !== undefined) params.set('hasActions', query.hasActions.toString())
147
-
159
+
148
160
  return this.fetchJson(`/api/charts/search?${params.toString()}`)
149
161
  }
150
162