@medicine-wheel/mcp 4.21.0 → 4.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/http-store.d.ts.map +1 -1
- package/dist/http-store.js +16 -2
- package/dist/http-store.js.map +1 -1
- package/dist/tools/structural-tension.d.ts +43 -0
- package/dist/tools/structural-tension.d.ts.map +1 -1
- package/dist/tools/structural-tension.js +1425 -645
- package/dist/tools/structural-tension.js.map +1 -1
- package/package.json +21 -21
|
@@ -9,701 +9,1481 @@
|
|
|
9
9
|
* - SOUTH: Growth/Action Steps (embodied practice)
|
|
10
10
|
* - WEST: Reflection/Current Reality assessment (honest truth)
|
|
11
11
|
* - NORTH: Wisdom/Completion (integration)
|
|
12
|
+
*
|
|
13
|
+
* Delayed resolution (Episode 297, 2026-10-19): a step can record that someone
|
|
14
|
+
* strategically chose to delay its resolution — when resolution is wanted, why
|
|
15
|
+
* waiting serves the work, and which agent session to rehydrate when the time
|
|
16
|
+
* or the event comes. Contract: rispecs/delayed-resolution.spec.md.
|
|
12
17
|
*/
|
|
13
18
|
import { store } from "../store.js";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
19
|
+
const DAY_MS = 86_400_000;
|
|
20
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
21
|
+
/** ISO form of a date the caller gave, or null when it is not a date. */
|
|
22
|
+
function isoOrNull(value) {
|
|
23
|
+
if (typeof value !== 'string' || value.trim() === '')
|
|
24
|
+
return null;
|
|
25
|
+
const t = Date.parse(value);
|
|
26
|
+
return Number.isNaN(t) ? null : new Date(t).toISOString();
|
|
27
|
+
}
|
|
28
|
+
function refuse(refusal, message, extra = {}) {
|
|
29
|
+
return { status: "refused", refusal, message, ...extra };
|
|
30
|
+
}
|
|
31
|
+
function nonEmpty(value) {
|
|
32
|
+
return typeof value === 'string' && value.trim() !== '' ? value.trim() : null;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The next step id for a chart: one past the highest index already used.
|
|
36
|
+
* The array length is not an index once a step can be removed — two steps
|
|
37
|
+
* would then share an id.
|
|
38
|
+
*/
|
|
39
|
+
function nextStepId(chart) {
|
|
40
|
+
const prefix = `${chart.id}_action_`;
|
|
41
|
+
let max = -1;
|
|
42
|
+
for (const s of chart.action_steps) {
|
|
43
|
+
if (typeof s.id === 'string' && s.id.startsWith(prefix)) {
|
|
44
|
+
const n = Number(s.id.slice(prefix.length));
|
|
45
|
+
if (Number.isInteger(n) && n > max)
|
|
46
|
+
max = n;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const ids = new Set(chart.action_steps.map(s => s.id));
|
|
50
|
+
let next = max + 1;
|
|
51
|
+
while (ids.has(`${prefix}${next}`))
|
|
52
|
+
next++;
|
|
53
|
+
return `${prefix}${next}`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Due dates for steps written with a chart: spread evenly between now and the
|
|
57
|
+
* chart's due date, leaving one interval before it. A chart already past its
|
|
58
|
+
* date gives each step that date; a chart without one gives none.
|
|
59
|
+
*/
|
|
60
|
+
function spreadDueDates(chartDue, count, now = Date.now()) {
|
|
61
|
+
const due = chartDue ? Date.parse(chartDue) : NaN;
|
|
62
|
+
if (Number.isNaN(due))
|
|
63
|
+
return Array.from({ length: count }, () => undefined);
|
|
64
|
+
if (due <= now)
|
|
65
|
+
return Array.from({ length: count }, () => new Date(due).toISOString());
|
|
66
|
+
const interval = (due - now) / (count + 1);
|
|
67
|
+
return Array.from({ length: count }, (_, i) => new Date(now + interval * (i + 1)).toISOString());
|
|
68
|
+
}
|
|
69
|
+
function newStep(id, title, due, currentReality = 'Not yet assessed') {
|
|
70
|
+
const step = {
|
|
71
|
+
id,
|
|
72
|
+
title,
|
|
73
|
+
current_reality: currentReality,
|
|
74
|
+
status: 'pending',
|
|
75
|
+
progress: 0,
|
|
76
|
+
ceremonies_honored: [],
|
|
77
|
+
relations_honored: [],
|
|
78
|
+
};
|
|
79
|
+
if (due)
|
|
80
|
+
step.due_date = due;
|
|
81
|
+
return step;
|
|
82
|
+
}
|
|
83
|
+
function findStep(chart, stepId) {
|
|
84
|
+
return chart.action_steps.find(s => s.id === stepId);
|
|
85
|
+
}
|
|
86
|
+
function isOpen(delay) {
|
|
87
|
+
return !!delay && !delay.ended_at;
|
|
88
|
+
}
|
|
89
|
+
/** The status a step returns to when its delay ends: it never completed while waiting. */
|
|
90
|
+
function statusAfterDelay(step) {
|
|
91
|
+
return (step.progress ?? 0) > 0 ? 'in_progress' : 'pending';
|
|
92
|
+
}
|
|
93
|
+
function daysRemaining(resolveBy, now) {
|
|
94
|
+
// `+ 0` turns -0 into 0 so a delay due within the day reads as 0.
|
|
95
|
+
return Math.ceil((Date.parse(resolveBy) - now) / DAY_MS) + 0;
|
|
96
|
+
}
|
|
97
|
+
function isOwed(delay, now) {
|
|
98
|
+
return isOpen(delay) && Date.parse(delay.resolve_by) <= now;
|
|
99
|
+
}
|
|
100
|
+
function settlePhase(chart) {
|
|
101
|
+
const total = chart.action_steps.length;
|
|
102
|
+
const completed = chart.action_steps.filter(s => s.status === 'completed').length;
|
|
103
|
+
if (total > 0) {
|
|
104
|
+
if (completed === total)
|
|
105
|
+
chart.phase = 'completion';
|
|
106
|
+
else if (completed > 0)
|
|
107
|
+
chart.phase = 'assimilation';
|
|
108
|
+
}
|
|
109
|
+
return { completed, total };
|
|
110
|
+
}
|
|
111
|
+
function appendReality(chart, line) {
|
|
112
|
+
chart.current_reality = chart.current_reality ? `${chart.current_reality}\n${line}` : line;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The full `owner/repo#number` form, or null. A bare `#number` resolves against
|
|
116
|
+
* whichever repository the reader happens to be in, so it is refused.
|
|
117
|
+
*/
|
|
118
|
+
function parseGithubIssue(spec) {
|
|
119
|
+
if (typeof spec !== 'string')
|
|
120
|
+
return null;
|
|
121
|
+
const match = /^([A-Za-z0-9._-]+)\/([A-Za-z0-9._-]+)#(\d+)$/.exec(spec.trim());
|
|
122
|
+
if (!match)
|
|
123
|
+
return null;
|
|
124
|
+
const [, owner, repo, number] = match;
|
|
125
|
+
return { ref: `${owner}/${repo}#${number}`, owner, repo, number: Number(number), url: `https://github.com/${owner}/${repo}/issues/${number}` };
|
|
126
|
+
}
|
|
127
|
+
function shellQuote(value) {
|
|
128
|
+
return /^[\w@%+=:,./-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
|
|
129
|
+
}
|
|
130
|
+
/** The command that resumes a session: the stored one, or for Claude, `claude --resume <id>`. */
|
|
131
|
+
function resumeCommandFor(session) {
|
|
132
|
+
if (!session)
|
|
133
|
+
return { resume_command: null };
|
|
134
|
+
if (session.resume_command)
|
|
135
|
+
return { resume_command: session.resume_command, resume_command_derived: false };
|
|
136
|
+
if ((session.agent ?? 'claude') === 'claude') {
|
|
137
|
+
const cmd = `claude --resume ${shellQuote(session.session_id)}`;
|
|
138
|
+
return {
|
|
139
|
+
resume_command: session.cwd ? `cd ${shellQuote(session.cwd)} && ${cmd}` : cmd,
|
|
140
|
+
resume_command_derived: true,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return { resume_command: null };
|
|
144
|
+
}
|
|
145
|
+
function parseMaybeJson(value) {
|
|
146
|
+
if (typeof value !== 'string')
|
|
147
|
+
return value;
|
|
148
|
+
try {
|
|
149
|
+
return JSON.parse(value);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return value;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function normalizeSession(input) {
|
|
156
|
+
const value = parseMaybeJson(input);
|
|
157
|
+
if (value === undefined || value === null || value === '')
|
|
158
|
+
return {};
|
|
159
|
+
if (typeof value !== 'object' || Array.isArray(value)) {
|
|
160
|
+
return { error: 'session must be an object carrying session_id' };
|
|
161
|
+
}
|
|
162
|
+
const s = value;
|
|
163
|
+
const sessionId = nonEmpty(s.session_id);
|
|
164
|
+
if (!sessionId)
|
|
165
|
+
return { error: 'session.session_id is required' };
|
|
166
|
+
const session = { session_id: sessionId, agent: nonEmpty(s.agent) ?? 'claude' };
|
|
167
|
+
for (const key of ['host', 'cwd', 'tmux', 'resume_command', 'inventory_record']) {
|
|
168
|
+
const v = nonEmpty(s[key]);
|
|
169
|
+
if (v)
|
|
170
|
+
session[key] = v;
|
|
171
|
+
}
|
|
172
|
+
return { session };
|
|
173
|
+
}
|
|
174
|
+
function normalizeWakeOn(input) {
|
|
175
|
+
const value = parseMaybeJson(input);
|
|
176
|
+
if (value === undefined || value === null || value === '')
|
|
177
|
+
return undefined;
|
|
178
|
+
const list = Array.isArray(value) ? value : [value];
|
|
179
|
+
const cues = list.map(v => nonEmpty(v)).filter((v) => !!v);
|
|
180
|
+
return cues.length > 0 ? cues : undefined;
|
|
181
|
+
}
|
|
182
|
+
/** One row of a delay listing. `owed` is true only for an open delay whose date has passed. */
|
|
183
|
+
function delayRow(chart, step, delay, state, now) {
|
|
184
|
+
return {
|
|
185
|
+
chart_id: chart.id,
|
|
186
|
+
desired_outcome: chart.desired_outcome,
|
|
187
|
+
step_id: step.id,
|
|
188
|
+
step_title: step.title,
|
|
189
|
+
step_status: step.status,
|
|
190
|
+
state,
|
|
191
|
+
resolve_by: delay.resolve_by,
|
|
192
|
+
days_remaining: daysRemaining(delay.resolve_by, now),
|
|
193
|
+
owed: state === 'open' && isOwed(delay, now),
|
|
194
|
+
reason: delay.reason,
|
|
195
|
+
chosen_by: delay.chosen_by,
|
|
196
|
+
chosen_at: delay.chosen_at,
|
|
197
|
+
primary_choice: delay.primary_choice,
|
|
198
|
+
wake_on: delay.wake_on ?? [],
|
|
199
|
+
session: delay.session ?? null,
|
|
200
|
+
...(delay.ended_at
|
|
201
|
+
? { ended_at: delay.ended_at, ended_by: delay.ended_by, outcome: delay.outcome, end_note: delay.end_note }
|
|
202
|
+
: {}),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function stepSummary(step, now) {
|
|
206
|
+
const delay = isOpen(step.delayed_resolution) ? step.delayed_resolution : undefined;
|
|
207
|
+
return {
|
|
208
|
+
id: step.id,
|
|
209
|
+
title: step.title,
|
|
210
|
+
status: step.status,
|
|
211
|
+
progress: step.progress,
|
|
212
|
+
due_date: step.due_date,
|
|
213
|
+
has_sub_chart: !!step.sub_chart_id,
|
|
214
|
+
...(delay
|
|
215
|
+
? { delayed: { resolve_by: delay.resolve_by, days_remaining: daysRemaining(delay.resolve_by, now), owed: isOwed(delay, now) } }
|
|
216
|
+
: {}),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function openDelayCounts(chart, now) {
|
|
220
|
+
let delayed = 0;
|
|
221
|
+
let owed = 0;
|
|
222
|
+
for (const s of chart.action_steps) {
|
|
223
|
+
if (isOpen(s.delayed_resolution)) {
|
|
224
|
+
delayed++;
|
|
225
|
+
if (isOwed(s.delayed_resolution, now))
|
|
226
|
+
owed++;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return { delayed_steps: delayed, owed_delays: owed };
|
|
230
|
+
}
|
|
231
|
+
// ── Tools ──────────────────────────────────────────────────────────────────
|
|
232
|
+
export function createStructuralTensionTools(targetStore) {
|
|
233
|
+
const store = targetStore;
|
|
234
|
+
async function loadChart(chartId) {
|
|
235
|
+
return (await store.getChart(chartId));
|
|
236
|
+
}
|
|
237
|
+
return [
|
|
238
|
+
{
|
|
239
|
+
name: "create_structural_tension_chart",
|
|
240
|
+
description: "Create a structural tension chart with desired outcome, current reality, and optional action steps. Charts are associated with a medicine wheel direction. REMEMBER: Desired outcome is what you want to CREATE (not solve). Current reality must be factual assessment (never 'ready to begin'). When the chart has a due date, the initial steps get due dates spread between now and that date.",
|
|
241
|
+
inputSchema: {
|
|
242
|
+
type: "object",
|
|
243
|
+
properties: {
|
|
244
|
+
desired_outcome: {
|
|
245
|
+
type: "string",
|
|
246
|
+
description: "What you want to CREATE (not solve/fix). Focus on positive outcomes, not problems to eliminate. Must be visualizable, specific, quantified where possible.",
|
|
247
|
+
},
|
|
248
|
+
current_reality: {
|
|
249
|
+
type: "string",
|
|
250
|
+
description: "Factual assessment of where you are NOW relative to the desired outcome. NEVER use 'ready to begin' or readiness statements. Just facts.",
|
|
251
|
+
},
|
|
252
|
+
direction: {
|
|
253
|
+
type: "string",
|
|
254
|
+
enum: ["east", "south", "west", "north"],
|
|
255
|
+
description: "Which medicine wheel direction this chart serves (east=vision, south=growth, west=reflection, north=wisdom)",
|
|
256
|
+
},
|
|
257
|
+
due_date: {
|
|
258
|
+
type: "string",
|
|
259
|
+
description: "When you want to achieve this outcome (ISO date string, optional). Stored in ISO form.",
|
|
260
|
+
},
|
|
261
|
+
action_steps: {
|
|
262
|
+
type: "array",
|
|
263
|
+
items: { type: "string" },
|
|
264
|
+
description: "Optional strategic secondary choices (NOT tasks). Each step should support advancement toward the desired outcome.",
|
|
265
|
+
},
|
|
42
266
|
},
|
|
267
|
+
required: ["desired_outcome", "current_reality", "direction"],
|
|
43
268
|
},
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
name: "add_action_step",
|
|
88
|
-
description: "Add a strategic secondary choice (action step) to an existing structural tension chart. Each action step is NOT a task — it's a strategic choice that supports the primary goal. Can be telescoped into its own sub-chart.",
|
|
89
|
-
inputSchema: {
|
|
90
|
-
type: "object",
|
|
91
|
-
properties: {
|
|
92
|
-
chart_id: {
|
|
93
|
-
type: "string",
|
|
94
|
-
description: "ID of the chart to add action step to",
|
|
95
|
-
},
|
|
96
|
-
title: {
|
|
97
|
-
type: "string",
|
|
98
|
-
description: "Title of the action step (strategic secondary choice)",
|
|
99
|
-
},
|
|
100
|
-
current_reality: {
|
|
101
|
-
type: "string",
|
|
102
|
-
description: "Current reality for this specific action step",
|
|
103
|
-
},
|
|
104
|
-
due_date: {
|
|
105
|
-
type: "string",
|
|
106
|
-
description: "Optional due date for this action step",
|
|
107
|
-
},
|
|
269
|
+
handler: async (args) => {
|
|
270
|
+
try {
|
|
271
|
+
let dueDate;
|
|
272
|
+
if (args.due_date !== undefined && args.due_date !== null && args.due_date !== '') {
|
|
273
|
+
const iso = isoOrNull(args.due_date);
|
|
274
|
+
if (!iso)
|
|
275
|
+
return refuse("due_date_invalid", `due_date is not a date: ${JSON.stringify(args.due_date)}. Use an ISO date, e.g. 2026-11-30T17:00:00Z`);
|
|
276
|
+
dueDate = iso;
|
|
277
|
+
}
|
|
278
|
+
const chartId = `stc:${args.direction}:${Date.now()}`;
|
|
279
|
+
const titles = args.action_steps || [];
|
|
280
|
+
const stepDue = spreadDueDates(dueDate, titles.length);
|
|
281
|
+
const actionSteps = titles.map((title, idx) => newStep(`${chartId}_action_${idx}`, title, stepDue[idx]));
|
|
282
|
+
const chart = {
|
|
283
|
+
id: chartId,
|
|
284
|
+
desired_outcome: args.desired_outcome,
|
|
285
|
+
current_reality: args.current_reality,
|
|
286
|
+
direction: args.direction,
|
|
287
|
+
action_steps: actionSteps,
|
|
288
|
+
due_date: dueDate,
|
|
289
|
+
created_at: new Date().toISOString(),
|
|
290
|
+
updated_at: new Date().toISOString(),
|
|
291
|
+
phase: 'germination',
|
|
292
|
+
ceremonies_linked: [],
|
|
293
|
+
};
|
|
294
|
+
await store.saveChart(chart);
|
|
295
|
+
return {
|
|
296
|
+
chart_id: chartId,
|
|
297
|
+
direction: args.direction,
|
|
298
|
+
phase: 'germination',
|
|
299
|
+
action_steps_count: actionSteps.length,
|
|
300
|
+
chart: chart,
|
|
301
|
+
teaching: "Structural tension is the generative force. The tension between desired outcome and current reality naturally seeks resolution through advancement.",
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
306
|
+
return { status: "error", message: `Failed to create chart: ${errorMsg}` };
|
|
307
|
+
}
|
|
108
308
|
},
|
|
109
|
-
required: ["chart_id", "title", "current_reality"],
|
|
110
309
|
},
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
id: `${args.chart_id}_action_${chart.action_steps.length}`,
|
|
119
|
-
title: args.title,
|
|
120
|
-
current_reality: args.current_reality,
|
|
121
|
-
status: 'pending',
|
|
122
|
-
progress: 0,
|
|
123
|
-
due_date: args.due_date,
|
|
124
|
-
ceremonies_honored: [],
|
|
125
|
-
relations_honored: [],
|
|
126
|
-
};
|
|
127
|
-
chart.action_steps.push(actionStep);
|
|
128
|
-
chart.updated_at = new Date().toISOString();
|
|
129
|
-
await store.saveChart(chart);
|
|
130
|
-
return {
|
|
131
|
-
action_step_id: actionStep.id,
|
|
132
|
-
chart_id: args.chart_id,
|
|
133
|
-
total_steps: chart.action_steps.length,
|
|
134
|
-
teaching: "Action steps are strategic secondary choices understood in context of the structural tension, not isolated tasks.",
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
catch (error) {
|
|
138
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
139
|
-
return { status: "error", message: `Failed to add action step: ${errorMsg}` };
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
name: "telescope_action_step",
|
|
145
|
-
description: "Expand an action step into its own structural tension chart (sub-chart). The action step title becomes the desired outcome of the new chart. This enables hierarchical planning while maintaining structural tension at each level.",
|
|
146
|
-
inputSchema: {
|
|
147
|
-
type: "object",
|
|
148
|
-
properties: {
|
|
149
|
-
chart_id: {
|
|
150
|
-
type: "string",
|
|
151
|
-
description: "ID of the parent chart",
|
|
152
|
-
},
|
|
153
|
-
action_step_id: {
|
|
154
|
-
type: "string",
|
|
155
|
-
description: "ID of the action step to telescope",
|
|
156
|
-
},
|
|
157
|
-
new_current_reality: {
|
|
158
|
-
type: "string",
|
|
159
|
-
description: "Current reality for the telescoped chart (required, must be factual)",
|
|
160
|
-
},
|
|
161
|
-
initial_action_steps: {
|
|
162
|
-
type: "array",
|
|
163
|
-
items: { type: "string" },
|
|
164
|
-
description: "Optional initial action steps for the sub-chart",
|
|
310
|
+
{
|
|
311
|
+
name: "get_chart",
|
|
312
|
+
description: "Read a structural tension chart whole: desired outcome, current reality, due date and every action step as stored, including delayed resolutions and their history.",
|
|
313
|
+
inputSchema: {
|
|
314
|
+
type: "object",
|
|
315
|
+
properties: {
|
|
316
|
+
chart_id: { type: "string", description: "ID of the chart" },
|
|
165
317
|
},
|
|
318
|
+
required: ["chart_id"],
|
|
319
|
+
},
|
|
320
|
+
handler: async (args) => {
|
|
321
|
+
try {
|
|
322
|
+
const chart = await loadChart(args.chart_id);
|
|
323
|
+
if (!chart) {
|
|
324
|
+
return { status: "not_found", message: `Chart ${args.chart_id} not found` };
|
|
325
|
+
}
|
|
326
|
+
const now = Date.now();
|
|
327
|
+
return {
|
|
328
|
+
chart_id: chart.id,
|
|
329
|
+
chart,
|
|
330
|
+
...openDelayCounts(chart, now),
|
|
331
|
+
teaching: "The chart is read as a whole: the tension lives between its desired outcome and its current reality, not in any single step.",
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
336
|
+
return { status: "error", message: `Failed to get chart: ${errorMsg}` };
|
|
337
|
+
}
|
|
166
338
|
},
|
|
167
|
-
required: ["chart_id", "action_step_id", "new_current_reality"],
|
|
168
|
-
},
|
|
169
|
-
handler: async (args) => {
|
|
170
|
-
try {
|
|
171
|
-
const parentChart = (await store.getChart(args.chart_id));
|
|
172
|
-
if (!parentChart) {
|
|
173
|
-
return { status: "not_found", message: `Parent chart ${args.chart_id} not found` };
|
|
174
|
-
}
|
|
175
|
-
const actionStep = parentChart.action_steps.find((s) => s.id === args.action_step_id);
|
|
176
|
-
if (!actionStep) {
|
|
177
|
-
return { status: "not_found", message: `Action step ${args.action_step_id} not found` };
|
|
178
|
-
}
|
|
179
|
-
const subChartId = `stc:${parentChart.direction}:${Date.now()}:sub`;
|
|
180
|
-
const subActionSteps = (args.initial_action_steps || []).map((title, idx) => ({
|
|
181
|
-
id: `${subChartId}_action_${idx}`,
|
|
182
|
-
title,
|
|
183
|
-
current_reality: 'Not yet assessed',
|
|
184
|
-
status: 'pending',
|
|
185
|
-
progress: 0,
|
|
186
|
-
ceremonies_honored: [],
|
|
187
|
-
relations_honored: [],
|
|
188
|
-
}));
|
|
189
|
-
const subChart = {
|
|
190
|
-
id: subChartId,
|
|
191
|
-
desired_outcome: actionStep.title,
|
|
192
|
-
current_reality: args.new_current_reality,
|
|
193
|
-
direction: parentChart.direction,
|
|
194
|
-
action_steps: subActionSteps,
|
|
195
|
-
due_date: actionStep.due_date || parentChart.due_date,
|
|
196
|
-
created_at: new Date().toISOString(),
|
|
197
|
-
updated_at: new Date().toISOString(),
|
|
198
|
-
phase: 'germination',
|
|
199
|
-
ceremonies_linked: [],
|
|
200
|
-
};
|
|
201
|
-
await store.saveChart(subChart);
|
|
202
|
-
// Link sub-chart to action step
|
|
203
|
-
actionStep.sub_chart_id = subChartId;
|
|
204
|
-
parentChart.updated_at = new Date().toISOString();
|
|
205
|
-
await store.saveChart(parentChart);
|
|
206
|
-
return {
|
|
207
|
-
sub_chart_id: subChartId,
|
|
208
|
-
parent_chart_id: args.chart_id,
|
|
209
|
-
action_step_id: args.action_step_id,
|
|
210
|
-
desired_outcome: actionStep.title,
|
|
211
|
-
sub_action_steps: subActionSteps.length,
|
|
212
|
-
teaching: "Each action step can become its own structural tension chart. The hierarchy maintains tension at every level.",
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
catch (error) {
|
|
216
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
217
|
-
return { status: "error", message: `Failed to telescope action step: ${errorMsg}` };
|
|
218
|
-
}
|
|
219
339
|
},
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
type: "string",
|
|
229
|
-
description: "ID of the chart",
|
|
230
|
-
},
|
|
231
|
-
action_step_id: {
|
|
232
|
-
type: "string",
|
|
233
|
-
description: "ID of the action step",
|
|
234
|
-
},
|
|
235
|
-
progress: {
|
|
236
|
-
type: "number",
|
|
237
|
-
description: "Progress percentage (0-100)",
|
|
238
|
-
minimum: 0,
|
|
239
|
-
maximum: 100,
|
|
240
|
-
},
|
|
241
|
-
new_current_reality: {
|
|
242
|
-
type: "string",
|
|
243
|
-
description: "Optional updated current reality for this action step",
|
|
340
|
+
{
|
|
341
|
+
name: "get_action_step",
|
|
342
|
+
description: "Read one action step of a chart, with its due date, any delayed resolution (open or ended) and its history, and the sub-chart it telescopes into when it has one.",
|
|
343
|
+
inputSchema: {
|
|
344
|
+
type: "object",
|
|
345
|
+
properties: {
|
|
346
|
+
chart_id: { type: "string", description: "ID of the chart holding the step" },
|
|
347
|
+
step_id: { type: "string", description: "ID of the action step" },
|
|
244
348
|
},
|
|
349
|
+
required: ["chart_id", "step_id"],
|
|
350
|
+
},
|
|
351
|
+
handler: async (args) => {
|
|
352
|
+
try {
|
|
353
|
+
const chart = await loadChart(args.chart_id);
|
|
354
|
+
if (!chart) {
|
|
355
|
+
return { status: "not_found", message: `Chart ${args.chart_id} not found` };
|
|
356
|
+
}
|
|
357
|
+
const step = findStep(chart, args.step_id);
|
|
358
|
+
if (!step) {
|
|
359
|
+
return { status: "not_found", message: `Action step ${args.step_id} not found in chart ${args.chart_id}` };
|
|
360
|
+
}
|
|
361
|
+
const now = Date.now();
|
|
362
|
+
let subChart;
|
|
363
|
+
if (step.sub_chart_id)
|
|
364
|
+
subChart = await loadChart(step.sub_chart_id);
|
|
365
|
+
return {
|
|
366
|
+
chart_id: chart.id,
|
|
367
|
+
chart: {
|
|
368
|
+
id: chart.id,
|
|
369
|
+
desired_outcome: chart.desired_outcome,
|
|
370
|
+
direction: chart.direction,
|
|
371
|
+
due_date: chart.due_date,
|
|
372
|
+
},
|
|
373
|
+
step,
|
|
374
|
+
delay: step.delayed_resolution
|
|
375
|
+
? delayRow(chart, step, step.delayed_resolution, isOpen(step.delayed_resolution) ? 'open' : 'ended', now)
|
|
376
|
+
: null,
|
|
377
|
+
...(step.sub_chart_id
|
|
378
|
+
? subChart
|
|
379
|
+
? { sub_chart: subChart }
|
|
380
|
+
: { sub_chart: null, sub_chart_missing: step.sub_chart_id }
|
|
381
|
+
: {}),
|
|
382
|
+
teaching: "A step is a strategic secondary choice, read in the context of the chart it serves.",
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
387
|
+
return { status: "error", message: `Failed to get action step: ${errorMsg}` };
|
|
388
|
+
}
|
|
245
389
|
},
|
|
246
|
-
required: ["chart_id", "action_step_id", "progress"],
|
|
247
|
-
},
|
|
248
|
-
handler: async (args) => {
|
|
249
|
-
try {
|
|
250
|
-
const chart = (await store.getChart(args.chart_id));
|
|
251
|
-
if (!chart) {
|
|
252
|
-
return { status: "not_found", message: `Chart ${args.chart_id} not found` };
|
|
253
|
-
}
|
|
254
|
-
const actionStep = chart.action_steps.find((s) => s.id === args.action_step_id);
|
|
255
|
-
if (!actionStep) {
|
|
256
|
-
return { status: "not_found", message: `Action step ${args.action_step_id} not found` };
|
|
257
|
-
}
|
|
258
|
-
actionStep.progress = args.progress;
|
|
259
|
-
if (args.progress > 0 && actionStep.status === 'pending') {
|
|
260
|
-
actionStep.status = 'in_progress';
|
|
261
|
-
}
|
|
262
|
-
if (args.new_current_reality) {
|
|
263
|
-
actionStep.current_reality = args.new_current_reality;
|
|
264
|
-
}
|
|
265
|
-
chart.updated_at = new Date().toISOString();
|
|
266
|
-
await store.saveChart(chart);
|
|
267
|
-
return {
|
|
268
|
-
action_step_id: args.action_step_id,
|
|
269
|
-
progress: args.progress,
|
|
270
|
-
status: actionStep.status,
|
|
271
|
-
teaching: "Progress without completion is honest advancement. The structure continues to seek resolution.",
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
catch (error) {
|
|
275
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
276
|
-
return { status: "error", message: `Failed to update progress: ${errorMsg}` };
|
|
277
|
-
}
|
|
278
390
|
},
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
391
|
+
{
|
|
392
|
+
name: "add_action_step",
|
|
393
|
+
description: "Add a strategic secondary choice (action step) to an existing structural tension chart. Each action step is NOT a task — it's a strategic choice that supports the primary goal. Can be telescoped into its own sub-chart. A step without its own due date takes the chart's.",
|
|
394
|
+
inputSchema: {
|
|
395
|
+
type: "object",
|
|
396
|
+
properties: {
|
|
397
|
+
chart_id: {
|
|
398
|
+
type: "string",
|
|
399
|
+
description: "ID of the chart to add action step to",
|
|
400
|
+
},
|
|
401
|
+
title: {
|
|
402
|
+
type: "string",
|
|
403
|
+
description: "Title of the action step (strategic secondary choice)",
|
|
404
|
+
},
|
|
405
|
+
current_reality: {
|
|
406
|
+
type: "string",
|
|
407
|
+
description: "Current reality for this specific action step",
|
|
408
|
+
},
|
|
409
|
+
due_date: {
|
|
410
|
+
type: "string",
|
|
411
|
+
description: "Optional due date for this action step (ISO date). When omitted, the step takes the chart's due date.",
|
|
412
|
+
},
|
|
297
413
|
},
|
|
414
|
+
required: ["chart_id", "title", "current_reality"],
|
|
298
415
|
},
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const actionStep = chart.action_steps.find((s) => s.id === args.action_step_id);
|
|
308
|
-
if (!actionStep) {
|
|
309
|
-
return { status: "not_found", message: `Action step ${args.action_step_id} not found` };
|
|
310
|
-
}
|
|
311
|
-
actionStep.status = 'completed';
|
|
312
|
-
actionStep.progress = 100;
|
|
313
|
-
actionStep.completed_at = new Date().toISOString();
|
|
314
|
-
if (args.ceremony_id) {
|
|
315
|
-
actionStep.ceremonies_honored.push(args.ceremony_id);
|
|
316
|
-
if (!chart.ceremonies_linked.includes(args.ceremony_id)) {
|
|
317
|
-
chart.ceremonies_linked.push(args.ceremony_id);
|
|
416
|
+
handler: async (args) => {
|
|
417
|
+
try {
|
|
418
|
+
let dueDate;
|
|
419
|
+
if (args.due_date !== undefined && args.due_date !== null && args.due_date !== '') {
|
|
420
|
+
const iso = isoOrNull(args.due_date);
|
|
421
|
+
if (!iso)
|
|
422
|
+
return refuse("due_date_invalid", `due_date is not a date: ${JSON.stringify(args.due_date)}. Use an ISO date, e.g. 2026-11-30T17:00:00Z`);
|
|
423
|
+
dueDate = iso;
|
|
318
424
|
}
|
|
425
|
+
const chart = await loadChart(args.chart_id);
|
|
426
|
+
if (!chart) {
|
|
427
|
+
return { status: "not_found", message: `Chart ${args.chart_id} not found` };
|
|
428
|
+
}
|
|
429
|
+
const inherited = dueDate === undefined;
|
|
430
|
+
if (inherited)
|
|
431
|
+
dueDate = isoOrNull(chart.due_date) ?? undefined;
|
|
432
|
+
const actionStep = newStep(nextStepId(chart), args.title, dueDate, args.current_reality);
|
|
433
|
+
chart.action_steps.push(actionStep);
|
|
434
|
+
chart.updated_at = new Date().toISOString();
|
|
435
|
+
await store.saveChart(chart);
|
|
436
|
+
return {
|
|
437
|
+
action_step_id: actionStep.id,
|
|
438
|
+
chart_id: args.chart_id,
|
|
439
|
+
due_date: actionStep.due_date ?? null,
|
|
440
|
+
due_date_inherited: inherited && actionStep.due_date !== undefined,
|
|
441
|
+
total_steps: chart.action_steps.length,
|
|
442
|
+
teaching: "Action steps are strategic secondary choices understood in context of the structural tension, not isolated tasks.",
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
catch (error) {
|
|
446
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
447
|
+
return { status: "error", message: `Failed to add action step: ${errorMsg}` };
|
|
319
448
|
}
|
|
320
|
-
|
|
321
|
-
const completedCount = chart.action_steps.filter((s) => s.status === 'completed').length;
|
|
322
|
-
const totalCount = chart.action_steps.length;
|
|
323
|
-
if (totalCount > 0) {
|
|
324
|
-
if (completedCount === totalCount) {
|
|
325
|
-
chart.phase = 'completion';
|
|
326
|
-
}
|
|
327
|
-
else if (completedCount > 0) {
|
|
328
|
-
chart.phase = 'assimilation';
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
chart.updated_at = new Date().toISOString();
|
|
332
|
-
await store.saveChart(chart);
|
|
333
|
-
const allComplete = completedCount === totalCount && totalCount > 0;
|
|
334
|
-
return {
|
|
335
|
-
action_step_id: args.action_step_id,
|
|
336
|
-
completed_at: actionStep.completed_at,
|
|
337
|
-
chart_phase: chart.phase,
|
|
338
|
-
completed_count: completedCount,
|
|
339
|
-
total_count: totalCount,
|
|
340
|
-
all_complete: allComplete,
|
|
341
|
-
ceremony_suggestion: allComplete
|
|
342
|
-
? "All action steps complete. Consider logging a closing ceremony (direction: north) to honor this achievement."
|
|
343
|
-
: undefined,
|
|
344
|
-
teaching: "Completion flows into current reality. Each completed step advances the whole structure.",
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
catch (error) {
|
|
348
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
349
|
-
return { status: "error", message: `Failed to mark complete: ${errorMsg}` };
|
|
350
|
-
}
|
|
449
|
+
},
|
|
351
450
|
},
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
type: "string",
|
|
361
|
-
description: "ID of the chart",
|
|
362
|
-
},
|
|
363
|
-
new_current_reality: {
|
|
364
|
-
type: "string",
|
|
365
|
-
description: "Updated factual assessment of current reality",
|
|
451
|
+
{
|
|
452
|
+
name: "remove_action_step",
|
|
453
|
+
description: "Remove an action step from a chart. A sub-chart the step telescoped into is not deleted; its id is returned so it can be reached.",
|
|
454
|
+
inputSchema: {
|
|
455
|
+
type: "object",
|
|
456
|
+
properties: {
|
|
457
|
+
chart_id: { type: "string", description: "ID of the chart" },
|
|
458
|
+
step_id: { type: "string", description: "ID of the action step to remove" },
|
|
366
459
|
},
|
|
460
|
+
required: ["chart_id", "step_id"],
|
|
461
|
+
},
|
|
462
|
+
handler: async (args) => {
|
|
463
|
+
try {
|
|
464
|
+
const chart = await loadChart(args.chart_id);
|
|
465
|
+
if (!chart) {
|
|
466
|
+
return { status: "not_found", message: `Chart ${args.chart_id} not found` };
|
|
467
|
+
}
|
|
468
|
+
const index = chart.action_steps.findIndex(s => s.id === args.step_id);
|
|
469
|
+
if (index === -1) {
|
|
470
|
+
return { status: "not_found", message: `Action step ${args.step_id} not found in chart ${args.chart_id}` };
|
|
471
|
+
}
|
|
472
|
+
const [removed] = chart.action_steps.splice(index, 1);
|
|
473
|
+
settlePhase(chart);
|
|
474
|
+
chart.updated_at = new Date().toISOString();
|
|
475
|
+
await store.saveChart(chart);
|
|
476
|
+
return {
|
|
477
|
+
chart_id: chart.id,
|
|
478
|
+
removed_step: removed,
|
|
479
|
+
remaining_steps: chart.action_steps.length,
|
|
480
|
+
...(removed.sub_chart_id ? { sub_chart_left: removed.sub_chart_id } : {}),
|
|
481
|
+
teaching: "Removing a step is a strategic choice too: the structure is rebuilt around what still serves the desired outcome.",
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
catch (error) {
|
|
485
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
486
|
+
return { status: "error", message: `Failed to remove action step: ${errorMsg}` };
|
|
487
|
+
}
|
|
367
488
|
},
|
|
368
|
-
required: ["chart_id", "new_current_reality"],
|
|
369
|
-
},
|
|
370
|
-
handler: async (args) => {
|
|
371
|
-
try {
|
|
372
|
-
const chart = (await store.getChart(args.chart_id));
|
|
373
|
-
if (!chart) {
|
|
374
|
-
return { status: "not_found", message: `Chart ${args.chart_id} not found` };
|
|
375
|
-
}
|
|
376
|
-
const previous = chart.current_reality;
|
|
377
|
-
chart.current_reality = args.new_current_reality;
|
|
378
|
-
chart.updated_at = new Date().toISOString();
|
|
379
|
-
await store.saveChart(chart);
|
|
380
|
-
return {
|
|
381
|
-
chart_id: args.chart_id,
|
|
382
|
-
previous_reality: previous,
|
|
383
|
-
current_reality: args.new_current_reality,
|
|
384
|
-
teaching: "Honest assessment of current reality is essential. The tension between reality and vision drives advancement.",
|
|
385
|
-
};
|
|
386
|
-
}
|
|
387
|
-
catch (error) {
|
|
388
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
389
|
-
return { status: "error", message: `Failed to update current reality: ${errorMsg}` };
|
|
390
|
-
}
|
|
391
489
|
},
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
490
|
+
{
|
|
491
|
+
name: "telescope_action_step",
|
|
492
|
+
description: "Expand an action step into its own structural tension chart (sub-chart). The action step title becomes the desired outcome of the new chart. This enables hierarchical planning while maintaining structural tension at each level.",
|
|
493
|
+
inputSchema: {
|
|
494
|
+
type: "object",
|
|
495
|
+
properties: {
|
|
496
|
+
chart_id: {
|
|
497
|
+
type: "string",
|
|
498
|
+
description: "ID of the parent chart",
|
|
499
|
+
},
|
|
500
|
+
action_step_id: {
|
|
501
|
+
type: "string",
|
|
502
|
+
description: "ID of the action step to telescope",
|
|
503
|
+
},
|
|
504
|
+
new_current_reality: {
|
|
505
|
+
type: "string",
|
|
506
|
+
description: "Current reality for the telescoped chart (required, must be factual)",
|
|
507
|
+
},
|
|
508
|
+
initial_action_steps: {
|
|
509
|
+
type: "array",
|
|
510
|
+
items: { type: "string" },
|
|
511
|
+
description: "Optional initial action steps for the sub-chart",
|
|
512
|
+
},
|
|
402
513
|
},
|
|
514
|
+
required: ["chart_id", "action_step_id", "new_current_reality"],
|
|
515
|
+
},
|
|
516
|
+
handler: async (args) => {
|
|
517
|
+
try {
|
|
518
|
+
const parentChart = await loadChart(args.chart_id);
|
|
519
|
+
if (!parentChart) {
|
|
520
|
+
return { status: "not_found", message: `Parent chart ${args.chart_id} not found` };
|
|
521
|
+
}
|
|
522
|
+
const actionStep = findStep(parentChart, args.action_step_id);
|
|
523
|
+
if (!actionStep) {
|
|
524
|
+
return { status: "not_found", message: `Action step ${args.action_step_id} not found` };
|
|
525
|
+
}
|
|
526
|
+
const subChartId = `stc:${parentChart.direction}:${Date.now()}:sub`;
|
|
527
|
+
const subDue = actionStep.due_date || parentChart.due_date;
|
|
528
|
+
const titles = args.initial_action_steps || [];
|
|
529
|
+
const stepDue = spreadDueDates(subDue, titles.length);
|
|
530
|
+
const subActionSteps = titles.map((title, idx) => newStep(`${subChartId}_action_${idx}`, title, stepDue[idx]));
|
|
531
|
+
const subChart = {
|
|
532
|
+
id: subChartId,
|
|
533
|
+
desired_outcome: actionStep.title,
|
|
534
|
+
current_reality: args.new_current_reality,
|
|
535
|
+
direction: parentChart.direction,
|
|
536
|
+
action_steps: subActionSteps,
|
|
537
|
+
due_date: subDue,
|
|
538
|
+
created_at: new Date().toISOString(),
|
|
539
|
+
updated_at: new Date().toISOString(),
|
|
540
|
+
phase: 'germination',
|
|
541
|
+
ceremonies_linked: [],
|
|
542
|
+
parent_chart_id: parentChart.id,
|
|
543
|
+
parent_action_step_id: actionStep.id,
|
|
544
|
+
level: (typeof parentChart.level === 'number' ? parentChart.level : 0) + 1,
|
|
545
|
+
};
|
|
546
|
+
await store.saveChart(subChart);
|
|
547
|
+
// Link sub-chart to action step
|
|
548
|
+
actionStep.sub_chart_id = subChartId;
|
|
549
|
+
parentChart.updated_at = new Date().toISOString();
|
|
550
|
+
await store.saveChart(parentChart);
|
|
551
|
+
return {
|
|
552
|
+
sub_chart_id: subChartId,
|
|
553
|
+
parent_chart_id: args.chart_id,
|
|
554
|
+
action_step_id: args.action_step_id,
|
|
555
|
+
level: subChart.level,
|
|
556
|
+
desired_outcome: actionStep.title,
|
|
557
|
+
sub_action_steps: subActionSteps.length,
|
|
558
|
+
teaching: "Each action step can become its own structural tension chart. The hierarchy maintains tension at every level.",
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
catch (error) {
|
|
562
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
563
|
+
return { status: "error", message: `Failed to telescope action step: ${errorMsg}` };
|
|
564
|
+
}
|
|
403
565
|
},
|
|
404
|
-
required: ["chart_id"],
|
|
405
566
|
},
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
direction: chart.direction,
|
|
421
|
-
phase: chart.phase,
|
|
422
|
-
desired_outcome: chart.desired_outcome,
|
|
423
|
-
current_reality: chart.current_reality,
|
|
424
|
-
due_date: chart.due_date,
|
|
567
|
+
{
|
|
568
|
+
name: "update_action_progress",
|
|
569
|
+
description: "Update progress on an action step without marking it complete. Progress can advance while work continues.",
|
|
570
|
+
inputSchema: {
|
|
571
|
+
type: "object",
|
|
572
|
+
properties: {
|
|
573
|
+
chart_id: {
|
|
574
|
+
type: "string",
|
|
575
|
+
description: "ID of the chart",
|
|
576
|
+
},
|
|
577
|
+
action_step_id: {
|
|
578
|
+
type: "string",
|
|
579
|
+
description: "ID of the action step",
|
|
580
|
+
},
|
|
425
581
|
progress: {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
status: s.status,
|
|
436
|
-
progress: s.progress,
|
|
437
|
-
has_sub_chart: !!s.sub_chart_id,
|
|
438
|
-
})),
|
|
439
|
-
ceremonies_linked: chart.ceremonies_linked.length,
|
|
440
|
-
teaching: "Progress is not linear. The structure advances through maintained tension, not willpower.",
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
catch (error) {
|
|
444
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
445
|
-
return { status: "error", message: `Failed to get chart progress: ${errorMsg}` };
|
|
446
|
-
}
|
|
447
|
-
},
|
|
448
|
-
},
|
|
449
|
-
{
|
|
450
|
-
name: "list_structural_tension_charts",
|
|
451
|
-
description: "List all active structural tension charts. Filter by medicine wheel direction.",
|
|
452
|
-
inputSchema: {
|
|
453
|
-
type: "object",
|
|
454
|
-
properties: {
|
|
455
|
-
direction: {
|
|
456
|
-
type: "string",
|
|
457
|
-
enum: ["east", "south", "west", "north"],
|
|
458
|
-
description: "Filter by medicine wheel direction (optional)",
|
|
582
|
+
type: "number",
|
|
583
|
+
description: "Progress percentage (0-100)",
|
|
584
|
+
minimum: 0,
|
|
585
|
+
maximum: 100,
|
|
586
|
+
},
|
|
587
|
+
new_current_reality: {
|
|
588
|
+
type: "string",
|
|
589
|
+
description: "Optional updated current reality for this action step",
|
|
590
|
+
},
|
|
459
591
|
},
|
|
592
|
+
required: ["chart_id", "action_step_id", "progress"],
|
|
593
|
+
},
|
|
594
|
+
handler: async (args) => {
|
|
595
|
+
try {
|
|
596
|
+
const chart = await loadChart(args.chart_id);
|
|
597
|
+
if (!chart) {
|
|
598
|
+
return { status: "not_found", message: `Chart ${args.chart_id} not found` };
|
|
599
|
+
}
|
|
600
|
+
const actionStep = findStep(chart, args.action_step_id);
|
|
601
|
+
if (!actionStep) {
|
|
602
|
+
return { status: "not_found", message: `Action step ${args.action_step_id} not found` };
|
|
603
|
+
}
|
|
604
|
+
actionStep.progress = args.progress;
|
|
605
|
+
if (args.progress > 0 && actionStep.status === 'pending') {
|
|
606
|
+
actionStep.status = 'in_progress';
|
|
607
|
+
}
|
|
608
|
+
if (args.new_current_reality) {
|
|
609
|
+
actionStep.current_reality = args.new_current_reality;
|
|
610
|
+
}
|
|
611
|
+
chart.updated_at = new Date().toISOString();
|
|
612
|
+
await store.saveChart(chart);
|
|
613
|
+
return {
|
|
614
|
+
action_step_id: args.action_step_id,
|
|
615
|
+
progress: args.progress,
|
|
616
|
+
status: actionStep.status,
|
|
617
|
+
teaching: "Progress without completion is honest advancement. The structure continues to seek resolution.",
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
catch (error) {
|
|
621
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
622
|
+
return { status: "error", message: `Failed to update progress: ${errorMsg}` };
|
|
623
|
+
}
|
|
460
624
|
},
|
|
461
625
|
},
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
626
|
+
{
|
|
627
|
+
name: "mark_action_complete",
|
|
628
|
+
description: "Mark an action step as complete and add 'Completed: <title>' to the chart's current reality. Optionally link a ceremony that honored this completion. A step completed while its resolution was delayed ends that delay with outcome 'resolved'.",
|
|
629
|
+
inputSchema: {
|
|
630
|
+
type: "object",
|
|
631
|
+
properties: {
|
|
632
|
+
chart_id: {
|
|
633
|
+
type: "string",
|
|
634
|
+
description: "ID of the chart",
|
|
635
|
+
},
|
|
636
|
+
action_step_id: {
|
|
637
|
+
type: "string",
|
|
638
|
+
description: "ID of the action step to complete",
|
|
639
|
+
},
|
|
640
|
+
ceremony_id: {
|
|
641
|
+
type: "string",
|
|
642
|
+
description: "Optional ceremony ID that honored this completion",
|
|
643
|
+
},
|
|
644
|
+
completed_by: {
|
|
645
|
+
type: "string",
|
|
646
|
+
description: "Optional: the person or seat completing the step. Recorded as ended_by when the completion ends a delayed resolution.",
|
|
647
|
+
},
|
|
648
|
+
},
|
|
649
|
+
required: ["chart_id", "action_step_id"],
|
|
650
|
+
},
|
|
651
|
+
handler: async (args) => {
|
|
652
|
+
try {
|
|
653
|
+
const chart = await loadChart(args.chart_id);
|
|
654
|
+
if (!chart) {
|
|
655
|
+
return { status: "not_found", message: `Chart ${args.chart_id} not found` };
|
|
656
|
+
}
|
|
657
|
+
const actionStep = findStep(chart, args.action_step_id);
|
|
658
|
+
if (!actionStep) {
|
|
659
|
+
return { status: "not_found", message: `Action step ${args.action_step_id} not found` };
|
|
660
|
+
}
|
|
661
|
+
const wasCompleted = actionStep.status === 'completed';
|
|
662
|
+
const now = new Date().toISOString();
|
|
663
|
+
let delayEnded;
|
|
664
|
+
if (isOpen(actionStep.delayed_resolution)) {
|
|
665
|
+
// Rule 4b: resolution reached is the third way a delay ends.
|
|
666
|
+
Object.assign(actionStep.delayed_resolution, {
|
|
667
|
+
ended_at: now,
|
|
668
|
+
ended_by: nonEmpty(args.completed_by) ?? 'mark_action_complete',
|
|
669
|
+
outcome: 'resolved',
|
|
670
|
+
end_note: 'completed',
|
|
671
|
+
});
|
|
672
|
+
delayEnded = actionStep.delayed_resolution;
|
|
673
|
+
}
|
|
674
|
+
actionStep.status = 'completed';
|
|
675
|
+
actionStep.progress = 100;
|
|
676
|
+
if (!wasCompleted)
|
|
677
|
+
actionStep.completed_at = now;
|
|
678
|
+
if (args.ceremony_id) {
|
|
679
|
+
actionStep.ceremonies_honored.push(args.ceremony_id);
|
|
680
|
+
if (!chart.ceremonies_linked.includes(args.ceremony_id)) {
|
|
681
|
+
chart.ceremonies_linked.push(args.ceremony_id);
|
|
491
682
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
683
|
+
}
|
|
684
|
+
// Completion flows into current reality, once.
|
|
685
|
+
if (!wasCompleted)
|
|
686
|
+
appendReality(chart, `Completed: ${actionStep.title}`);
|
|
687
|
+
const { completed: completedCount, total: totalCount } = settlePhase(chart);
|
|
688
|
+
chart.updated_at = now;
|
|
689
|
+
await store.saveChart(chart);
|
|
690
|
+
const allComplete = completedCount === totalCount && totalCount > 0;
|
|
691
|
+
return {
|
|
692
|
+
action_step_id: args.action_step_id,
|
|
693
|
+
completed_at: actionStep.completed_at,
|
|
694
|
+
chart_phase: chart.phase,
|
|
695
|
+
completed_count: completedCount,
|
|
696
|
+
total_count: totalCount,
|
|
697
|
+
all_complete: allComplete,
|
|
698
|
+
current_reality: chart.current_reality,
|
|
699
|
+
...(delayEnded ? { delayed_resolution_ended: delayEnded } : {}),
|
|
700
|
+
ceremony_suggestion: allComplete
|
|
701
|
+
? "All action steps complete. Consider logging a closing ceremony (direction: north) to honor this achievement."
|
|
702
|
+
: undefined,
|
|
703
|
+
teaching: "Completion flows into current reality. Each completed step advances the whole structure.",
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
catch (error) {
|
|
707
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
708
|
+
return { status: "error", message: `Failed to mark complete: ${errorMsg}` };
|
|
709
|
+
}
|
|
710
|
+
},
|
|
500
711
|
},
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
type: "string",
|
|
510
|
-
description: "
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
712
|
+
{
|
|
713
|
+
name: "delay_resolution",
|
|
714
|
+
description: "Record on an action step that someone strategically chose to delay its resolution: when resolution is wanted (resolve_by, in the future), why waiting serves the work now, what the delay serves (defaults to the chart's desired outcome), event cues to wake on, and the agent session to rehydrate. Sets the step's due date to resolve_by and its status to 'delayed'. Re-delaying keeps the earlier record in delayed_resolution_history. A completed step cannot be delayed.",
|
|
715
|
+
inputSchema: {
|
|
716
|
+
type: "object",
|
|
717
|
+
properties: {
|
|
718
|
+
chart_id: { type: "string", description: "ID of the chart holding the step" },
|
|
719
|
+
step_id: { type: "string", description: "ID of the action step" },
|
|
720
|
+
resolve_by: { type: "string", description: "When resolution is wanted (ISO date-time, in the future). Becomes the step's due date." },
|
|
721
|
+
reason: { type: "string", description: "Why delaying resolution serves the work now" },
|
|
722
|
+
chosen_by: { type: "string", description: "The person or seat that made the choice" },
|
|
723
|
+
primary_choice: { type: "string", description: "What the delay serves. Defaults to the chart's desired outcome, copied now." },
|
|
724
|
+
wake_on: {
|
|
725
|
+
type: "array",
|
|
726
|
+
items: { type: "string" },
|
|
727
|
+
description: "Event cues, in words, that should end the wait before resolve_by",
|
|
728
|
+
},
|
|
729
|
+
session: {
|
|
730
|
+
type: "object",
|
|
731
|
+
description: "The agent session to rehydrate when the delay ends",
|
|
732
|
+
properties: {
|
|
733
|
+
session_id: { type: "string", description: "Claude Code session id, or another agent's" },
|
|
734
|
+
agent: { type: "string", description: "Agent kind (default 'claude')" },
|
|
735
|
+
host: { type: "string", description: "Host the session lives on (gaia, ilex, eury)" },
|
|
736
|
+
cwd: { type: "string", description: "Working directory of the session" },
|
|
737
|
+
tmux: { type: "string", description: "tmux session or pane" },
|
|
738
|
+
resume_command: { type: "string", description: "Command that resumes it, e.g. `cd <cwd> && claude --resume <id>`" },
|
|
739
|
+
inventory_record: { type: "string", description: "Path of its record in the session inventory" },
|
|
740
|
+
},
|
|
741
|
+
required: ["session_id"],
|
|
742
|
+
},
|
|
532
743
|
},
|
|
744
|
+
required: ["chart_id", "step_id", "resolve_by", "reason", "chosen_by"],
|
|
745
|
+
},
|
|
746
|
+
handler: async (args) => {
|
|
747
|
+
try {
|
|
748
|
+
const reason = nonEmpty(args.reason);
|
|
749
|
+
if (!reason)
|
|
750
|
+
return refuse("reason_missing", "reason is required: say why delaying resolution serves the work now");
|
|
751
|
+
const chosenBy = nonEmpty(args.chosen_by);
|
|
752
|
+
if (!chosenBy)
|
|
753
|
+
return refuse("chosen_by_missing", "chosen_by is required: the person or seat that made the choice");
|
|
754
|
+
const resolveBy = isoOrNull(args.resolve_by);
|
|
755
|
+
if (!resolveBy) {
|
|
756
|
+
return refuse("resolve_by_invalid", `resolve_by is not a date: ${JSON.stringify(args.resolve_by)}. Use an ISO date-time, e.g. 2026-11-02T09:00:00Z`);
|
|
757
|
+
}
|
|
758
|
+
const now = Date.now();
|
|
759
|
+
if (Date.parse(resolveBy) <= now) {
|
|
760
|
+
return refuse("resolve_by_past", `resolve_by ${resolveBy} is not in the future. A delay names when resolution is wanted, later than now.`);
|
|
761
|
+
}
|
|
762
|
+
const { session, error: sessionError } = normalizeSession(args.session);
|
|
763
|
+
if (sessionError)
|
|
764
|
+
return refuse("session_invalid", sessionError);
|
|
765
|
+
const chart = await loadChart(args.chart_id);
|
|
766
|
+
if (!chart) {
|
|
767
|
+
return { status: "not_found", message: `Chart ${args.chart_id} not found` };
|
|
768
|
+
}
|
|
769
|
+
const step = findStep(chart, args.step_id);
|
|
770
|
+
if (!step) {
|
|
771
|
+
return { status: "not_found", message: `Action step ${args.step_id} not found in chart ${args.chart_id}` };
|
|
772
|
+
}
|
|
773
|
+
if (step.status === 'completed') {
|
|
774
|
+
return refuse("step_completed", `Action step ${step.id} is completed; a completed step cannot be delayed.`);
|
|
775
|
+
}
|
|
776
|
+
const record = {
|
|
777
|
+
chosen_at: new Date(now).toISOString(),
|
|
778
|
+
chosen_by: chosenBy,
|
|
779
|
+
resolve_by: resolveBy,
|
|
780
|
+
reason,
|
|
781
|
+
primary_choice: nonEmpty(args.primary_choice) ?? chart.desired_outcome,
|
|
782
|
+
};
|
|
783
|
+
const wakeOn = normalizeWakeOn(args.wake_on);
|
|
784
|
+
if (wakeOn)
|
|
785
|
+
record.wake_on = wakeOn;
|
|
786
|
+
if (session)
|
|
787
|
+
record.session = session;
|
|
788
|
+
// Nothing is overwritten: an earlier record, open or ended, moves to history.
|
|
789
|
+
const previous = step.delayed_resolution;
|
|
790
|
+
if (previous) {
|
|
791
|
+
step.delayed_resolution_history = [...(step.delayed_resolution_history ?? []), previous];
|
|
792
|
+
}
|
|
793
|
+
step.delayed_resolution = record;
|
|
794
|
+
step.due_date = resolveBy;
|
|
795
|
+
step.status = 'delayed';
|
|
796
|
+
chart.updated_at = new Date(now).toISOString();
|
|
797
|
+
await store.saveChart(chart);
|
|
798
|
+
return {
|
|
799
|
+
chart_id: chart.id,
|
|
800
|
+
step_id: step.id,
|
|
801
|
+
status: step.status,
|
|
802
|
+
due_date: step.due_date,
|
|
803
|
+
days_remaining: daysRemaining(resolveBy, now),
|
|
804
|
+
delayed_resolution: record,
|
|
805
|
+
redelayed: !!previous && isOpen(previous),
|
|
806
|
+
history_length: step.delayed_resolution_history?.length ?? 0,
|
|
807
|
+
step,
|
|
808
|
+
teaching: "Delaying resolution is a secondary choice in service of a primary choice. The tension is held on purpose, with a date when resolution is wanted.",
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
catch (error) {
|
|
812
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
813
|
+
return { status: "error", message: `Failed to delay resolution: ${errorMsg}` };
|
|
814
|
+
}
|
|
533
815
|
},
|
|
534
|
-
required: ["chart_id", "expected_outcome", "actual_outcome", "analysis", "adjustments"],
|
|
535
816
|
},
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
817
|
+
{
|
|
818
|
+
name: "list_delayed_resolutions",
|
|
819
|
+
description: "List open delayed resolutions across all charts, sorted by resolve_by (soonest first), with days_remaining and owed (resolve_by has passed). Each row names the step, its chart, the primary choice, wake_on cues and the session to rehydrate.",
|
|
820
|
+
inputSchema: {
|
|
821
|
+
type: "object",
|
|
822
|
+
properties: {
|
|
823
|
+
due_within_days: {
|
|
824
|
+
type: "number",
|
|
825
|
+
minimum: 0,
|
|
826
|
+
description: "Only delays whose resolve_by falls within this many days from now (owed ones included)",
|
|
827
|
+
},
|
|
828
|
+
include_ended: {
|
|
829
|
+
type: "boolean",
|
|
830
|
+
description: "Also list ended delays and earlier records kept in history (default false)",
|
|
831
|
+
},
|
|
832
|
+
},
|
|
833
|
+
},
|
|
834
|
+
handler: async (args) => {
|
|
835
|
+
try {
|
|
836
|
+
let windowEnd = Infinity;
|
|
837
|
+
const now = Date.now();
|
|
838
|
+
if (args.due_within_days !== undefined && args.due_within_days !== null) {
|
|
839
|
+
const days = Number(args.due_within_days);
|
|
840
|
+
if (!Number.isFinite(days) || days < 0) {
|
|
841
|
+
return refuse("due_within_days_invalid", `due_within_days must be a number of days, 0 or more: ${JSON.stringify(args.due_within_days)}`);
|
|
842
|
+
}
|
|
843
|
+
windowEnd = now + days * DAY_MS;
|
|
844
|
+
}
|
|
845
|
+
const includeEnded = args.include_ended === true || args.include_ended === 'true';
|
|
846
|
+
const charts = (await store.getAllCharts());
|
|
847
|
+
const rows = [];
|
|
848
|
+
for (const chart of charts) {
|
|
849
|
+
for (const step of chart.action_steps ?? []) {
|
|
850
|
+
const current = step.delayed_resolution;
|
|
851
|
+
if (current) {
|
|
852
|
+
const state = isOpen(current) ? 'open' : 'ended';
|
|
853
|
+
if (state === 'open' || includeEnded)
|
|
854
|
+
rows.push(delayRow(chart, step, current, state, now));
|
|
855
|
+
}
|
|
856
|
+
if (includeEnded) {
|
|
857
|
+
for (const past of step.delayed_resolution_history ?? []) {
|
|
858
|
+
rows.push(delayRow(chart, step, past, past.ended_at ? 'ended' : 'superseded', now));
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
const listed = rows
|
|
864
|
+
.filter(r => Date.parse(r.resolve_by) <= windowEnd)
|
|
865
|
+
.sort((a, b) => Date.parse(a.resolve_by) - Date.parse(b.resolve_by));
|
|
866
|
+
return {
|
|
867
|
+
count: listed.length,
|
|
868
|
+
open_count: listed.filter(r => r.state === 'open').length,
|
|
869
|
+
owed_count: listed.filter(r => r.owed).length,
|
|
870
|
+
as_of: new Date(now).toISOString(),
|
|
871
|
+
filter: {
|
|
872
|
+
due_within_days: args.due_within_days ?? null,
|
|
873
|
+
include_ended: includeEnded,
|
|
565
874
|
},
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
576
|
-
return { status: "error", message: `Failed to complete MMOT: ${errorMsg}` };
|
|
577
|
-
}
|
|
875
|
+
delays: listed,
|
|
876
|
+
teaching: "A delay whose date has passed is owed: the choice to wait has come due and asks to be resolved, re-chosen, or released.",
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
catch (error) {
|
|
880
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
881
|
+
return { status: "error", message: `Failed to list delayed resolutions: ${errorMsg}` };
|
|
882
|
+
}
|
|
883
|
+
},
|
|
578
884
|
},
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
type: "string",
|
|
588
|
-
|
|
885
|
+
{
|
|
886
|
+
name: "end_delayed_resolution",
|
|
887
|
+
description: "End a step's open delayed resolution, either by rehydrating its agent session or by releasing the step. The record is kept with ended_at, ended_by and outcome, and the step returns to pending or in_progress. When rehydrated, returns the session's resume_command.",
|
|
888
|
+
inputSchema: {
|
|
889
|
+
type: "object",
|
|
890
|
+
properties: {
|
|
891
|
+
chart_id: { type: "string", description: "ID of the chart holding the step" },
|
|
892
|
+
step_id: { type: "string", description: "ID of the action step" },
|
|
893
|
+
ended_by: { type: "string", description: "The person or seat ending the delay" },
|
|
894
|
+
outcome: {
|
|
895
|
+
type: "string",
|
|
896
|
+
enum: ["rehydrated", "released"],
|
|
897
|
+
description: "rehydrated: the session is being resumed; released: the step is let go from the wait",
|
|
898
|
+
},
|
|
899
|
+
end_note: { type: "string", description: "Optional note on how the delay ended" },
|
|
589
900
|
},
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
901
|
+
required: ["chart_id", "step_id", "ended_by", "outcome"],
|
|
902
|
+
},
|
|
903
|
+
handler: async (args) => {
|
|
904
|
+
try {
|
|
905
|
+
const endedBy = nonEmpty(args.ended_by);
|
|
906
|
+
if (!endedBy)
|
|
907
|
+
return refuse("ended_by_missing", "ended_by is required: the person or seat ending the delay");
|
|
908
|
+
if (args.outcome !== 'rehydrated' && args.outcome !== 'released') {
|
|
909
|
+
return refuse("outcome_invalid", `outcome must be 'rehydrated' or 'released', got ${JSON.stringify(args.outcome)}`);
|
|
910
|
+
}
|
|
911
|
+
const chart = await loadChart(args.chart_id);
|
|
912
|
+
if (!chart) {
|
|
913
|
+
return { status: "not_found", message: `Chart ${args.chart_id} not found` };
|
|
914
|
+
}
|
|
915
|
+
const step = findStep(chart, args.step_id);
|
|
916
|
+
if (!step) {
|
|
917
|
+
return { status: "not_found", message: `Action step ${args.step_id} not found in chart ${args.chart_id}` };
|
|
918
|
+
}
|
|
919
|
+
const delay = step.delayed_resolution;
|
|
920
|
+
if (!isOpen(delay)) {
|
|
921
|
+
return refuse("no_open_delay", `Action step ${step.id} has no open delayed resolution to end.`);
|
|
922
|
+
}
|
|
923
|
+
const now = new Date().toISOString();
|
|
924
|
+
delay.ended_at = now;
|
|
925
|
+
delay.ended_by = endedBy;
|
|
926
|
+
delay.outcome = args.outcome;
|
|
927
|
+
const note = nonEmpty(args.end_note);
|
|
928
|
+
if (note)
|
|
929
|
+
delay.end_note = note;
|
|
930
|
+
if (step.status === 'delayed')
|
|
931
|
+
step.status = statusAfterDelay(step);
|
|
932
|
+
chart.updated_at = now;
|
|
933
|
+
await store.saveChart(chart);
|
|
934
|
+
const resume = args.outcome === 'rehydrated' ? resumeCommandFor(delay.session) : undefined;
|
|
935
|
+
return {
|
|
936
|
+
chart_id: chart.id,
|
|
937
|
+
step_id: step.id,
|
|
938
|
+
status: step.status,
|
|
939
|
+
outcome: delay.outcome,
|
|
940
|
+
delayed_resolution: delay,
|
|
941
|
+
...(resume
|
|
942
|
+
? {
|
|
943
|
+
session: delay.session ?? null,
|
|
944
|
+
...resume,
|
|
945
|
+
...(resume.resume_command === null
|
|
946
|
+
? { note: delay.session ? `No resume command is known for agent '${delay.session.agent}'.` : 'This delay carried no session to rehydrate.' }
|
|
947
|
+
: {}),
|
|
948
|
+
}
|
|
949
|
+
: {}),
|
|
950
|
+
teaching: args.outcome === 'rehydrated'
|
|
951
|
+
? "Rehydrating brings the waiting session back with its state, so the work resumes where the choice to wait left it."
|
|
952
|
+
: "Releasing ends the wait. The step returns to the structure as an ordinary secondary choice.",
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
catch (error) {
|
|
956
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
957
|
+
return { status: "error", message: `Failed to end delayed resolution: ${errorMsg}` };
|
|
958
|
+
}
|
|
959
|
+
},
|
|
960
|
+
},
|
|
961
|
+
{
|
|
962
|
+
name: "update_current_reality",
|
|
963
|
+
description: "Update the current reality assessment of a chart. Current reality should be updated as circumstances change. Always factual, never readiness statements.",
|
|
964
|
+
inputSchema: {
|
|
965
|
+
type: "object",
|
|
966
|
+
properties: {
|
|
967
|
+
chart_id: {
|
|
968
|
+
type: "string",
|
|
969
|
+
description: "ID of the chart",
|
|
970
|
+
},
|
|
971
|
+
new_current_reality: {
|
|
972
|
+
type: "string",
|
|
973
|
+
description: "Updated factual assessment of current reality",
|
|
974
|
+
},
|
|
593
975
|
},
|
|
976
|
+
required: ["chart_id", "new_current_reality"],
|
|
977
|
+
},
|
|
978
|
+
handler: async (args) => {
|
|
979
|
+
try {
|
|
980
|
+
const chart = await loadChart(args.chart_id);
|
|
981
|
+
if (!chart) {
|
|
982
|
+
return { status: "not_found", message: `Chart ${args.chart_id} not found` };
|
|
983
|
+
}
|
|
984
|
+
const previous = chart.current_reality;
|
|
985
|
+
chart.current_reality = args.new_current_reality;
|
|
986
|
+
chart.updated_at = new Date().toISOString();
|
|
987
|
+
await store.saveChart(chart);
|
|
988
|
+
return {
|
|
989
|
+
chart_id: args.chart_id,
|
|
990
|
+
previous_reality: previous,
|
|
991
|
+
current_reality: args.new_current_reality,
|
|
992
|
+
teaching: "Honest assessment of current reality is essential. The tension between reality and vision drives advancement.",
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
catch (error) {
|
|
996
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
997
|
+
return { status: "error", message: `Failed to update current reality: ${errorMsg}` };
|
|
998
|
+
}
|
|
594
999
|
},
|
|
595
|
-
required: ["chart_id", "ceremony_id"],
|
|
596
1000
|
},
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
1001
|
+
{
|
|
1002
|
+
name: "update_desired_outcome",
|
|
1003
|
+
description: "Replace the desired outcome of a chart. The desired outcome names what is being created, not a problem to remove.",
|
|
1004
|
+
inputSchema: {
|
|
1005
|
+
type: "object",
|
|
1006
|
+
properties: {
|
|
1007
|
+
chart_id: { type: "string", description: "ID of the chart" },
|
|
1008
|
+
new_desired_outcome: { type: "string", description: "What you want to CREATE, stated as something that will exist" },
|
|
1009
|
+
},
|
|
1010
|
+
required: ["chart_id", "new_desired_outcome"],
|
|
1011
|
+
},
|
|
1012
|
+
handler: async (args) => {
|
|
1013
|
+
try {
|
|
1014
|
+
const outcome = nonEmpty(args.new_desired_outcome);
|
|
1015
|
+
if (!outcome)
|
|
1016
|
+
return refuse("desired_outcome_missing", "new_desired_outcome must be a non-empty statement of what is being created");
|
|
1017
|
+
const chart = await loadChart(args.chart_id);
|
|
1018
|
+
if (!chart) {
|
|
1019
|
+
return { status: "not_found", message: `Chart ${args.chart_id} not found` };
|
|
1020
|
+
}
|
|
1021
|
+
const previous = chart.desired_outcome;
|
|
1022
|
+
chart.desired_outcome = outcome;
|
|
605
1023
|
chart.updated_at = new Date().toISOString();
|
|
606
|
-
store.saveChart(chart);
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
}
|
|
1024
|
+
await store.saveChart(chart);
|
|
1025
|
+
return {
|
|
1026
|
+
chart_id: chart.id,
|
|
1027
|
+
previous_desired_outcome: previous,
|
|
1028
|
+
desired_outcome: outcome,
|
|
1029
|
+
teaching: "Changing the desired outcome changes the whole structure: every step is re-read against what is now being created.",
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
catch (error) {
|
|
1033
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1034
|
+
return { status: "error", message: `Failed to update desired outcome: ${errorMsg}` };
|
|
1035
|
+
}
|
|
1036
|
+
},
|
|
619
1037
|
},
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
1038
|
+
{
|
|
1039
|
+
name: "update_chart_due_date",
|
|
1040
|
+
description: "Move a chart's due date. The date is stored in ISO form and the move is recorded in the chart's due_date_history. Optionally respread the due dates of open, non-delayed steps between now and the new date. Reports how many open steps fall after the new date.",
|
|
1041
|
+
inputSchema: {
|
|
1042
|
+
type: "object",
|
|
1043
|
+
properties: {
|
|
1044
|
+
chart_id: { type: "string", description: "ID of the chart" },
|
|
1045
|
+
due_date: { type: "string", description: "The new due date (ISO date)" },
|
|
1046
|
+
redistribute_action_steps: {
|
|
1047
|
+
type: "boolean",
|
|
1048
|
+
description: "Respread open steps' due dates between now and the new date (default false). Steps with an open delay keep their resolve_by.",
|
|
1049
|
+
},
|
|
630
1050
|
},
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
1051
|
+
required: ["chart_id", "due_date"],
|
|
1052
|
+
},
|
|
1053
|
+
handler: async (args) => {
|
|
1054
|
+
try {
|
|
1055
|
+
const iso = isoOrNull(args.due_date);
|
|
1056
|
+
if (!iso)
|
|
1057
|
+
return refuse("due_date_invalid", `due_date is not a date: ${JSON.stringify(args.due_date)}. Use an ISO date, e.g. 2026-11-30T17:00:00Z`);
|
|
1058
|
+
const chart = await loadChart(args.chart_id);
|
|
1059
|
+
if (!chart) {
|
|
1060
|
+
return { status: "not_found", message: `Chart ${args.chart_id} not found` };
|
|
1061
|
+
}
|
|
1062
|
+
const now = Date.now();
|
|
1063
|
+
const at = new Date(now).toISOString();
|
|
1064
|
+
const previous = chart.due_date ?? null;
|
|
1065
|
+
chart.due_date = iso;
|
|
1066
|
+
chart.due_date_history = [...(chart.due_date_history ?? []), { from: previous, to: iso, at }];
|
|
1067
|
+
const open = chart.action_steps.filter(s => s.status !== 'completed');
|
|
1068
|
+
let rescheduled = 0;
|
|
1069
|
+
if (args.redistribute_action_steps === true || args.redistribute_action_steps === 'true') {
|
|
1070
|
+
const movable = open
|
|
1071
|
+
.filter(s => !isOpen(s.delayed_resolution))
|
|
1072
|
+
.sort((a, b) => {
|
|
1073
|
+
const da = a.due_date ? Date.parse(a.due_date) : Infinity;
|
|
1074
|
+
const db = b.due_date ? Date.parse(b.due_date) : Infinity;
|
|
1075
|
+
return da - db;
|
|
1076
|
+
});
|
|
1077
|
+
const dates = spreadDueDates(iso, movable.length, now);
|
|
1078
|
+
movable.forEach((s, i) => { s.due_date = dates[i]; });
|
|
1079
|
+
rescheduled = movable.length;
|
|
1080
|
+
}
|
|
1081
|
+
const newTime = Date.parse(iso);
|
|
1082
|
+
const pastDue = open.filter(s => s.due_date && Date.parse(s.due_date) > newTime);
|
|
1083
|
+
chart.updated_at = at;
|
|
1084
|
+
await store.saveChart(chart);
|
|
1085
|
+
return {
|
|
1086
|
+
chart_id: chart.id,
|
|
1087
|
+
previous_due_date: previous,
|
|
1088
|
+
new_due_date: iso,
|
|
1089
|
+
action_steps_rescheduled: rescheduled,
|
|
1090
|
+
action_steps_past_due_date: pastDue.length,
|
|
1091
|
+
...(pastDue.length > 0 ? { steps_after_due_date: pastDue.map(s => ({ id: s.id, title: s.title, due_date: s.due_date, status: s.status })) } : {}),
|
|
1092
|
+
teaching: "The due date is part of the structure. Moving it is a choice that every open step is read against.",
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
catch (error) {
|
|
1096
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1097
|
+
return { status: "error", message: `Failed to update chart due date: ${errorMsg}` };
|
|
1098
|
+
}
|
|
1099
|
+
},
|
|
1100
|
+
},
|
|
1101
|
+
{
|
|
1102
|
+
name: "get_chart_progress",
|
|
1103
|
+
description: "Get progress report for a structural tension chart. Shows desired outcome, current reality, action step status and due dates, delayed resolutions (owed when their date has passed), the next action, and overall phase.",
|
|
1104
|
+
inputSchema: {
|
|
1105
|
+
type: "object",
|
|
1106
|
+
properties: {
|
|
1107
|
+
chart_id: {
|
|
1108
|
+
type: "string",
|
|
1109
|
+
description: "ID of the chart",
|
|
1110
|
+
},
|
|
634
1111
|
},
|
|
1112
|
+
required: ["chart_id"],
|
|
1113
|
+
},
|
|
1114
|
+
handler: async (args) => {
|
|
1115
|
+
try {
|
|
1116
|
+
const chart = await loadChart(args.chart_id);
|
|
1117
|
+
if (!chart) {
|
|
1118
|
+
return { status: "not_found", message: `Chart ${args.chart_id} not found` };
|
|
1119
|
+
}
|
|
1120
|
+
const now = Date.now();
|
|
1121
|
+
const steps = chart.action_steps;
|
|
1122
|
+
const count = (status) => steps.filter(s => s.status === status).length;
|
|
1123
|
+
const avgProgress = steps.length > 0
|
|
1124
|
+
? steps.reduce((sum, s) => sum + (s.progress ?? 0), 0) / steps.length
|
|
1125
|
+
: 0;
|
|
1126
|
+
// The next action is the open, not-delayed step due soonest. A delayed
|
|
1127
|
+
// step was chosen to wait; when its date passes it is reported as owed.
|
|
1128
|
+
const next = steps
|
|
1129
|
+
.filter(s => s.status !== 'completed' && !isOpen(s.delayed_resolution))
|
|
1130
|
+
.sort((a, b) => {
|
|
1131
|
+
const da = a.due_date ? Date.parse(a.due_date) : Infinity;
|
|
1132
|
+
const db = b.due_date ? Date.parse(b.due_date) : Infinity;
|
|
1133
|
+
return da - db;
|
|
1134
|
+
})[0];
|
|
1135
|
+
return {
|
|
1136
|
+
chart_id: chart.id,
|
|
1137
|
+
direction: chart.direction,
|
|
1138
|
+
phase: chart.phase,
|
|
1139
|
+
desired_outcome: chart.desired_outcome,
|
|
1140
|
+
current_reality: chart.current_reality,
|
|
1141
|
+
due_date: chart.due_date,
|
|
1142
|
+
progress: {
|
|
1143
|
+
overall_percentage: Math.round(avgProgress),
|
|
1144
|
+
completed: count('completed'),
|
|
1145
|
+
in_progress: count('in_progress'),
|
|
1146
|
+
pending: count('pending'),
|
|
1147
|
+
delayed: count('delayed'),
|
|
1148
|
+
total: steps.length,
|
|
1149
|
+
},
|
|
1150
|
+
...openDelayCounts(chart, now),
|
|
1151
|
+
next_action: next ? { id: next.id, title: next.title, status: next.status, due_date: next.due_date ?? null } : null,
|
|
1152
|
+
action_steps: steps.map(s => stepSummary(s, now)),
|
|
1153
|
+
ceremonies_linked: chart.ceremonies_linked.length,
|
|
1154
|
+
teaching: "Progress is not linear. The structure advances through maintained tension, not willpower.",
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
catch (error) {
|
|
1158
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1159
|
+
return { status: "error", message: `Failed to get chart progress: ${errorMsg}` };
|
|
1160
|
+
}
|
|
635
1161
|
},
|
|
636
|
-
required: ["chart_id", "cycle_id"],
|
|
637
1162
|
},
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
1163
|
+
{
|
|
1164
|
+
name: "list_structural_tension_charts",
|
|
1165
|
+
description: "List all active structural tension charts, each with its steps' status, due dates and delayed resolutions. Filter by medicine wheel direction.",
|
|
1166
|
+
inputSchema: {
|
|
1167
|
+
type: "object",
|
|
1168
|
+
properties: {
|
|
1169
|
+
direction: {
|
|
1170
|
+
type: "string",
|
|
1171
|
+
enum: ["east", "south", "west", "north"],
|
|
1172
|
+
description: "Filter by medicine wheel direction (optional)",
|
|
1173
|
+
},
|
|
1174
|
+
},
|
|
1175
|
+
},
|
|
1176
|
+
handler: async (args) => {
|
|
1177
|
+
try {
|
|
1178
|
+
const charts = (await store.getAllCharts(args.direction));
|
|
1179
|
+
const now = Date.now();
|
|
1180
|
+
return {
|
|
1181
|
+
count: charts.length,
|
|
1182
|
+
filter: args.direction || 'all',
|
|
1183
|
+
charts: charts.map(c => ({
|
|
1184
|
+
id: c.id,
|
|
1185
|
+
direction: c.direction,
|
|
1186
|
+
desired_outcome: c.desired_outcome,
|
|
1187
|
+
current_reality: c.current_reality,
|
|
1188
|
+
phase: c.phase,
|
|
1189
|
+
action_steps_count: c.action_steps.length,
|
|
1190
|
+
completed_steps: c.action_steps.filter(s => s.status === 'completed').length,
|
|
1191
|
+
...openDelayCounts(c, now),
|
|
1192
|
+
due_date: c.due_date,
|
|
1193
|
+
updated_at: c.updated_at,
|
|
1194
|
+
...(c.github_issue ? { github_issue: c.github_issue.ref } : {}),
|
|
1195
|
+
...(c.status ? { status: c.status } : {}),
|
|
1196
|
+
...(c.parent_chart_id ? { parent_chart_id: c.parent_chart_id, parent_action_step_id: c.parent_action_step_id, level: c.level } : {}),
|
|
1197
|
+
action_steps: c.action_steps.map(s => {
|
|
1198
|
+
const delay = isOpen(s.delayed_resolution) ? s.delayed_resolution : undefined;
|
|
1199
|
+
return {
|
|
1200
|
+
id: s.id,
|
|
1201
|
+
title: s.title,
|
|
1202
|
+
status: s.status,
|
|
1203
|
+
due_date: s.due_date,
|
|
1204
|
+
...(delay ? { resolve_by: delay.resolve_by, owed: isOwed(delay, now) } : {}),
|
|
1205
|
+
};
|
|
1206
|
+
}),
|
|
1207
|
+
})),
|
|
1208
|
+
// An empty chart store and "there is no structural tension work" are
|
|
1209
|
+
// different facts, and this tool used to make them look identical:
|
|
1210
|
+
// `{count: 0}` plus a teaching about the strategic landscape reads as
|
|
1211
|
+
// an answer. Meanwhile `list_relational_nodes` with kind "stc_chart"
|
|
1212
|
+
// returns rows — a DIFFERENT record, a relational node carrying that
|
|
1213
|
+
// metadata kind rather than a chart in the chart store. Saying where
|
|
1214
|
+
// this tool looked lets a caller tell the two apart instead of
|
|
1215
|
+
// concluding nothing exists.
|
|
1216
|
+
...(charts.length === 0
|
|
1217
|
+
? {
|
|
1218
|
+
searched: "the chart store (/api/charts)",
|
|
1219
|
+
note: "No charts in the chart store. Nodes carrying metadata.kind 'stc_chart' are a separate record — list_relational_nodes with kind 'stc_chart' searches those.",
|
|
1220
|
+
}
|
|
1221
|
+
: {}),
|
|
1222
|
+
teaching: "Each chart holds structural tension that seeks resolution. Together they form the strategic landscape.",
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
catch (error) {
|
|
1226
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1227
|
+
return { status: "error", message: `Failed to list charts: ${errorMsg}` };
|
|
1228
|
+
}
|
|
1229
|
+
},
|
|
657
1230
|
},
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
1231
|
+
{
|
|
1232
|
+
name: "creator_moment_of_truth",
|
|
1233
|
+
description: "Four-step review process (MMOT) for a structural tension chart. Acknowledges truth, analyzes how things happened, creates plan for adjustment, and sets up feedback. Aligns with West direction (reflection/truth).",
|
|
1234
|
+
inputSchema: {
|
|
1235
|
+
type: "object",
|
|
1236
|
+
properties: {
|
|
1237
|
+
chart_id: {
|
|
1238
|
+
type: "string",
|
|
1239
|
+
description: "ID of the chart to review",
|
|
1240
|
+
},
|
|
1241
|
+
expected_outcome: {
|
|
1242
|
+
type: "string",
|
|
1243
|
+
description: "What was expected to happen",
|
|
1244
|
+
},
|
|
1245
|
+
actual_outcome: {
|
|
1246
|
+
type: "string",
|
|
1247
|
+
description: "What actually happened (factual)",
|
|
1248
|
+
},
|
|
1249
|
+
analysis: {
|
|
1250
|
+
type: "string",
|
|
1251
|
+
description: "How did this come to pass? Step-by-step tracking, not blame.",
|
|
1252
|
+
},
|
|
1253
|
+
adjustments: {
|
|
1254
|
+
type: "array",
|
|
1255
|
+
items: { type: "string" },
|
|
1256
|
+
description: "What specific changes will be made based on learnings",
|
|
1257
|
+
},
|
|
1258
|
+
feedback_system: {
|
|
1259
|
+
type: "string",
|
|
1260
|
+
description: "How will you track whether changes are being made",
|
|
1261
|
+
},
|
|
668
1262
|
},
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
1263
|
+
required: ["chart_id", "expected_outcome", "actual_outcome", "analysis", "adjustments"],
|
|
1264
|
+
},
|
|
1265
|
+
handler: async (args) => {
|
|
1266
|
+
try {
|
|
1267
|
+
const chart = await loadChart(args.chart_id);
|
|
1268
|
+
if (!chart) {
|
|
1269
|
+
return { status: "not_found", message: `Chart ${args.chart_id} not found` };
|
|
1270
|
+
}
|
|
1271
|
+
const mmotId = `mmot:${args.chart_id}:${Date.now()}`;
|
|
1272
|
+
await store.saveMmot({
|
|
1273
|
+
id: mmotId,
|
|
1274
|
+
chart_id: args.chart_id,
|
|
1275
|
+
timestamp: new Date().toISOString(),
|
|
1276
|
+
step1_expected: args.expected_outcome,
|
|
1277
|
+
step1_actual: args.actual_outcome,
|
|
1278
|
+
step2_analysis: args.analysis,
|
|
1279
|
+
step3_adjustments: args.adjustments,
|
|
1280
|
+
step4_feedback: args.feedback_system || '',
|
|
1281
|
+
});
|
|
1282
|
+
// Update current reality based on MMOT
|
|
1283
|
+
chart.current_reality = args.actual_outcome;
|
|
1284
|
+
chart.updated_at = new Date().toISOString();
|
|
1285
|
+
await store.saveChart(chart);
|
|
1286
|
+
return {
|
|
1287
|
+
mmot_id: mmotId,
|
|
1288
|
+
chart_id: args.chart_id,
|
|
1289
|
+
steps: {
|
|
1290
|
+
step1_acknowledge: {
|
|
1291
|
+
expected: args.expected_outcome,
|
|
1292
|
+
actual: args.actual_outcome,
|
|
1293
|
+
discrepancy: args.expected_outcome !== args.actual_outcome,
|
|
1294
|
+
},
|
|
1295
|
+
step2_analyze: args.analysis,
|
|
1296
|
+
step3_plan: args.adjustments,
|
|
1297
|
+
step4_feedback: args.feedback_system || 'Not specified',
|
|
1298
|
+
},
|
|
1299
|
+
current_reality_updated: true,
|
|
1300
|
+
teaching: "The goal is effectiveness, not perfection. Use discrepancies to learn, not to judge. Truth as a verb.",
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
catch (error) {
|
|
1304
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1305
|
+
return { status: "error", message: `Failed to complete MMOT: ${errorMsg}` };
|
|
1306
|
+
}
|
|
1307
|
+
},
|
|
1308
|
+
},
|
|
1309
|
+
{
|
|
1310
|
+
name: "link_ceremony_to_chart",
|
|
1311
|
+
description: "Link a ceremony to a structural tension chart. Ceremonies witness and honor the work of advancing toward desired outcomes.",
|
|
1312
|
+
inputSchema: {
|
|
1313
|
+
type: "object",
|
|
1314
|
+
properties: {
|
|
1315
|
+
chart_id: {
|
|
1316
|
+
type: "string",
|
|
1317
|
+
description: "ID of the chart",
|
|
1318
|
+
},
|
|
1319
|
+
ceremony_id: {
|
|
1320
|
+
type: "string",
|
|
1321
|
+
description: "ID of the ceremony to link",
|
|
1322
|
+
},
|
|
672
1323
|
},
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
1324
|
+
required: ["chart_id", "ceremony_id"],
|
|
1325
|
+
},
|
|
1326
|
+
handler: async (args) => {
|
|
1327
|
+
try {
|
|
1328
|
+
const chart = await loadChart(args.chart_id);
|
|
1329
|
+
if (!chart) {
|
|
1330
|
+
return { status: "not_found", message: `Chart ${args.chart_id} not found` };
|
|
1331
|
+
}
|
|
1332
|
+
if (!chart.ceremonies_linked.includes(args.ceremony_id)) {
|
|
1333
|
+
chart.ceremonies_linked.push(args.ceremony_id);
|
|
1334
|
+
chart.updated_at = new Date().toISOString();
|
|
1335
|
+
await store.saveChart(chart);
|
|
1336
|
+
}
|
|
1337
|
+
return {
|
|
1338
|
+
chart_id: args.chart_id,
|
|
1339
|
+
ceremony_id: args.ceremony_id,
|
|
1340
|
+
total_ceremonies: chart.ceremonies_linked.length,
|
|
1341
|
+
teaching: "Ceremony witnesses the work. Strategic advancement is relational practice.",
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
catch (error) {
|
|
1345
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1346
|
+
return { status: "error", message: `Failed to link ceremony: ${errorMsg}` };
|
|
1347
|
+
}
|
|
1348
|
+
},
|
|
1349
|
+
},
|
|
1350
|
+
{
|
|
1351
|
+
name: "link_chart_to_github_issue",
|
|
1352
|
+
description: "Link a structural tension chart to a GitHub issue, given as the full 'owner/repo#number' (a bare '#number' is refused). The chart is read back after saving to confirm the link was kept.",
|
|
1353
|
+
inputSchema: {
|
|
1354
|
+
type: "object",
|
|
1355
|
+
properties: {
|
|
1356
|
+
chart_id: { type: "string", description: "ID of the chart" },
|
|
1357
|
+
github_issue: { type: "string", description: "The issue as owner/repo#number, e.g. jgwill/medicine-wheel#170" },
|
|
676
1358
|
},
|
|
1359
|
+
required: ["chart_id", "github_issue"],
|
|
1360
|
+
},
|
|
1361
|
+
handler: async (args) => {
|
|
1362
|
+
try {
|
|
1363
|
+
const issue = parseGithubIssue(args.github_issue);
|
|
1364
|
+
if (!issue) {
|
|
1365
|
+
return refuse("github_issue_invalid", `Invalid GitHub issue reference: ${JSON.stringify(args.github_issue)}. Use the full path 'owner/repo#number'; a bare '#number' resolves against the wrong repository as soon as the chart is read somewhere else.`);
|
|
1366
|
+
}
|
|
1367
|
+
const chart = await loadChart(args.chart_id);
|
|
1368
|
+
if (!chart) {
|
|
1369
|
+
return { status: "not_found", message: `Chart ${args.chart_id} not found` };
|
|
1370
|
+
}
|
|
1371
|
+
const previous = chart.github_issue ?? null;
|
|
1372
|
+
chart.github_issue = issue;
|
|
1373
|
+
chart.updated_at = new Date().toISOString();
|
|
1374
|
+
await store.saveChart(chart);
|
|
1375
|
+
// A wheel whose /api/charts predates 0.23.0 drops chart fields outside
|
|
1376
|
+
// its fixed list. Say so instead of reporting a link that was not kept.
|
|
1377
|
+
const reread = await loadChart(chart.id);
|
|
1378
|
+
if (reread?.github_issue?.ref !== issue.ref) {
|
|
1379
|
+
return {
|
|
1380
|
+
status: "not_persisted",
|
|
1381
|
+
chart_id: chart.id,
|
|
1382
|
+
github_issue: issue,
|
|
1383
|
+
message: "The chart was saved but the store did not keep github_issue. A wheel app older than 0.23.0 drops chart fields outside its fixed list; upgrade the app serving /api/charts.",
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
return {
|
|
1387
|
+
chart_id: chart.id,
|
|
1388
|
+
github_issue: issue,
|
|
1389
|
+
previous_github_issue: previous,
|
|
1390
|
+
teaching: "A chart linked to its issue can be found from either side; the full owner/repo#number keeps that link true wherever it is read.",
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
catch (error) {
|
|
1394
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1395
|
+
return { status: "error", message: `Failed to link chart to GitHub issue: ${errorMsg}` };
|
|
1396
|
+
}
|
|
677
1397
|
},
|
|
678
|
-
required: ["chart_id", "action_step_id", "node_id"],
|
|
679
1398
|
},
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
1399
|
+
{
|
|
1400
|
+
name: "link_chart_to_cycle",
|
|
1401
|
+
description: "Link a structural tension chart to a medicine wheel research cycle. Charts can belong to cycles for organizational grouping.",
|
|
1402
|
+
inputSchema: {
|
|
1403
|
+
type: "object",
|
|
1404
|
+
properties: {
|
|
1405
|
+
chart_id: {
|
|
1406
|
+
type: "string",
|
|
1407
|
+
description: "ID of the chart",
|
|
1408
|
+
},
|
|
1409
|
+
cycle_id: {
|
|
1410
|
+
type: "string",
|
|
1411
|
+
description: "ID of the medicine wheel cycle to link to",
|
|
1412
|
+
},
|
|
1413
|
+
},
|
|
1414
|
+
required: ["chart_id", "cycle_id"],
|
|
1415
|
+
},
|
|
1416
|
+
handler: async (args) => {
|
|
1417
|
+
try {
|
|
1418
|
+
const chart = await loadChart(args.chart_id);
|
|
1419
|
+
if (!chart) {
|
|
1420
|
+
return { status: "not_found", message: `Chart ${args.chart_id} not found` };
|
|
1421
|
+
}
|
|
1422
|
+
chart.cycle_id = args.cycle_id;
|
|
692
1423
|
chart.updated_at = new Date().toISOString();
|
|
693
|
-
store.saveChart(chart);
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
1424
|
+
await store.saveChart(chart);
|
|
1425
|
+
return {
|
|
1426
|
+
chart_id: args.chart_id,
|
|
1427
|
+
cycle_id: args.cycle_id,
|
|
1428
|
+
teaching: "Charts within cycles form the strategic landscape of a research journey.",
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
catch (error) {
|
|
1432
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1433
|
+
return { status: "error", message: `Failed to link chart to cycle: ${errorMsg}` };
|
|
1434
|
+
}
|
|
1435
|
+
},
|
|
1436
|
+
},
|
|
1437
|
+
{
|
|
1438
|
+
name: "honor_relation_in_action_step",
|
|
1439
|
+
description: "Honor a relational node in an action step. Links the strategic work to relational accountability.",
|
|
1440
|
+
inputSchema: {
|
|
1441
|
+
type: "object",
|
|
1442
|
+
properties: {
|
|
1443
|
+
chart_id: {
|
|
1444
|
+
type: "string",
|
|
1445
|
+
description: "ID of the chart",
|
|
1446
|
+
},
|
|
1447
|
+
action_step_id: {
|
|
1448
|
+
type: "string",
|
|
1449
|
+
description: "ID of the action step",
|
|
1450
|
+
},
|
|
1451
|
+
node_id: {
|
|
1452
|
+
type: "string",
|
|
1453
|
+
description: "ID of the relational node to honor",
|
|
1454
|
+
},
|
|
1455
|
+
},
|
|
1456
|
+
required: ["chart_id", "action_step_id", "node_id"],
|
|
1457
|
+
},
|
|
1458
|
+
handler: async (args) => {
|
|
1459
|
+
try {
|
|
1460
|
+
const chart = await loadChart(args.chart_id);
|
|
1461
|
+
if (!chart) {
|
|
1462
|
+
return { status: "not_found", message: `Chart ${args.chart_id} not found` };
|
|
1463
|
+
}
|
|
1464
|
+
const actionStep = findStep(chart, args.action_step_id);
|
|
1465
|
+
if (!actionStep) {
|
|
1466
|
+
return { status: "not_found", message: `Action step ${args.action_step_id} not found` };
|
|
1467
|
+
}
|
|
1468
|
+
if (!actionStep.relations_honored.includes(args.node_id)) {
|
|
1469
|
+
actionStep.relations_honored.push(args.node_id);
|
|
1470
|
+
chart.updated_at = new Date().toISOString();
|
|
1471
|
+
await store.saveChart(chart);
|
|
1472
|
+
}
|
|
1473
|
+
return {
|
|
1474
|
+
action_step_id: args.action_step_id,
|
|
1475
|
+
node_id: args.node_id,
|
|
1476
|
+
total_relations_honored: actionStep.relations_honored.length,
|
|
1477
|
+
teaching: "Strategic action is relational. Honoring relations grounds advancement in accountability.",
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
catch (error) {
|
|
1481
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1482
|
+
return { status: "error", message: `Failed to honor relation: ${errorMsg}` };
|
|
1483
|
+
}
|
|
1484
|
+
},
|
|
706
1485
|
},
|
|
707
|
-
|
|
708
|
-
|
|
1486
|
+
];
|
|
1487
|
+
}
|
|
1488
|
+
export const structuralTensionTools = createStructuralTensionTools(store);
|
|
709
1489
|
//# sourceMappingURL=structural-tension.js.map
|