@lumenflow/mcp 3.2.0 → 3.2.1

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 (95) hide show
  1. package/dist/bin.d.ts +16 -0
  2. package/dist/bin.d.ts.map +1 -0
  3. package/dist/bin.js.map +1 -0
  4. package/dist/cli-runner.d.ts +58 -0
  5. package/dist/cli-runner.d.ts.map +1 -0
  6. package/dist/cli-runner.js +164 -0
  7. package/dist/cli-runner.js.map +1 -0
  8. package/dist/index.d.ts +37 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/mcp-constants.d.ts +177 -0
  12. package/dist/mcp-constants.d.ts.map +1 -0
  13. package/dist/mcp-constants.js +197 -0
  14. package/dist/mcp-constants.js.map +1 -0
  15. package/dist/resources.d.ts +53 -0
  16. package/dist/resources.d.ts.map +1 -0
  17. package/dist/resources.js +131 -0
  18. package/dist/resources.js.map +1 -0
  19. package/dist/runtime-cache.d.ts +7 -0
  20. package/dist/runtime-cache.d.ts.map +1 -0
  21. package/dist/runtime-cache.js +28 -0
  22. package/dist/runtime-cache.js.map +1 -0
  23. package/dist/runtime-tool-resolver.constants.d.ts +26 -0
  24. package/dist/runtime-tool-resolver.constants.d.ts.map +1 -0
  25. package/dist/runtime-tool-resolver.constants.js +36 -0
  26. package/dist/runtime-tool-resolver.constants.js.map +1 -0
  27. package/dist/runtime-tool-resolver.d.ts +5 -0
  28. package/dist/runtime-tool-resolver.d.ts.map +1 -0
  29. package/dist/runtime-tool-resolver.js +2030 -0
  30. package/dist/runtime-tool-resolver.js.map +1 -0
  31. package/dist/server.d.ts +58 -0
  32. package/dist/server.d.ts.map +1 -0
  33. package/dist/server.js +212 -0
  34. package/dist/server.js.map +1 -0
  35. package/dist/tools/agent-tools.d.ts +18 -0
  36. package/dist/tools/agent-tools.d.ts.map +1 -0
  37. package/dist/tools/agent-tools.js +235 -0
  38. package/dist/tools/agent-tools.js.map +1 -0
  39. package/dist/tools/context-tools.d.ts +13 -0
  40. package/dist/tools/context-tools.d.ts.map +1 -0
  41. package/dist/tools/context-tools.js +58 -0
  42. package/dist/tools/context-tools.js.map +1 -0
  43. package/dist/tools/flow-tools.d.ts +22 -0
  44. package/dist/tools/flow-tools.d.ts.map +1 -0
  45. package/dist/tools/flow-tools.js +130 -0
  46. package/dist/tools/flow-tools.js.map +1 -0
  47. package/dist/tools/initiative-tools.d.ts +34 -0
  48. package/dist/tools/initiative-tools.d.ts.map +1 -0
  49. package/dist/tools/initiative-tools.js +420 -0
  50. package/dist/tools/initiative-tools.js.map +1 -0
  51. package/dist/tools/memory-tools.d.ts +58 -0
  52. package/dist/tools/memory-tools.d.ts.map +1 -0
  53. package/dist/tools/memory-tools.js +523 -0
  54. package/dist/tools/memory-tools.js.map +1 -0
  55. package/dist/tools/orchestration-tools.d.ts +18 -0
  56. package/dist/tools/orchestration-tools.d.ts.map +1 -0
  57. package/dist/tools/orchestration-tools.js +202 -0
  58. package/dist/tools/orchestration-tools.js.map +1 -0
  59. package/dist/tools/parity-tools.d.ts +138 -0
  60. package/dist/tools/parity-tools.d.ts.map +1 -0
  61. package/dist/tools/parity-tools.js +1690 -0
  62. package/dist/tools/parity-tools.js.map +1 -0
  63. package/dist/tools/runtime-task-constants.d.ts +19 -0
  64. package/dist/tools/runtime-task-constants.d.ts.map +1 -0
  65. package/dist/tools/runtime-task-constants.js +21 -0
  66. package/dist/tools/runtime-task-constants.js.map +1 -0
  67. package/dist/tools/runtime-task-tools.d.ts +10 -0
  68. package/dist/tools/runtime-task-tools.d.ts.map +1 -0
  69. package/dist/tools/runtime-task-tools.js +116 -0
  70. package/dist/tools/runtime-task-tools.js.map +1 -0
  71. package/dist/tools/setup-tools.d.ts +34 -0
  72. package/dist/tools/setup-tools.d.ts.map +1 -0
  73. package/dist/tools/setup-tools.js +254 -0
  74. package/dist/tools/setup-tools.js.map +1 -0
  75. package/dist/tools/validation-tools.d.ts +26 -0
  76. package/dist/tools/validation-tools.d.ts.map +1 -0
  77. package/dist/tools/validation-tools.js +180 -0
  78. package/dist/tools/validation-tools.js.map +1 -0
  79. package/dist/tools/wu-tools.d.ts +101 -0
  80. package/dist/tools/wu-tools.d.ts.map +1 -0
  81. package/dist/tools/wu-tools.js +964 -0
  82. package/dist/tools/wu-tools.js.map +1 -0
  83. package/dist/tools-shared.d.ts +257 -0
  84. package/dist/tools-shared.d.ts.map +1 -0
  85. package/dist/tools-shared.js +410 -0
  86. package/dist/tools-shared.js.map +1 -0
  87. package/dist/tools.d.ts +99 -0
  88. package/dist/tools.d.ts.map +1 -0
  89. package/dist/tools.js +253 -0
  90. package/dist/tools.js.map +1 -0
  91. package/dist/worktree-enforcement.d.ts +32 -0
  92. package/dist/worktree-enforcement.d.ts.map +1 -0
  93. package/dist/worktree-enforcement.js +154 -0
  94. package/dist/worktree-enforcement.js.map +1 -0
  95. package/package.json +5 -5
@@ -0,0 +1,2030 @@
1
+ // Copyright (c) 2026 Hellmai Ltd
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+ import { mkdir, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { TOOL_HANDLER_KINDS, defaultRuntimeToolCapabilityResolver, } from '@lumenflow/kernel';
6
+ import { z } from 'zod';
7
+ import { STATE_RUNTIME_CONSTANTS, STATE_RUNTIME_EVENT_TYPES, STATE_RUNTIME_MESSAGES, } from './runtime-tool-resolver.constants.js';
8
+ import { MetadataKeys } from './mcp-constants.js';
9
+ const DEFAULT_IN_PROCESS_INPUT_SCHEMA = z.record(z.string(), z.unknown());
10
+ const DEFAULT_IN_PROCESS_OUTPUT_SCHEMA = z.record(z.string(), z.unknown());
11
+ const RUNTIME_PROJECT_ROOT_METADATA_KEY = MetadataKeys.PROJECT_ROOT;
12
+ const UTF8_ENCODING = 'utf-8';
13
+ const DEFAULT_FILE_READ_MAX_SIZE_BYTES = 10 * 1024 * 1024;
14
+ const ORCHESTRATION_TOOL_ERROR_CODES = {
15
+ INVALID_INPUT: 'INVALID_INPUT',
16
+ ORCHESTRATE_INIT_STATUS_ERROR: 'ORCHESTRATE_INIT_STATUS_ERROR',
17
+ ORCHESTRATE_MONITOR_ERROR: 'ORCHESTRATE_MONITOR_ERROR',
18
+ DELEGATION_LIST_ERROR: 'DELEGATION_LIST_ERROR',
19
+ };
20
+ const ORCHESTRATE_MONITOR_DEFAULT_SINCE = '30m';
21
+ const ORCHESTRATE_MONITOR_TIME_PATTERN = /^(\d+)\s*([smhd])$/i;
22
+ const ORCHESTRATE_MONITOR_TIME_MULTIPLIERS = {
23
+ s: 1000,
24
+ m: 60_000,
25
+ h: 3_600_000,
26
+ d: 86_400_000,
27
+ };
28
+ const INITIATIVE_FILE_SUFFIX = '.yaml';
29
+ const STATUS_DONE = 'done';
30
+ const STATUS_IN_PROGRESS = 'in_progress';
31
+ const STATUS_BLOCKED = 'blocked';
32
+ const STATUS_READY = 'ready';
33
+ const STATUS_UNKNOWN = 'unknown';
34
+ const LOCK_POLICY_ALL = 'all';
35
+ const LOCK_POLICY_ACTIVE = 'active';
36
+ const LOCK_POLICY_NONE = 'none';
37
+ const DEFAULT_WIP_LIMIT = 1;
38
+ const DELEGATION_LIST_LOG_PREFIX = '[delegation:list]';
39
+ const INIT_STATUS_HEADER = 'Initiative:';
40
+ const INIT_STATUS_PROGRESS_HEADER = 'Progress:';
41
+ const INIT_STATUS_WUS_HEADER = 'WUs:';
42
+ const INIT_STATUS_LANE_HEADER = 'Lane Availability:';
43
+ const LANE_SECTION_KEYS = ['definitions', 'engineering', 'business'];
44
+ const DEFAULT_SIGNAL_TYPE = 'unknown';
45
+ const WU_ID_PATTERN = /^WU-\d+$/;
46
+ const FILE_TOOL_ERROR_CODES = {
47
+ INVALID_INPUT: 'INVALID_INPUT',
48
+ FILE_READ_FAILED: 'FILE_READ_FAILED',
49
+ FILE_READ_TOO_LARGE: 'FILE_READ_TOO_LARGE',
50
+ FILE_WRITE_FAILED: 'FILE_WRITE_FAILED',
51
+ FILE_EDIT_FAILED: 'FILE_EDIT_FAILED',
52
+ FILE_EDIT_TARGET_NOT_FOUND: 'FILE_EDIT_TARGET_NOT_FOUND',
53
+ FILE_EDIT_NOT_UNIQUE: 'FILE_EDIT_NOT_UNIQUE',
54
+ FILE_DELETE_FAILED: 'FILE_DELETE_FAILED',
55
+ };
56
+ const FILE_TOOL_MESSAGES = {
57
+ FILE_WRITTEN: 'File written',
58
+ FILE_EDITED: 'File edited',
59
+ DELETE_COMPLETE: 'Delete complete',
60
+ PATH_NOT_FOUND: 'Path not found',
61
+ PARENT_DIRECTORY_MISSING: 'Parent directory does not exist',
62
+ DIRECTORY_NOT_EMPTY: 'Directory is not empty. Use recursive=true to delete non-empty directories.',
63
+ };
64
+ const STATE_TOOL_ERROR_CODES = {
65
+ INVALID_INPUT: 'INVALID_INPUT',
66
+ BACKLOG_PRUNE_FAILED: 'BACKLOG_PRUNE_FAILED',
67
+ STATE_BOOTSTRAP_FAILED: 'STATE_BOOTSTRAP_FAILED',
68
+ STATE_CLEANUP_FAILED: 'STATE_CLEANUP_FAILED',
69
+ STATE_DOCTOR_FAILED: 'STATE_DOCTOR_FAILED',
70
+ SIGNAL_CLEANUP_FAILED: 'SIGNAL_CLEANUP_FAILED',
71
+ };
72
+ const FILE_READ_INPUT_SCHEMA = z.object({
73
+ path: z.string().min(1),
74
+ encoding: z.string().optional(),
75
+ start_line: z.number().int().positive().optional(),
76
+ end_line: z.number().int().positive().optional(),
77
+ max_size: z.number().int().positive().optional(),
78
+ });
79
+ const FILE_WRITE_INPUT_SCHEMA = z.object({
80
+ path: z.string().min(1),
81
+ content: z.string(),
82
+ encoding: z.string().optional(),
83
+ no_create_dirs: z.boolean().optional(),
84
+ });
85
+ const FILE_EDIT_INPUT_SCHEMA = z.object({
86
+ path: z.string().min(1),
87
+ old_string: z.string(),
88
+ new_string: z.string(),
89
+ encoding: z.string().optional(),
90
+ replace_all: z.boolean().optional(),
91
+ });
92
+ const FILE_DELETE_INPUT_SCHEMA = z.object({
93
+ path: z.string().min(1),
94
+ recursive: z.boolean().optional(),
95
+ force: z.boolean().optional(),
96
+ });
97
+ const BACKLOG_PRUNE_INPUT_SCHEMA = z.object({
98
+ execute: z.boolean().optional(),
99
+ dry_run: z.boolean().optional(),
100
+ stale_days_in_progress: z.number().int().positive().optional(),
101
+ stale_days_ready: z.number().int().positive().optional(),
102
+ archive_days: z.number().int().positive().optional(),
103
+ });
104
+ const STATE_BOOTSTRAP_INPUT_SCHEMA = z.object({
105
+ execute: z.boolean().optional(),
106
+ dry_run: z.boolean().optional(),
107
+ force: z.boolean().optional(),
108
+ wu_dir: z.string().optional(),
109
+ state_dir: z.string().optional(),
110
+ });
111
+ const STATE_CLEANUP_INPUT_SCHEMA = z.object({
112
+ dry_run: z.boolean().optional(),
113
+ signals_only: z.boolean().optional(),
114
+ memory_only: z.boolean().optional(),
115
+ events_only: z.boolean().optional(),
116
+ json: z.boolean().optional(),
117
+ quiet: z.boolean().optional(),
118
+ base_dir: z.string().optional(),
119
+ });
120
+ const STATE_DOCTOR_INPUT_SCHEMA = z.object({
121
+ fix: z.boolean().optional(),
122
+ dry_run: z.boolean().optional(),
123
+ json: z.boolean().optional(),
124
+ quiet: z.boolean().optional(),
125
+ base_dir: z.string().optional(),
126
+ });
127
+ const SIGNAL_CLEANUP_INPUT_SCHEMA = z.object({
128
+ dry_run: z.boolean().optional(),
129
+ ttl: z.string().optional(),
130
+ unread_ttl: z.string().optional(),
131
+ max_entries: z.number().int().positive().optional(),
132
+ json: z.boolean().optional(),
133
+ quiet: z.boolean().optional(),
134
+ base_dir: z.string().optional(),
135
+ });
136
+ const ORCHESTRATE_INIT_STATUS_INPUT_SCHEMA = z.object({
137
+ initiative: z.string().min(1),
138
+ });
139
+ const ORCHESTRATE_MONITOR_INPUT_SCHEMA = z.object({
140
+ threshold: z.number().positive().optional(),
141
+ recover: z.boolean().optional(),
142
+ dry_run: z.boolean().optional(),
143
+ since: z.string().optional(),
144
+ wu: z.string().optional(),
145
+ signals_only: z.boolean().optional(),
146
+ });
147
+ const DELEGATION_LIST_INPUT_SCHEMA = z.object({
148
+ wu: z.string().optional(),
149
+ initiative: z.string().optional(),
150
+ json: z.boolean().optional(),
151
+ });
152
+ const FILE_READ_OUTPUT_SCHEMA = z.object({
153
+ content: z.string(),
154
+ metadata: z.record(z.string(), z.unknown()).optional(),
155
+ });
156
+ const FILE_WRITE_OUTPUT_SCHEMA = z.object({
157
+ message: z.string(),
158
+ path: z.string(),
159
+ bytes_written: z.number().int().nonnegative(),
160
+ });
161
+ const FILE_EDIT_OUTPUT_SCHEMA = z.object({
162
+ message: z.string(),
163
+ path: z.string(),
164
+ replacements: z.number().int().positive(),
165
+ });
166
+ const FILE_DELETE_OUTPUT_SCHEMA = z.object({
167
+ message: z.string(),
168
+ metadata: z
169
+ .object({
170
+ deleted_count: z.number().int().nonnegative(),
171
+ was_directory: z.boolean(),
172
+ })
173
+ .optional(),
174
+ });
175
+ // WU-1803: Lazy module loaders to avoid eager imports (same pattern as getCore in tools-shared)
176
+ let coreModule = null;
177
+ async function getCoreLazy() {
178
+ if (!coreModule)
179
+ coreModule = await import('@lumenflow/core');
180
+ return coreModule;
181
+ }
182
+ const MEMORY_MODULE_ID = '@lumenflow/memory';
183
+ let memoryModule = null;
184
+ async function getMemoryLazy() {
185
+ if (!memoryModule) {
186
+ memoryModule = (await import(MEMORY_MODULE_ID));
187
+ }
188
+ return memoryModule;
189
+ }
190
+ // --- WU-1803: Context in-process handler implementations ---
191
+ // WU-1905: flow:bottlenecks, flow:report, metrics, and metrics:snapshot handlers
192
+ // have been migrated to pack handler implementations in
193
+ // packages/@lumenflow/packs/software-delivery/tool-impl/flow-metrics-tools.ts
194
+ /**
195
+ * context:get handler — delegates to @lumenflow/core computeWuContext
196
+ */
197
+ const contextGetHandler = async () => {
198
+ try {
199
+ const core = await getCoreLazy();
200
+ const context = await core.computeWuContext({ cwd: process.cwd() });
201
+ return { success: true, data: context };
202
+ }
203
+ catch (err) {
204
+ return {
205
+ success: false,
206
+ error: { code: 'CONTEXT_ERROR', message: err.message },
207
+ };
208
+ }
209
+ };
210
+ /**
211
+ * wu:list handler — delegates to @lumenflow/core listWUs
212
+ */
213
+ const wuListHandler = async (rawInput) => {
214
+ try {
215
+ const input = (rawInput ?? {});
216
+ const core = await getCoreLazy();
217
+ const options = { projectRoot: process.cwd() };
218
+ if (typeof input.status === 'string')
219
+ options.status = input.status;
220
+ if (typeof input.lane === 'string')
221
+ options.lane = input.lane;
222
+ const wus = await core.listWUs(options);
223
+ return { success: true, data: wus };
224
+ }
225
+ catch (err) {
226
+ return {
227
+ success: false,
228
+ error: { code: 'WU_LIST_ERROR', message: err.message },
229
+ };
230
+ }
231
+ };
232
+ function isRecord(value) {
233
+ return typeof value === 'object' && value !== null;
234
+ }
235
+ function normalizeToken(value) {
236
+ return typeof value === 'string' ? value.trim().toLowerCase() : '';
237
+ }
238
+ function normalizeLifecycleStatus(value) {
239
+ return typeof value === 'string' ? value.trim().toLowerCase() : '';
240
+ }
241
+ function hasIncompletePhase(phases) {
242
+ if (!Array.isArray(phases) || phases.length === 0) {
243
+ return false;
244
+ }
245
+ return phases.some((phase) => {
246
+ if (!isRecord(phase)) {
247
+ return true;
248
+ }
249
+ return normalizeLifecycleStatus(phase.status) !== STATUS_DONE;
250
+ });
251
+ }
252
+ function deriveInitiativeLifecycleStatus(status, phases) {
253
+ const normalizedStatus = normalizeLifecycleStatus(status);
254
+ if (normalizedStatus === STATUS_DONE && hasIncompletePhase(phases)) {
255
+ return STATUS_IN_PROGRESS;
256
+ }
257
+ return normalizedStatus || STATUS_IN_PROGRESS;
258
+ }
259
+ function extractInitiativeWuIds(wus) {
260
+ if (!Array.isArray(wus)) {
261
+ return [];
262
+ }
263
+ const wuIds = [];
264
+ for (const entry of wus) {
265
+ if (typeof entry === 'string' && entry.trim().length > 0) {
266
+ wuIds.push(entry);
267
+ continue;
268
+ }
269
+ if (isRecord(entry) && typeof entry.id === 'string' && entry.id.trim().length > 0) {
270
+ wuIds.push(entry.id);
271
+ }
272
+ }
273
+ return wuIds;
274
+ }
275
+ function countProgress(entries) {
276
+ const progress = {
277
+ total: entries.length,
278
+ done: 0,
279
+ active: 0,
280
+ pending: 0,
281
+ blocked: 0,
282
+ percentage: 0,
283
+ };
284
+ for (const wu of entries) {
285
+ if (wu.status === STATUS_DONE) {
286
+ progress.done += 1;
287
+ }
288
+ else if (wu.status === STATUS_IN_PROGRESS) {
289
+ progress.active += 1;
290
+ }
291
+ else if (wu.status === STATUS_BLOCKED) {
292
+ progress.blocked += 1;
293
+ }
294
+ else {
295
+ progress.pending += 1;
296
+ }
297
+ }
298
+ progress.percentage = progress.total > 0 ? Math.round((progress.done / progress.total) * 100) : 0;
299
+ return progress;
300
+ }
301
+ function collectLaneDefinitions(value, target) {
302
+ if (!Array.isArray(value)) {
303
+ return;
304
+ }
305
+ for (const entry of value) {
306
+ if (isRecord(entry)) {
307
+ target.push(entry);
308
+ }
309
+ }
310
+ }
311
+ function resolveLanePolicyConfig(config) {
312
+ const laneConfigMap = {};
313
+ if (!isRecord(config) || !isRecord(config.lanes)) {
314
+ return laneConfigMap;
315
+ }
316
+ const laneDefinitions = [];
317
+ if (Array.isArray(config.lanes)) {
318
+ collectLaneDefinitions(config.lanes, laneDefinitions);
319
+ }
320
+ else {
321
+ for (const key of LANE_SECTION_KEYS) {
322
+ collectLaneDefinitions(config.lanes[key], laneDefinitions);
323
+ }
324
+ }
325
+ for (const laneDefinition of laneDefinitions) {
326
+ if (typeof laneDefinition.name !== 'string' || laneDefinition.name.trim().length === 0) {
327
+ continue;
328
+ }
329
+ const lockPolicy = laneDefinition.lock_policy === LOCK_POLICY_ACTIVE ||
330
+ laneDefinition.lock_policy === LOCK_POLICY_NONE
331
+ ? laneDefinition.lock_policy
332
+ : LOCK_POLICY_ALL;
333
+ const wipLimit = typeof laneDefinition.wip_limit === 'number' ? laneDefinition.wip_limit : DEFAULT_WIP_LIMIT;
334
+ laneConfigMap[laneDefinition.name] = { lockPolicy, wipLimit };
335
+ }
336
+ return laneConfigMap;
337
+ }
338
+ function computeLaneAvailability(wus, laneConfigMap) {
339
+ const groupedByLane = new Map();
340
+ for (const wu of wus) {
341
+ if (!wu.lane) {
342
+ continue;
343
+ }
344
+ const laneEntries = groupedByLane.get(wu.lane);
345
+ if (laneEntries) {
346
+ laneEntries.push(wu);
347
+ }
348
+ else {
349
+ groupedByLane.set(wu.lane, [wu]);
350
+ }
351
+ }
352
+ const result = {};
353
+ for (const [lane, entries] of groupedByLane) {
354
+ const laneConfig = laneConfigMap[lane] ?? {
355
+ lockPolicy: LOCK_POLICY_ALL,
356
+ wipLimit: DEFAULT_WIP_LIMIT,
357
+ };
358
+ const inProgress = entries.filter((wu) => wu.status === STATUS_IN_PROGRESS);
359
+ const blocked = entries.filter((wu) => wu.status === STATUS_BLOCKED);
360
+ let available;
361
+ let occupiedBy = null;
362
+ if (laneConfig.lockPolicy === LOCK_POLICY_NONE) {
363
+ available = true;
364
+ }
365
+ else if (laneConfig.lockPolicy === LOCK_POLICY_ACTIVE) {
366
+ available = inProgress.length === 0;
367
+ occupiedBy = inProgress[0]?.id ?? null;
368
+ }
369
+ else {
370
+ available = inProgress.length === 0 && blocked.length === 0;
371
+ occupiedBy = inProgress[0]?.id ?? blocked[0]?.id ?? null;
372
+ }
373
+ result[lane] = {
374
+ available,
375
+ policy: laneConfig.lockPolicy,
376
+ occupied_by: occupiedBy,
377
+ in_progress: inProgress.length,
378
+ blocked: blocked.length,
379
+ wip_limit: laneConfig.wipLimit,
380
+ };
381
+ }
382
+ return result;
383
+ }
384
+ function formatInitiativeStatusMessage(input) {
385
+ const lines = [];
386
+ lines.push(`${INIT_STATUS_HEADER} ${input.initiativeId} - ${input.initiativeTitle}`);
387
+ lines.push(`Lifecycle Status: ${input.lifecycleStatus}`);
388
+ if (input.rawStatus && input.rawStatus !== input.lifecycleStatus) {
389
+ lines.push(`Lifecycle mismatch: metadata status '${input.rawStatus}' conflicts with phase state; reporting '${input.lifecycleStatus}'.`);
390
+ }
391
+ lines.push('');
392
+ lines.push(INIT_STATUS_PROGRESS_HEADER);
393
+ lines.push(` Done: ${input.progress.done}/${input.progress.total} (${input.progress.percentage}%)`);
394
+ lines.push(` Active: ${input.progress.active}`);
395
+ lines.push(` Pending: ${input.progress.pending}`);
396
+ lines.push(` Blocked: ${input.progress.blocked}`);
397
+ lines.push('');
398
+ lines.push(INIT_STATUS_WUS_HEADER);
399
+ if (input.wus.length === 0) {
400
+ lines.push(' (no WUs found for initiative)');
401
+ }
402
+ else {
403
+ for (const wu of input.wus) {
404
+ lines.push(` ${wu.id}: ${wu.title} [${wu.status}]`);
405
+ }
406
+ }
407
+ lines.push('');
408
+ lines.push(INIT_STATUS_LANE_HEADER);
409
+ const lanes = Object.keys(input.laneAvailability).sort((left, right) => left.localeCompare(right));
410
+ if (lanes.length === 0) {
411
+ lines.push(' (no lanes found)');
412
+ }
413
+ else {
414
+ for (const lane of lanes) {
415
+ const availability = input.laneAvailability[lane];
416
+ if (!availability) {
417
+ continue;
418
+ }
419
+ const status = availability.available ? 'available' : 'occupied';
420
+ lines.push(` ${lane}: ${status} (wip_limit=${availability.wip_limit}, lock_policy=${availability.policy}, in_progress=${availability.in_progress}, blocked=${availability.blocked}, occupied_by=${availability.occupied_by ?? 'none'})`);
421
+ }
422
+ }
423
+ return lines.join('\n');
424
+ }
425
+ async function resolveInitiativeDoc(core, projectRoot, initiativeRef) {
426
+ const config = core.getConfig({ projectRoot });
427
+ const initiativesDir = path.join(projectRoot, config.directories.initiativesDir);
428
+ const initiativeFiles = await readdir(initiativesDir);
429
+ const normalizedRef = normalizeToken(initiativeRef);
430
+ for (const file of initiativeFiles) {
431
+ if (!file.endsWith(INITIATIVE_FILE_SUFFIX)) {
432
+ continue;
433
+ }
434
+ const content = await readFile(path.join(initiativesDir, file), UTF8_ENCODING);
435
+ const parsed = core.parseYAML(content);
436
+ if (!isRecord(parsed)) {
437
+ continue;
438
+ }
439
+ const id = normalizeToken(parsed.id);
440
+ const slug = normalizeToken(parsed.slug);
441
+ if (id === normalizedRef || slug === normalizedRef) {
442
+ return {
443
+ id: typeof parsed.id === 'string' ? parsed.id : undefined,
444
+ slug: typeof parsed.slug === 'string' ? parsed.slug : undefined,
445
+ title: typeof parsed.title === 'string' ? parsed.title : undefined,
446
+ status: parsed.status,
447
+ phases: parsed.phases,
448
+ wus: parsed.wus,
449
+ };
450
+ }
451
+ }
452
+ throw new Error(`Initiative '${initiativeRef}' not found`);
453
+ }
454
+ async function getCompletedWuIdsFromStamps(core, projectRoot) {
455
+ const completed = new Set();
456
+ const stampsPath = path.join(projectRoot, core.LUMENFLOW_PATHS.STAMPS_DIR);
457
+ try {
458
+ const files = await readdir(stampsPath);
459
+ for (const file of files) {
460
+ if (file.endsWith('.done')) {
461
+ completed.add(file.slice(0, -'.done'.length));
462
+ }
463
+ }
464
+ }
465
+ catch {
466
+ return completed;
467
+ }
468
+ return completed;
469
+ }
470
+ function parseSinceInputToDate(sinceInput) {
471
+ const relativeMatch = ORCHESTRATE_MONITOR_TIME_PATTERN.exec(sinceInput.trim());
472
+ if (relativeMatch) {
473
+ const amount = Number.parseInt(relativeMatch[1] ?? '0', 10);
474
+ const unit = (relativeMatch[2] ?? '').toLowerCase();
475
+ const multiplier = ORCHESTRATE_MONITOR_TIME_MULTIPLIERS[unit];
476
+ if (Number.isFinite(amount) && amount > 0 && multiplier) {
477
+ return new Date(Date.now() - amount * multiplier);
478
+ }
479
+ }
480
+ const absoluteDate = new Date(sinceInput);
481
+ if (Number.isNaN(absoluteDate.getTime())) {
482
+ throw new Error(`Invalid time format: ${sinceInput}`);
483
+ }
484
+ return absoluteDate;
485
+ }
486
+ async function loadRecentSignals(core, projectRoot, since) {
487
+ const signalRecords = [];
488
+ const memoryPath = path.join(projectRoot, core.LUMENFLOW_PATHS.MEMORY_DIR);
489
+ let files;
490
+ try {
491
+ files = await readdir(memoryPath);
492
+ }
493
+ catch {
494
+ return signalRecords;
495
+ }
496
+ const ndjsonFiles = files.filter((file) => file.endsWith('.ndjson'));
497
+ for (const file of ndjsonFiles) {
498
+ const content = await readFile(path.join(memoryPath, file), UTF8_ENCODING);
499
+ for (const line of content.split('\n')) {
500
+ const trimmedLine = line.trim();
501
+ if (trimmedLine.length === 0) {
502
+ continue;
503
+ }
504
+ try {
505
+ const parsed = JSON.parse(trimmedLine);
506
+ if (!isRecord(parsed) || typeof parsed.timestamp !== 'string') {
507
+ continue;
508
+ }
509
+ const timestamp = new Date(parsed.timestamp);
510
+ if (Number.isNaN(timestamp.getTime()) || timestamp < since) {
511
+ continue;
512
+ }
513
+ signalRecords.push({
514
+ timestamp: parsed.timestamp,
515
+ type: typeof parsed.type === 'string' ? parsed.type : DEFAULT_SIGNAL_TYPE,
516
+ wuId: typeof parsed.wuId === 'string' ? parsed.wuId : undefined,
517
+ message: typeof parsed.message === 'string' ? parsed.message : undefined,
518
+ });
519
+ }
520
+ catch {
521
+ continue;
522
+ }
523
+ }
524
+ }
525
+ signalRecords.sort((left, right) => new Date(left.timestamp).getTime() - new Date(right.timestamp).getTime());
526
+ return signalRecords;
527
+ }
528
+ const orchestrateInitStatusInProcess = async (rawInput, context) => {
529
+ const parsedInput = ORCHESTRATE_INIT_STATUS_INPUT_SCHEMA.safeParse(rawInput);
530
+ if (!parsedInput.success) {
531
+ return createFailureOutput(ORCHESTRATION_TOOL_ERROR_CODES.INVALID_INPUT, parsedInput.error.message);
532
+ }
533
+ try {
534
+ const core = await getCoreLazy();
535
+ const projectRoot = resolveWorkspaceRoot(context);
536
+ const initiativeDoc = await resolveInitiativeDoc(core, projectRoot, parsedInput.data.initiative);
537
+ const initiativeId = initiativeDoc.id ?? parsedInput.data.initiative;
538
+ const initiativeSlug = initiativeDoc.slug ?? '';
539
+ const allWUs = await core.listWUs({ projectRoot });
540
+ const completedWuIds = await getCompletedWuIdsFromStamps(core, projectRoot);
541
+ const declaredWuIds = extractInitiativeWuIds(initiativeDoc.wus);
542
+ const declaredWuIdSet = new Set(declaredWuIds);
543
+ const normalizedInitiativeRefs = new Set([
544
+ normalizeToken(parsedInput.data.initiative),
545
+ normalizeToken(initiativeId),
546
+ normalizeToken(initiativeSlug),
547
+ ]);
548
+ const wuById = new Map(allWUs.map((wu) => [wu.id, wu]));
549
+ const inferredWuIds = allWUs
550
+ .filter((wu) => normalizedInitiativeRefs.has(normalizeToken(wu.initiative)))
551
+ .map((wu) => wu.id);
552
+ const orderedWuIds = declaredWuIds.length > 0 ? declaredWuIds : inferredWuIds;
553
+ const dedupedWuIds = [...new Set(orderedWuIds)];
554
+ const statusEntries = dedupedWuIds.map((wuId) => {
555
+ const wu = wuById.get(wuId);
556
+ const fallbackStatus = declaredWuIdSet.has(wuId) ? STATUS_READY : STATUS_UNKNOWN;
557
+ return {
558
+ id: wuId,
559
+ title: wu?.title ?? wuId,
560
+ lane: wu?.lane ?? '',
561
+ status: completedWuIds.has(wuId) ? STATUS_DONE : (wu?.status ?? fallbackStatus),
562
+ };
563
+ });
564
+ const progress = countProgress(statusEntries);
565
+ const config = core.getConfig({ projectRoot });
566
+ const laneConfigMap = resolveLanePolicyConfig(config);
567
+ const laneAvailability = computeLaneAvailability(statusEntries, laneConfigMap);
568
+ const lifecycleStatus = deriveInitiativeLifecycleStatus(initiativeDoc.status, initiativeDoc.phases);
569
+ const rawStatus = normalizeLifecycleStatus(initiativeDoc.status);
570
+ const message = formatInitiativeStatusMessage({
571
+ initiativeId,
572
+ initiativeTitle: initiativeDoc.title ?? initiativeId,
573
+ lifecycleStatus,
574
+ rawStatus,
575
+ progress,
576
+ wus: statusEntries,
577
+ laneAvailability,
578
+ });
579
+ return createSuccessOutput({
580
+ message,
581
+ initiative: {
582
+ id: initiativeId,
583
+ slug: initiativeSlug || undefined,
584
+ title: initiativeDoc.title ?? initiativeId,
585
+ lifecycle_status: lifecycleStatus,
586
+ raw_status: rawStatus || undefined,
587
+ },
588
+ progress,
589
+ wus: statusEntries,
590
+ lane_availability: laneAvailability,
591
+ });
592
+ }
593
+ catch (cause) {
594
+ return createFailureOutput(ORCHESTRATION_TOOL_ERROR_CODES.ORCHESTRATE_INIT_STATUS_ERROR, cause.message);
595
+ }
596
+ };
597
+ const orchestrateMonitorInProcess = async (rawInput, context) => {
598
+ const parsedInput = ORCHESTRATE_MONITOR_INPUT_SCHEMA.safeParse(rawInput);
599
+ if (!parsedInput.success) {
600
+ return createFailureOutput(ORCHESTRATION_TOOL_ERROR_CODES.INVALID_INPUT, parsedInput.error.message);
601
+ }
602
+ try {
603
+ const core = await getCoreLazy();
604
+ const projectRoot = resolveWorkspaceRoot(context);
605
+ if (parsedInput.data.signals_only) {
606
+ const sinceInput = parsedInput.data.since && parsedInput.data.since.trim().length > 0
607
+ ? parsedInput.data.since
608
+ : ORCHESTRATE_MONITOR_DEFAULT_SINCE;
609
+ const sinceDate = parseSinceInputToDate(sinceInput);
610
+ const allSignals = await loadRecentSignals(core, projectRoot, sinceDate);
611
+ const filteredSignals = parsedInput.data.wu
612
+ ? allSignals.filter((signal) => signal.wuId === parsedInput.data.wu)
613
+ : allSignals;
614
+ const lines = [
615
+ `Signals since ${sinceDate.toISOString()}:`,
616
+ `Count: ${filteredSignals.length}`,
617
+ ];
618
+ if (filteredSignals.length > 0) {
619
+ for (const signal of filteredSignals) {
620
+ lines.push(`${signal.timestamp} [${signal.wuId ?? 'system'}] ${signal.type}: ${signal.message ?? ''}`);
621
+ }
622
+ }
623
+ else {
624
+ lines.push('No signals found.');
625
+ }
626
+ return createSuccessOutput({
627
+ message: lines.join('\n'),
628
+ since: sinceInput,
629
+ signals: filteredSignals,
630
+ total: filteredSignals.length,
631
+ });
632
+ }
633
+ const thresholdMinutes = parsedInput.data.threshold ?? core.DEFAULT_THRESHOLD_MINUTES;
634
+ const stateDir = path.join(projectRoot, core.LUMENFLOW_PATHS.STATE_DIR);
635
+ const registryStore = new core.DelegationRegistryStore(stateDir);
636
+ let delegations = [];
637
+ try {
638
+ await registryStore.load();
639
+ delegations = registryStore.getAllDelegations();
640
+ }
641
+ catch {
642
+ delegations = [];
643
+ }
644
+ const analysis = core.analyzeDelegations(delegations);
645
+ const stuckDelegations = core.detectStuckDelegations(delegations, thresholdMinutes);
646
+ const zombieLocks = await core.checkZombieLocks({ baseDir: projectRoot });
647
+ const suggestions = core.generateSuggestions(stuckDelegations, zombieLocks);
648
+ const monitorResult = {
649
+ analysis,
650
+ stuckDelegations,
651
+ zombieLocks,
652
+ suggestions,
653
+ dryRun: parsedInput.data.dry_run ?? false,
654
+ };
655
+ let recoveryResults;
656
+ if (parsedInput.data.recover) {
657
+ recoveryResults = await core.runRecovery(stuckDelegations, {
658
+ baseDir: projectRoot,
659
+ dryRun: parsedInput.data.dry_run ?? false,
660
+ });
661
+ }
662
+ let monitorOutput = core.formatMonitorOutput(monitorResult);
663
+ if (recoveryResults && recoveryResults.length > 0) {
664
+ monitorOutput = `${monitorOutput}\n\n${core.formatRecoveryResults(recoveryResults)}`;
665
+ }
666
+ if (stuckDelegations.length > 0 || zombieLocks.length > 0) {
667
+ return createFailureOutput(ORCHESTRATION_TOOL_ERROR_CODES.ORCHESTRATE_MONITOR_ERROR, monitorOutput);
668
+ }
669
+ return createSuccessOutput({
670
+ message: monitorOutput,
671
+ ...monitorResult,
672
+ recoveryResults,
673
+ });
674
+ }
675
+ catch (cause) {
676
+ return createFailureOutput(ORCHESTRATION_TOOL_ERROR_CODES.ORCHESTRATE_MONITOR_ERROR, cause.message);
677
+ }
678
+ };
679
+ const delegationListInProcess = async (rawInput, context) => {
680
+ const parsedInput = DELEGATION_LIST_INPUT_SCHEMA.safeParse(rawInput);
681
+ if (!parsedInput.success) {
682
+ return createFailureOutput(ORCHESTRATION_TOOL_ERROR_CODES.INVALID_INPUT, parsedInput.error.message);
683
+ }
684
+ if (!parsedInput.data.wu && !parsedInput.data.initiative) {
685
+ return createFailureOutput(ORCHESTRATION_TOOL_ERROR_CODES.INVALID_INPUT, 'Either wu or initiative is required');
686
+ }
687
+ try {
688
+ const core = await getCoreLazy();
689
+ const projectRoot = resolveWorkspaceRoot(context);
690
+ const config = core.getConfig({ projectRoot });
691
+ const registryDir = path.join(projectRoot, config.state.stateDir);
692
+ const wuDir = path.join(projectRoot, config.directories.wuDir);
693
+ if (parsedInput.data.wu) {
694
+ const wuId = parsedInput.data.wu.toUpperCase();
695
+ if (!WU_ID_PATTERN.test(wuId)) {
696
+ return createFailureOutput(ORCHESTRATION_TOOL_ERROR_CODES.INVALID_INPUT, `Invalid WU ID format: ${parsedInput.data.wu}. Expected format: WU-XXX`);
697
+ }
698
+ const delegations = await core.getDelegationsByWU(wuId, registryDir);
699
+ if (parsedInput.data.json) {
700
+ const tree = core.buildDelegationTree(delegations, wuId);
701
+ return createSuccessOutput(core.treeToJSON(tree));
702
+ }
703
+ if (delegations.length === 0) {
704
+ return createSuccessOutput({
705
+ message: `${DELEGATION_LIST_LOG_PREFIX} No delegations found for ${wuId}`,
706
+ delegations,
707
+ });
708
+ }
709
+ const tree = core.buildDelegationTree(delegations, wuId);
710
+ return createSuccessOutput({
711
+ message: `${DELEGATION_LIST_LOG_PREFIX} Delegation tree for ${wuId}:\n\n${core.formatDelegationTree(tree)}\n\nTotal: ${delegations.length} delegation(s)`,
712
+ delegations,
713
+ });
714
+ }
715
+ const initiativeId = parsedInput.data.initiative.toUpperCase();
716
+ const delegations = await core.getDelegationsByInitiative(initiativeId, registryDir, wuDir);
717
+ if (parsedInput.data.json) {
718
+ return createSuccessOutput(delegations);
719
+ }
720
+ if (delegations.length === 0) {
721
+ return createSuccessOutput({
722
+ message: `${DELEGATION_LIST_LOG_PREFIX} No delegations found for ${initiativeId}`,
723
+ delegations,
724
+ });
725
+ }
726
+ const typedDelegations = delegations;
727
+ const targetWuIds = new Set(typedDelegations
728
+ .map((record) => record.targetWuId)
729
+ .filter((wuId) => typeof wuId === 'string'));
730
+ const rootWuIds = [
731
+ ...new Set(typedDelegations
732
+ .map((record) => record.parentWuId)
733
+ .filter((wuId) => typeof wuId === 'string')),
734
+ ].filter((wuId) => !targetWuIds.has(wuId));
735
+ const lines = [`${DELEGATION_LIST_LOG_PREFIX} Delegations for ${initiativeId}:`, ''];
736
+ for (const rootWuId of rootWuIds) {
737
+ const tree = core.buildDelegationTree(delegations, rootWuId);
738
+ lines.push(core.formatDelegationTree(tree));
739
+ lines.push('');
740
+ }
741
+ lines.push(`Total: ${delegations.length} delegation(s) across ${rootWuIds.length} root WU(s)`);
742
+ return createSuccessOutput({
743
+ message: lines.join('\n'),
744
+ delegations,
745
+ root_wu_ids: rootWuIds,
746
+ });
747
+ }
748
+ catch (cause) {
749
+ return createFailureOutput(ORCHESTRATION_TOOL_ERROR_CODES.DELEGATION_LIST_ERROR, cause.message);
750
+ }
751
+ };
752
+ function createFailureOutput(code, message) {
753
+ return {
754
+ success: false,
755
+ error: {
756
+ code,
757
+ message,
758
+ },
759
+ };
760
+ }
761
+ function createSuccessOutput(data) {
762
+ return {
763
+ success: true,
764
+ data,
765
+ };
766
+ }
767
+ function resolveWorkspaceRoot(context) {
768
+ const root = context.metadata?.[RUNTIME_PROJECT_ROOT_METADATA_KEY];
769
+ if (typeof root === 'string' && root.trim().length > 0) {
770
+ return path.resolve(root);
771
+ }
772
+ return process.cwd();
773
+ }
774
+ function resolveTargetPath(context, inputPath) {
775
+ return path.resolve(resolveWorkspaceRoot(context), inputPath);
776
+ }
777
+ function resolveEncoding(encoding) {
778
+ return (encoding ?? UTF8_ENCODING);
779
+ }
780
+ function extractLineRange(content, startLine, endLine) {
781
+ if (startLine === undefined && endLine === undefined) {
782
+ return content;
783
+ }
784
+ const lines = content.split('\n');
785
+ const start = (startLine ?? 1) - 1;
786
+ const end = endLine ?? lines.length;
787
+ return lines.slice(start, end).join('\n');
788
+ }
789
+ function countOccurrences(content, searchText) {
790
+ if (!searchText) {
791
+ return 0;
792
+ }
793
+ let count = 0;
794
+ let cursor = 0;
795
+ while (cursor < content.length) {
796
+ const index = content.indexOf(searchText, cursor);
797
+ if (index === -1) {
798
+ break;
799
+ }
800
+ count += 1;
801
+ cursor = index + searchText.length;
802
+ }
803
+ return count;
804
+ }
805
+ async function getPathInfo(targetPath) {
806
+ try {
807
+ const targetStats = await stat(targetPath);
808
+ return { exists: true, isDirectory: targetStats.isDirectory() };
809
+ }
810
+ catch {
811
+ return { exists: false, isDirectory: false };
812
+ }
813
+ }
814
+ async function countItemsInDirectory(directoryPath) {
815
+ const entries = await readdir(directoryPath, { withFileTypes: true });
816
+ let count = 0;
817
+ for (const entry of entries) {
818
+ count += 1;
819
+ if (entry.isDirectory()) {
820
+ count += await countItemsInDirectory(path.join(directoryPath, entry.name));
821
+ }
822
+ }
823
+ return count;
824
+ }
825
+ const fileReadInProcess = async (rawInput, context) => {
826
+ const parsedInput = FILE_READ_INPUT_SCHEMA.safeParse(rawInput);
827
+ if (!parsedInput.success) {
828
+ return createFailureOutput(FILE_TOOL_ERROR_CODES.INVALID_INPUT, parsedInput.error.message);
829
+ }
830
+ const targetPath = resolveTargetPath(context, parsedInput.data.path);
831
+ const encoding = resolveEncoding(parsedInput.data.encoding);
832
+ const maxSize = parsedInput.data.max_size ?? DEFAULT_FILE_READ_MAX_SIZE_BYTES;
833
+ try {
834
+ const targetStats = await stat(targetPath);
835
+ if (targetStats.size > maxSize) {
836
+ return createFailureOutput(FILE_TOOL_ERROR_CODES.FILE_READ_TOO_LARGE, `File size (${targetStats.size} bytes) exceeds maximum allowed (${maxSize} bytes).`);
837
+ }
838
+ const content = await readFile(targetPath, { encoding });
839
+ const selectedContent = extractLineRange(content, parsedInput.data.start_line, parsedInput.data.end_line);
840
+ const totalLineCount = content.length === 0 ? 0 : content.split('\n').length;
841
+ return createSuccessOutput({
842
+ content: selectedContent,
843
+ metadata: {
844
+ size_bytes: targetStats.size,
845
+ line_count: totalLineCount,
846
+ lines_returned: selectedContent.length === 0 ? 0 : selectedContent.split('\n').length,
847
+ },
848
+ });
849
+ }
850
+ catch (cause) {
851
+ return createFailureOutput(FILE_TOOL_ERROR_CODES.FILE_READ_FAILED, cause.message);
852
+ }
853
+ };
854
+ const fileWriteInProcess = async (rawInput, context) => {
855
+ const parsedInput = FILE_WRITE_INPUT_SCHEMA.safeParse(rawInput);
856
+ if (!parsedInput.success) {
857
+ return createFailureOutput(FILE_TOOL_ERROR_CODES.INVALID_INPUT, parsedInput.error.message);
858
+ }
859
+ const targetPath = resolveTargetPath(context, parsedInput.data.path);
860
+ const encoding = resolveEncoding(parsedInput.data.encoding);
861
+ const createDirectories = !parsedInput.data.no_create_dirs;
862
+ const parentDirectory = path.dirname(targetPath);
863
+ try {
864
+ if (createDirectories) {
865
+ await mkdir(parentDirectory, { recursive: true });
866
+ }
867
+ else {
868
+ const parentInfo = await getPathInfo(parentDirectory);
869
+ if (!parentInfo.exists || !parentInfo.isDirectory) {
870
+ return createFailureOutput(FILE_TOOL_ERROR_CODES.FILE_WRITE_FAILED, `${FILE_TOOL_MESSAGES.PARENT_DIRECTORY_MISSING}: ${parentDirectory}`);
871
+ }
872
+ }
873
+ await writeFile(targetPath, parsedInput.data.content, { encoding });
874
+ return createSuccessOutput({
875
+ message: FILE_TOOL_MESSAGES.FILE_WRITTEN,
876
+ path: targetPath,
877
+ bytes_written: Buffer.byteLength(parsedInput.data.content, encoding),
878
+ });
879
+ }
880
+ catch (cause) {
881
+ return createFailureOutput(FILE_TOOL_ERROR_CODES.FILE_WRITE_FAILED, cause.message);
882
+ }
883
+ };
884
+ const fileEditInProcess = async (rawInput, context) => {
885
+ const parsedInput = FILE_EDIT_INPUT_SCHEMA.safeParse(rawInput);
886
+ if (!parsedInput.success) {
887
+ return createFailureOutput(FILE_TOOL_ERROR_CODES.INVALID_INPUT, parsedInput.error.message);
888
+ }
889
+ const targetPath = resolveTargetPath(context, parsedInput.data.path);
890
+ const encoding = resolveEncoding(parsedInput.data.encoding);
891
+ const replaceAll = parsedInput.data.replace_all ?? false;
892
+ try {
893
+ const content = await readFile(targetPath, { encoding });
894
+ const occurrenceCount = countOccurrences(content, parsedInput.data.old_string);
895
+ if (occurrenceCount === 0) {
896
+ return createFailureOutput(FILE_TOOL_ERROR_CODES.FILE_EDIT_TARGET_NOT_FOUND, `old_string not found in file: ${parsedInput.data.old_string}`);
897
+ }
898
+ if (occurrenceCount > 1 && !replaceAll) {
899
+ return createFailureOutput(FILE_TOOL_ERROR_CODES.FILE_EDIT_NOT_UNIQUE, `old_string is not unique in file (found ${occurrenceCount} occurrences).`);
900
+ }
901
+ const nextContent = replaceAll
902
+ ? content.split(parsedInput.data.old_string).join(parsedInput.data.new_string)
903
+ : content.replace(parsedInput.data.old_string, parsedInput.data.new_string);
904
+ await writeFile(targetPath, nextContent, { encoding });
905
+ return createSuccessOutput({
906
+ message: FILE_TOOL_MESSAGES.FILE_EDITED,
907
+ path: targetPath,
908
+ replacements: replaceAll ? occurrenceCount : 1,
909
+ });
910
+ }
911
+ catch (cause) {
912
+ return createFailureOutput(FILE_TOOL_ERROR_CODES.FILE_EDIT_FAILED, cause.message);
913
+ }
914
+ };
915
+ const fileDeleteInProcess = async (rawInput, context) => {
916
+ const parsedInput = FILE_DELETE_INPUT_SCHEMA.safeParse(rawInput);
917
+ if (!parsedInput.success) {
918
+ return createFailureOutput(FILE_TOOL_ERROR_CODES.INVALID_INPUT, parsedInput.error.message);
919
+ }
920
+ const targetPath = resolveTargetPath(context, parsedInput.data.path);
921
+ const recursive = parsedInput.data.recursive ?? false;
922
+ const force = parsedInput.data.force ?? false;
923
+ try {
924
+ const targetInfo = await getPathInfo(targetPath);
925
+ if (!targetInfo.exists) {
926
+ if (force) {
927
+ return createSuccessOutput({
928
+ message: FILE_TOOL_MESSAGES.DELETE_COMPLETE,
929
+ metadata: {
930
+ deleted_count: 0,
931
+ was_directory: false,
932
+ },
933
+ });
934
+ }
935
+ return createFailureOutput(FILE_TOOL_ERROR_CODES.FILE_DELETE_FAILED, `${FILE_TOOL_MESSAGES.PATH_NOT_FOUND}: ${targetPath}`);
936
+ }
937
+ if (targetInfo.isDirectory && !recursive) {
938
+ const entries = await readdir(targetPath);
939
+ if (entries.length > 0) {
940
+ return createFailureOutput(FILE_TOOL_ERROR_CODES.FILE_DELETE_FAILED, FILE_TOOL_MESSAGES.DIRECTORY_NOT_EMPTY);
941
+ }
942
+ }
943
+ let deletedCount = 1;
944
+ if (targetInfo.isDirectory && recursive) {
945
+ deletedCount += await countItemsInDirectory(targetPath);
946
+ }
947
+ await rm(targetPath, { recursive, force });
948
+ return createSuccessOutput({
949
+ message: FILE_TOOL_MESSAGES.DELETE_COMPLETE,
950
+ metadata: {
951
+ deleted_count: deletedCount,
952
+ was_directory: targetInfo.isDirectory,
953
+ },
954
+ });
955
+ }
956
+ catch (cause) {
957
+ return createFailureOutput(FILE_TOOL_ERROR_CODES.FILE_DELETE_FAILED, cause.message);
958
+ }
959
+ };
960
+ function resolveCommandBaseDir(context, baseDir) {
961
+ if (!baseDir || baseDir.trim().length === 0) {
962
+ return resolveWorkspaceRoot(context);
963
+ }
964
+ return path.resolve(resolveWorkspaceRoot(context), baseDir);
965
+ }
966
+ function normalizeDryRun(execute, dryRun) {
967
+ if (dryRun !== undefined) {
968
+ return dryRun;
969
+ }
970
+ return !execute;
971
+ }
972
+ function toIsoTimestamp(timestamp, fallback) {
973
+ const candidate = timestamp ?? fallback;
974
+ if (!candidate) {
975
+ return new Date().toISOString();
976
+ }
977
+ if (candidate.includes('T')) {
978
+ return candidate;
979
+ }
980
+ const parsed = new Date(candidate);
981
+ if (Number.isNaN(parsed.getTime())) {
982
+ return new Date().toISOString();
983
+ }
984
+ return parsed.toISOString();
985
+ }
986
+ function calculateDaysSince(dateString) {
987
+ if (!dateString) {
988
+ return null;
989
+ }
990
+ const parsedDate = new Date(dateString);
991
+ if (Number.isNaN(parsedDate.getTime())) {
992
+ return null;
993
+ }
994
+ return Math.floor((Date.now() - parsedDate.getTime()) / STATE_RUNTIME_CONSTANTS.ONE_DAY_MS);
995
+ }
996
+ async function loadWuLifecycleDocuments(core, wuDir) {
997
+ const directoryInfo = await getPathInfo(wuDir);
998
+ if (!directoryInfo.exists || !directoryInfo.isDirectory) {
999
+ return [];
1000
+ }
1001
+ const files = await readdir(wuDir);
1002
+ const documents = [];
1003
+ for (const fileName of files) {
1004
+ if (!fileName.startsWith(STATE_RUNTIME_CONSTANTS.WU_FILE_PREFIX) ||
1005
+ !fileName.endsWith(STATE_RUNTIME_CONSTANTS.YAML_EXTENSION)) {
1006
+ continue;
1007
+ }
1008
+ const filePath = path.join(wuDir, fileName);
1009
+ try {
1010
+ const rawDoc = core.readWURaw(filePath);
1011
+ if (!rawDoc || typeof rawDoc.id !== 'string' || typeof rawDoc.status !== 'string') {
1012
+ continue;
1013
+ }
1014
+ documents.push({
1015
+ id: rawDoc.id,
1016
+ status: rawDoc.status,
1017
+ title: typeof rawDoc.title === 'string' ? rawDoc.title : undefined,
1018
+ lane: typeof rawDoc.lane === 'string' ? rawDoc.lane : undefined,
1019
+ created: typeof rawDoc.created === 'string' ? rawDoc.created : undefined,
1020
+ claimed_at: typeof rawDoc.claimed_at === 'string' ? rawDoc.claimed_at : undefined,
1021
+ completed: typeof rawDoc.completed === 'string' ? rawDoc.completed : undefined,
1022
+ completed_at: typeof rawDoc.completed_at === 'string' ? rawDoc.completed_at : undefined,
1023
+ updated: typeof rawDoc.updated === 'string' ? rawDoc.updated : undefined,
1024
+ filePath,
1025
+ });
1026
+ }
1027
+ catch {
1028
+ continue;
1029
+ }
1030
+ }
1031
+ return documents;
1032
+ }
1033
+ function inferBootstrapEvents(core, wu) {
1034
+ const readyLikeStatuses = new Set([
1035
+ core.WU_STATUS.READY,
1036
+ core.WU_STATUS.BACKLOG,
1037
+ core.WU_STATUS.TODO,
1038
+ ]);
1039
+ const doneStatuses = new Set([core.WU_STATUS.DONE, core.WU_STATUS.COMPLETED]);
1040
+ const normalizedStatus = wu.status;
1041
+ if (readyLikeStatuses.has(normalizedStatus)) {
1042
+ return [];
1043
+ }
1044
+ const claimTimestamp = toIsoTimestamp(wu.claimed_at, wu.created);
1045
+ const events = [
1046
+ {
1047
+ type: STATE_RUNTIME_EVENT_TYPES.CLAIM,
1048
+ wuId: wu.id,
1049
+ lane: wu.lane ?? STATE_RUNTIME_CONSTANTS.UNKNOWN_LANE,
1050
+ title: wu.title ?? STATE_RUNTIME_CONSTANTS.UNTITLED_WU,
1051
+ timestamp: claimTimestamp,
1052
+ },
1053
+ ];
1054
+ if (normalizedStatus === core.WU_STATUS.BLOCKED) {
1055
+ const blockedAt = new Date(claimTimestamp);
1056
+ blockedAt.setSeconds(blockedAt.getSeconds() + 1);
1057
+ events.push({
1058
+ type: STATE_RUNTIME_EVENT_TYPES.BLOCK,
1059
+ wuId: wu.id,
1060
+ timestamp: blockedAt.toISOString(),
1061
+ reason: STATE_RUNTIME_CONSTANTS.BOOTSTRAP_BLOCK_REASON,
1062
+ });
1063
+ return events;
1064
+ }
1065
+ if (doneStatuses.has(normalizedStatus)) {
1066
+ events.push({
1067
+ type: STATE_RUNTIME_EVENT_TYPES.COMPLETE,
1068
+ wuId: wu.id,
1069
+ timestamp: toIsoTimestamp(wu.completed_at ?? wu.completed, claimTimestamp),
1070
+ });
1071
+ }
1072
+ return events;
1073
+ }
1074
+ async function listActiveWuIds(projectRoot) {
1075
+ const core = await getCoreLazy();
1076
+ const activeStatuses = new Set([core.WU_STATUS.IN_PROGRESS, core.WU_STATUS.BLOCKED]);
1077
+ const wus = await core.listWUs({ projectRoot });
1078
+ return new Set(wus.filter((wu) => activeStatuses.has(wu.status)).map((wu) => wu.id));
1079
+ }
1080
+ async function readNdjsonRecords(filePath) {
1081
+ try {
1082
+ const content = await readFile(filePath, UTF8_ENCODING);
1083
+ return content
1084
+ .split('\n')
1085
+ .map((line) => line.trim())
1086
+ .filter((line) => line.length > 0)
1087
+ .map((line) => {
1088
+ try {
1089
+ return JSON.parse(line);
1090
+ }
1091
+ catch {
1092
+ return null;
1093
+ }
1094
+ })
1095
+ .filter((line) => line !== null);
1096
+ }
1097
+ catch {
1098
+ return [];
1099
+ }
1100
+ }
1101
+ function buildStateDoctorDeps(core, projectRoot) {
1102
+ const config = core.getConfig({ projectRoot });
1103
+ const wuDir = path.join(projectRoot, config.directories.wuDir);
1104
+ const stampsDir = path.join(projectRoot, config.state.stampsDir);
1105
+ const stateDir = path.join(projectRoot, config.state.stateDir);
1106
+ const signalsPath = path.join(projectRoot, core.LUMENFLOW_PATHS.MEMORY_SIGNALS);
1107
+ const eventsPath = path.join(projectRoot, core.LUMENFLOW_PATHS.WU_EVENTS);
1108
+ return {
1109
+ listWUs: async () => {
1110
+ const documents = await loadWuLifecycleDocuments(core, wuDir);
1111
+ return documents.map((document) => ({
1112
+ id: document.id,
1113
+ status: document.status,
1114
+ lane: document.lane,
1115
+ title: document.title,
1116
+ }));
1117
+ },
1118
+ listStamps: async () => {
1119
+ try {
1120
+ const files = await readdir(stampsDir);
1121
+ return files
1122
+ .filter((file) => file.endsWith(STATE_RUNTIME_CONSTANTS.DONE_STAMP_EXTENSION))
1123
+ .map((file) => file.slice(0, -1 * STATE_RUNTIME_CONSTANTS.DONE_STAMP_EXTENSION.length));
1124
+ }
1125
+ catch {
1126
+ return [];
1127
+ }
1128
+ },
1129
+ listSignals: async () => {
1130
+ const signals = await readNdjsonRecords(signalsPath);
1131
+ return signals
1132
+ .filter((signal) => typeof signal.id === 'string')
1133
+ .map((signal) => ({
1134
+ id: String(signal.id),
1135
+ wuId: typeof signal.wuId === 'string'
1136
+ ? signal.wuId
1137
+ : typeof signal.wu_id === 'string'
1138
+ ? signal.wu_id
1139
+ : undefined,
1140
+ timestamp: typeof signal.timestamp === 'string' ? signal.timestamp : undefined,
1141
+ message: typeof signal.message === 'string' ? signal.message : undefined,
1142
+ }));
1143
+ },
1144
+ listEvents: async () => {
1145
+ const events = await readNdjsonRecords(eventsPath);
1146
+ return events
1147
+ .filter((event) => (typeof event.wuId === 'string' || typeof event.wu_id === 'string') &&
1148
+ typeof event.type === 'string')
1149
+ .map((event) => ({
1150
+ wuId: typeof event.wuId === 'string'
1151
+ ? event.wuId
1152
+ : typeof event.wu_id === 'string'
1153
+ ? event.wu_id
1154
+ : '',
1155
+ type: String(event.type),
1156
+ timestamp: typeof event.timestamp === 'string' ? event.timestamp : undefined,
1157
+ }));
1158
+ },
1159
+ removeSignal: async (signalId) => {
1160
+ const signals = await readNdjsonRecords(signalsPath);
1161
+ const retainedSignals = signals.filter((signal) => signal.id !== signalId);
1162
+ const payload = retainedSignals.map((signal) => JSON.stringify(signal)).join('\n') +
1163
+ (retainedSignals.length > 0 ? '\n' : '');
1164
+ await writeFile(signalsPath, payload, UTF8_ENCODING);
1165
+ },
1166
+ removeEvent: async (wuId) => {
1167
+ const events = await readNdjsonRecords(eventsPath);
1168
+ const retainedEvents = events.filter((event) => event.wuId !== wuId && event.wu_id !== wuId);
1169
+ const payload = retainedEvents.map((event) => JSON.stringify(event)).join('\n') +
1170
+ (retainedEvents.length > 0 ? '\n' : '');
1171
+ await writeFile(eventsPath, payload, UTF8_ENCODING);
1172
+ },
1173
+ createStamp: async (wuId, title) => {
1174
+ await mkdir(stampsDir, { recursive: true });
1175
+ const stampPath = path.join(stampsDir, `${wuId}${STATE_RUNTIME_CONSTANTS.DONE_STAMP_EXTENSION}`);
1176
+ const completedDate = new Date().toISOString().slice(0, 10);
1177
+ const stampContent = `WU ${wuId} — ${title}\nCompleted: ${completedDate}\n`;
1178
+ await writeFile(stampPath, stampContent, UTF8_ENCODING);
1179
+ },
1180
+ emitEvent: async (event) => {
1181
+ const stateStore = new core.WUStateStore(stateDir);
1182
+ await stateStore.load();
1183
+ if (event.type === STATE_RUNTIME_EVENT_TYPES.RELEASE) {
1184
+ await stateStore.release(event.wuId, event.reason ?? STATE_RUNTIME_CONSTANTS.STATE_DOCTOR_FIX_REASON);
1185
+ return;
1186
+ }
1187
+ await stateStore.complete(event.wuId);
1188
+ },
1189
+ };
1190
+ }
1191
+ const backlogPruneInProcess = async (rawInput, context) => {
1192
+ const parsedInput = BACKLOG_PRUNE_INPUT_SCHEMA.safeParse(rawInput);
1193
+ if (!parsedInput.success) {
1194
+ return createFailureOutput(STATE_TOOL_ERROR_CODES.INVALID_INPUT, parsedInput.error.message);
1195
+ }
1196
+ try {
1197
+ const core = await getCoreLazy();
1198
+ const workspaceRoot = resolveWorkspaceRoot(context);
1199
+ const config = core.getConfig({ projectRoot: workspaceRoot });
1200
+ const wuDir = path.join(workspaceRoot, config.directories.wuDir);
1201
+ const wuDocuments = await loadWuLifecycleDocuments(core, wuDir);
1202
+ const dryRun = normalizeDryRun(parsedInput.data.execute, parsedInput.data.dry_run);
1203
+ const staleDaysInProgress = parsedInput.data.stale_days_in_progress ??
1204
+ STATE_RUNTIME_CONSTANTS.DEFAULT_STALE_DAYS_IN_PROGRESS;
1205
+ const staleDaysReady = parsedInput.data.stale_days_ready ?? STATE_RUNTIME_CONSTANTS.DEFAULT_STALE_DAYS_READY;
1206
+ const archiveDays = parsedInput.data.archive_days ?? STATE_RUNTIME_CONSTANTS.DEFAULT_ARCHIVE_DAYS;
1207
+ const doneStatuses = new Set([core.WU_STATUS.DONE, core.WU_STATUS.COMPLETED]);
1208
+ const staleCandidates = [];
1209
+ const archivableCandidates = [];
1210
+ const healthyCandidates = [];
1211
+ for (const wu of wuDocuments) {
1212
+ if (doneStatuses.has(wu.status)) {
1213
+ const completedAge = calculateDaysSince(wu.completed ?? wu.completed_at);
1214
+ if (completedAge !== null && completedAge > archiveDays) {
1215
+ archivableCandidates.push(wu);
1216
+ }
1217
+ else {
1218
+ healthyCandidates.push(wu);
1219
+ }
1220
+ continue;
1221
+ }
1222
+ if (wu.status === core.WU_STATUS.BLOCKED) {
1223
+ healthyCandidates.push(wu);
1224
+ continue;
1225
+ }
1226
+ const lastActivity = wu.updated ?? wu.created;
1227
+ const daysSinceActivity = calculateDaysSince(lastActivity);
1228
+ if (daysSinceActivity === null) {
1229
+ healthyCandidates.push(wu);
1230
+ continue;
1231
+ }
1232
+ const isInProgressStale = wu.status === core.WU_STATUS.IN_PROGRESS && daysSinceActivity > staleDaysInProgress;
1233
+ const isReadyLikeStale = (wu.status === core.WU_STATUS.READY ||
1234
+ wu.status === core.WU_STATUS.BACKLOG ||
1235
+ wu.status === core.WU_STATUS.TODO) &&
1236
+ daysSinceActivity > staleDaysReady;
1237
+ if (isInProgressStale || isReadyLikeStale) {
1238
+ staleCandidates.push(wu);
1239
+ }
1240
+ else {
1241
+ healthyCandidates.push(wu);
1242
+ }
1243
+ }
1244
+ let taggedCount = 0;
1245
+ if (!dryRun) {
1246
+ const noteDate = new Date().toISOString().slice(0, 10);
1247
+ for (const staleWu of staleCandidates) {
1248
+ try {
1249
+ const wuDoc = core.readWURaw(staleWu.filePath);
1250
+ core.appendNote(wuDoc, `[${noteDate}] ${STATE_RUNTIME_CONSTANTS.STALE_NOTE_TEMPLATE}`);
1251
+ core.writeWU(staleWu.filePath, wuDoc);
1252
+ taggedCount += 1;
1253
+ }
1254
+ catch {
1255
+ continue;
1256
+ }
1257
+ }
1258
+ }
1259
+ return createSuccessOutput({
1260
+ dry_run: dryRun,
1261
+ total_wus: wuDocuments.length,
1262
+ stale_count: staleCandidates.length,
1263
+ stale_ids: staleCandidates.map((wu) => wu.id),
1264
+ archivable_count: archivableCandidates.length,
1265
+ archivable_ids: archivableCandidates.map((wu) => wu.id),
1266
+ healthy_count: healthyCandidates.length,
1267
+ tagged_count: taggedCount,
1268
+ });
1269
+ }
1270
+ catch (cause) {
1271
+ return createFailureOutput(STATE_TOOL_ERROR_CODES.BACKLOG_PRUNE_FAILED, cause.message);
1272
+ }
1273
+ };
1274
+ const stateBootstrapInProcess = async (rawInput, context) => {
1275
+ const parsedInput = STATE_BOOTSTRAP_INPUT_SCHEMA.safeParse(rawInput);
1276
+ if (!parsedInput.success) {
1277
+ return createFailureOutput(STATE_TOOL_ERROR_CODES.INVALID_INPUT, parsedInput.error.message);
1278
+ }
1279
+ try {
1280
+ const core = await getCoreLazy();
1281
+ const workspaceRoot = resolveWorkspaceRoot(context);
1282
+ const config = core.getConfig({ projectRoot: workspaceRoot });
1283
+ const wuDir = parsedInput.data.wu_dir
1284
+ ? path.resolve(workspaceRoot, parsedInput.data.wu_dir)
1285
+ : path.join(workspaceRoot, config.directories.wuDir);
1286
+ const stateDir = parsedInput.data.state_dir
1287
+ ? path.resolve(workspaceRoot, parsedInput.data.state_dir)
1288
+ : path.join(workspaceRoot, config.state.stateDir);
1289
+ const dryRun = normalizeDryRun(parsedInput.data.execute, parsedInput.data.dry_run);
1290
+ const force = parsedInput.data.force ?? false;
1291
+ const eventsFilePath = path.join(stateDir, STATE_RUNTIME_CONSTANTS.WU_EVENTS_FILE_NAME);
1292
+ const wuDocuments = await loadWuLifecycleDocuments(core, wuDir);
1293
+ const bootstrapEvents = wuDocuments
1294
+ .flatMap((wu) => inferBootstrapEvents(core, wu))
1295
+ .sort((left, right) => {
1296
+ return new Date(left.timestamp).getTime() - new Date(right.timestamp).getTime();
1297
+ });
1298
+ if (!dryRun) {
1299
+ const existingStateFile = await getPathInfo(eventsFilePath);
1300
+ if (existingStateFile.exists && !force) {
1301
+ return createFailureOutput(STATE_TOOL_ERROR_CODES.STATE_BOOTSTRAP_FAILED, `State file already exists: ${eventsFilePath}. Use --force to overwrite.`);
1302
+ }
1303
+ await mkdir(stateDir, { recursive: true });
1304
+ const payload = bootstrapEvents.map((event) => JSON.stringify(event)).join('\n') +
1305
+ (bootstrapEvents.length > 0 ? '\n' : '');
1306
+ await writeFile(eventsFilePath, payload, UTF8_ENCODING);
1307
+ }
1308
+ return createSuccessOutput({
1309
+ dry_run: dryRun,
1310
+ events_generated: bootstrapEvents.length,
1311
+ events_written: dryRun ? 0 : bootstrapEvents.length,
1312
+ skipped: 0,
1313
+ warnings: wuDocuments.length === 0 ? [STATE_RUNTIME_MESSAGES.WU_DIRECTORY_EMPTY_OR_MISSING] : [],
1314
+ });
1315
+ }
1316
+ catch (cause) {
1317
+ return createFailureOutput(STATE_TOOL_ERROR_CODES.STATE_BOOTSTRAP_FAILED, cause.message);
1318
+ }
1319
+ };
1320
+ const stateCleanupInProcess = async (rawInput, context) => {
1321
+ const parsedInput = STATE_CLEANUP_INPUT_SCHEMA.safeParse(rawInput);
1322
+ if (!parsedInput.success) {
1323
+ return createFailureOutput(STATE_TOOL_ERROR_CODES.INVALID_INPUT, parsedInput.error.message);
1324
+ }
1325
+ const exclusiveFlags = [
1326
+ parsedInput.data.signals_only,
1327
+ parsedInput.data.memory_only,
1328
+ parsedInput.data.events_only,
1329
+ ].filter(Boolean);
1330
+ if (exclusiveFlags.length > 1) {
1331
+ return createFailureOutput(STATE_TOOL_ERROR_CODES.INVALID_INPUT, STATE_RUNTIME_MESSAGES.MUTUALLY_EXCLUSIVE_CLEANUP_FLAGS);
1332
+ }
1333
+ try {
1334
+ const core = await getCoreLazy();
1335
+ const memory = await getMemoryLazy();
1336
+ const projectRoot = resolveCommandBaseDir(context, parsedInput.data.base_dir);
1337
+ const result = await core.cleanupState(projectRoot, {
1338
+ dryRun: parsedInput.data.dry_run,
1339
+ signalsOnly: parsedInput.data.signals_only,
1340
+ memoryOnly: parsedInput.data.memory_only,
1341
+ eventsOnly: parsedInput.data.events_only,
1342
+ cleanupSignals: async (dir, options) => memory.cleanupSignals(dir, {
1343
+ dryRun: options.dryRun,
1344
+ getActiveWuIds: () => listActiveWuIds(dir),
1345
+ }),
1346
+ cleanupMemory: async (dir, options) => memory.cleanupMemory(dir, {
1347
+ dryRun: options.dryRun,
1348
+ }),
1349
+ archiveEvents: async (dir, options) => core.archiveWuEvents(dir, {
1350
+ dryRun: options.dryRun,
1351
+ }),
1352
+ });
1353
+ return createSuccessOutput(result);
1354
+ }
1355
+ catch (cause) {
1356
+ return createFailureOutput(STATE_TOOL_ERROR_CODES.STATE_CLEANUP_FAILED, cause.message);
1357
+ }
1358
+ };
1359
+ const stateDoctorInProcess = async (rawInput, context) => {
1360
+ const parsedInput = STATE_DOCTOR_INPUT_SCHEMA.safeParse(rawInput);
1361
+ if (!parsedInput.success) {
1362
+ return createFailureOutput(STATE_TOOL_ERROR_CODES.INVALID_INPUT, parsedInput.error.message);
1363
+ }
1364
+ try {
1365
+ const core = await getCoreLazy();
1366
+ const projectRoot = resolveCommandBaseDir(context, parsedInput.data.base_dir);
1367
+ const deps = buildStateDoctorDeps(core, projectRoot);
1368
+ const diagnosis = await core.diagnoseState(projectRoot, deps, {
1369
+ fix: parsedInput.data.fix,
1370
+ dryRun: parsedInput.data.dry_run,
1371
+ });
1372
+ return createSuccessOutput(diagnosis);
1373
+ }
1374
+ catch (cause) {
1375
+ return createFailureOutput(STATE_TOOL_ERROR_CODES.STATE_DOCTOR_FAILED, cause.message);
1376
+ }
1377
+ };
1378
+ const signalCleanupInProcess = async (rawInput, context) => {
1379
+ const parsedInput = SIGNAL_CLEANUP_INPUT_SCHEMA.safeParse(rawInput);
1380
+ if (!parsedInput.success) {
1381
+ return createFailureOutput(STATE_TOOL_ERROR_CODES.INVALID_INPUT, parsedInput.error.message);
1382
+ }
1383
+ try {
1384
+ const memory = await getMemoryLazy();
1385
+ const projectRoot = resolveCommandBaseDir(context, parsedInput.data.base_dir);
1386
+ const result = await memory.cleanupSignals(projectRoot, {
1387
+ dryRun: parsedInput.data.dry_run,
1388
+ ttl: parsedInput.data.ttl,
1389
+ unreadTtl: parsedInput.data.unread_ttl,
1390
+ maxEntries: parsedInput.data.max_entries,
1391
+ getActiveWuIds: () => listActiveWuIds(projectRoot),
1392
+ });
1393
+ return createSuccessOutput(result);
1394
+ }
1395
+ catch (cause) {
1396
+ return createFailureOutput(STATE_TOOL_ERROR_CODES.SIGNAL_CLEANUP_FAILED, cause.message);
1397
+ }
1398
+ };
1399
+ // --- WU-1802: Validation/Lane in-process handler implementations ---
1400
+ const VALIDATION_TOOL_ERROR_CODES = {
1401
+ INVALID_INPUT: 'INVALID_INPUT',
1402
+ VALIDATE_ERROR: 'VALIDATE_ERROR',
1403
+ VALIDATE_AGENT_SKILLS_ERROR: 'VALIDATE_AGENT_SKILLS_ERROR',
1404
+ VALIDATE_AGENT_SYNC_ERROR: 'VALIDATE_AGENT_SYNC_ERROR',
1405
+ VALIDATE_BACKLOG_SYNC_ERROR: 'VALIDATE_BACKLOG_SYNC_ERROR',
1406
+ VALIDATE_SKILLS_SPEC_ERROR: 'VALIDATE_SKILLS_SPEC_ERROR',
1407
+ LUMENFLOW_VALIDATE_ERROR: 'LUMENFLOW_VALIDATE_ERROR',
1408
+ LANE_HEALTH_ERROR: 'LANE_HEALTH_ERROR',
1409
+ LANE_SUGGEST_ERROR: 'LANE_SUGGEST_ERROR',
1410
+ WU_STATUS_ERROR: 'WU_STATUS_ERROR',
1411
+ WU_CREATE_ERROR: 'WU_CREATE_ERROR',
1412
+ WU_CLAIM_ERROR: 'WU_CLAIM_ERROR',
1413
+ WU_PROTO_ERROR: 'WU_PROTO_ERROR',
1414
+ WU_DONE_ERROR: 'WU_DONE_ERROR',
1415
+ WU_PREP_ERROR: 'WU_PREP_ERROR',
1416
+ WU_SANDBOX_ERROR: 'WU_SANDBOX_ERROR',
1417
+ WU_PRUNE_ERROR: 'WU_PRUNE_ERROR',
1418
+ WU_DELETE_ERROR: 'WU_DELETE_ERROR',
1419
+ WU_CLEANUP_ERROR: 'WU_CLEANUP_ERROR',
1420
+ WU_BRIEF_ERROR: 'WU_BRIEF_ERROR',
1421
+ WU_DELEGATE_ERROR: 'WU_DELEGATE_ERROR',
1422
+ WU_UNLOCK_LANE_ERROR: 'WU_UNLOCK_LANE_ERROR',
1423
+ AGENT_SESSION_ERROR: 'AGENT_SESSION_ERROR',
1424
+ AGENT_SESSION_END_ERROR: 'AGENT_SESSION_END_ERROR',
1425
+ AGENT_LOG_ISSUE_ERROR: 'AGENT_LOG_ISSUE_ERROR',
1426
+ AGENT_ISSUES_QUERY_ERROR: 'AGENT_ISSUES_QUERY_ERROR',
1427
+ LUMENFLOW_INIT_ERROR: 'LUMENFLOW_INIT_ERROR',
1428
+ LUMENFLOW_DOCTOR_ERROR: 'LUMENFLOW_DOCTOR_ERROR',
1429
+ LUMENFLOW_INTEGRATE_ERROR: 'LUMENFLOW_INTEGRATE_ERROR',
1430
+ LUMENFLOW_UPGRADE_ERROR: 'LUMENFLOW_UPGRADE_ERROR',
1431
+ LUMENFLOW_RELEASE_ERROR: 'LUMENFLOW_RELEASE_ERROR',
1432
+ DOCS_SYNC_ERROR: 'DOCS_SYNC_ERROR',
1433
+ SYNC_TEMPLATES_ALIAS_ERROR: 'SYNC_TEMPLATES_ALIAS_ERROR',
1434
+ PLAN_CREATE_ERROR: 'PLAN_CREATE_ERROR',
1435
+ PLAN_EDIT_ERROR: 'PLAN_EDIT_ERROR',
1436
+ PLAN_LINK_ERROR: 'PLAN_LINK_ERROR',
1437
+ PLAN_PROMOTE_ERROR: 'PLAN_PROMOTE_ERROR',
1438
+ GATES_ERROR: 'GATES_ERROR',
1439
+ INITIATIVE_LIST_ERROR: 'INITIATIVE_LIST_ERROR',
1440
+ INITIATIVE_STATUS_ERROR: 'INITIATIVE_STATUS_ERROR',
1441
+ INITIATIVE_CREATE_ERROR: 'INITIATIVE_CREATE_ERROR',
1442
+ INITIATIVE_EDIT_ERROR: 'INITIATIVE_EDIT_ERROR',
1443
+ INITIATIVE_ADD_WU_ERROR: 'INITIATIVE_ADD_WU_ERROR',
1444
+ INITIATIVE_REMOVE_WU_ERROR: 'INITIATIVE_REMOVE_WU_ERROR',
1445
+ INITIATIVE_BULK_ASSIGN_ERROR: 'INITIATIVE_BULK_ASSIGN_ERROR',
1446
+ INITIATIVE_PLAN_ERROR: 'INITIATIVE_PLAN_ERROR',
1447
+ INIT_PLAN_ERROR: 'INIT_PLAN_ERROR',
1448
+ ORCHESTRATE_INITIATIVE_ERROR: 'ORCHESTRATE_INITIATIVE_ERROR',
1449
+ MEM_INIT_ERROR: 'MEM_INIT_ERROR',
1450
+ MEM_START_ERROR: 'MEM_START_ERROR',
1451
+ MEM_READY_ERROR: 'MEM_READY_ERROR',
1452
+ MEM_CHECKPOINT_ERROR: 'MEM_CHECKPOINT_ERROR',
1453
+ MEM_CLEANUP_ERROR: 'MEM_CLEANUP_ERROR',
1454
+ MEM_CONTEXT_ERROR: 'MEM_CONTEXT_ERROR',
1455
+ MEM_CREATE_ERROR: 'MEM_CREATE_ERROR',
1456
+ MEM_DELETE_ERROR: 'MEM_DELETE_ERROR',
1457
+ MEM_EXPORT_ERROR: 'MEM_EXPORT_ERROR',
1458
+ MEM_INBOX_ERROR: 'MEM_INBOX_ERROR',
1459
+ MEM_SIGNAL_ERROR: 'MEM_SIGNAL_ERROR',
1460
+ MEM_SUMMARIZE_ERROR: 'MEM_SUMMARIZE_ERROR',
1461
+ MEM_TRIAGE_ERROR: 'MEM_TRIAGE_ERROR',
1462
+ MEM_RECOVER_ERROR: 'MEM_RECOVER_ERROR',
1463
+ WU_BLOCK_ERROR: 'WU_BLOCK_ERROR',
1464
+ WU_UNBLOCK_ERROR: 'WU_UNBLOCK_ERROR',
1465
+ WU_EDIT_ERROR: 'WU_EDIT_ERROR',
1466
+ WU_RELEASE_ERROR: 'WU_RELEASE_ERROR',
1467
+ WU_RECOVER_ERROR: 'WU_RECOVER_ERROR',
1468
+ WU_REPAIR_ERROR: 'WU_REPAIR_ERROR',
1469
+ WU_DEPS_ERROR: 'WU_DEPS_ERROR',
1470
+ WU_PREFLIGHT_ERROR: 'WU_PREFLIGHT_ERROR',
1471
+ WU_VALIDATE_ERROR: 'WU_VALIDATE_ERROR',
1472
+ WU_INFER_LANE_ERROR: 'WU_INFER_LANE_ERROR',
1473
+ MISSING_PARAMETER: 'MISSING_PARAMETER',
1474
+ };
1475
+ const VALIDATION_TOOL_MESSAGES = {
1476
+ VALIDATE_PASSED: 'Validation passed',
1477
+ VALIDATE_INVALID_WU: 'Invalid WU',
1478
+ NO_WU_DIR: 'No WU directory found, skipping',
1479
+ NO_SKILLS_DIR: 'No skills directory found, skipping',
1480
+ NO_AGENTS_DIR: 'No agents directory found, skipping',
1481
+ AGENT_SKILLS_FAILED: 'Agent skills validation failed',
1482
+ AGENT_SYNC_FAILED: 'Agent sync validation failed',
1483
+ EMPTY_FILE: 'empty file',
1484
+ EMPTY_AGENT_CONFIG: 'empty agent config',
1485
+ EMPTY_SKILLS_SPEC: 'empty skills spec',
1486
+ BACKLOG_SYNC_VALID: 'Backlog sync valid',
1487
+ BACKLOG_SYNC_FAILED: 'Backlog sync validation failed',
1488
+ SKILLS_SPEC_FAILED: 'Skills spec validation failed',
1489
+ LANE_HEALTH_PASSED: 'Lane health check complete',
1490
+ WU_BLOCK_PASSED: 'WU blocked successfully',
1491
+ WU_UNBLOCK_PASSED: 'WU unblocked successfully',
1492
+ WU_EDIT_PASSED: 'WU edited successfully',
1493
+ WU_RELEASE_PASSED: 'WU released successfully',
1494
+ WU_RELEASE_NO_REASON: 'No reason provided',
1495
+ };
1496
+ /** WU-1856: Single function replaces PREFIX/SUFFIX constant fragmentation. */
1497
+ function validationCountMsg(label, count) {
1498
+ return `${label}: ${count} checked`;
1499
+ }
1500
+ const VALIDATION_TOOL_FILE_EXTENSIONS = ['.md', '.yaml', '.yml'];
1501
+ const WU_FILE_EXTENSIONS = ['.yaml', '.yml'];
1502
+ const SKILLS_DIR_RELATIVE = '.claude/skills';
1503
+ const AGENTS_DIR_RELATIVE = '.claude/agents';
1504
+ const VALIDATE_INPUT_SCHEMA = z.object({
1505
+ id: z.string().optional(),
1506
+ strict: z.boolean().optional(),
1507
+ done_only: z.boolean().optional(),
1508
+ });
1509
+ const VALIDATE_AGENT_SKILLS_INPUT_SCHEMA = z.object({
1510
+ skill: z.string().optional(),
1511
+ });
1512
+ const VALIDATE_AGENT_SYNC_INPUT_SCHEMA = z.object({});
1513
+ const VALIDATE_BACKLOG_SYNC_INPUT_SCHEMA = z.object({});
1514
+ const VALIDATE_SKILLS_SPEC_INPUT_SCHEMA = z.object({});
1515
+ const LANE_HEALTH_INPUT_SCHEMA = z.object({
1516
+ json: z.boolean().optional(),
1517
+ verbose: z.boolean().optional(),
1518
+ no_coverage: z.boolean().optional(),
1519
+ });
1520
+ const LANE_SUGGEST_INPUT_SCHEMA = z.object({
1521
+ dry_run: z.boolean().optional(),
1522
+ interactive: z.boolean().optional(),
1523
+ output: z.string().optional(),
1524
+ json: z.boolean().optional(),
1525
+ no_llm: z.boolean().optional(),
1526
+ include_git: z.boolean().optional(),
1527
+ });
1528
+ /** Helper: filter files by validation-relevant extensions */
1529
+ function hasValidationExtension(filename) {
1530
+ return VALIDATION_TOOL_FILE_EXTENSIONS.some((ext) => filename.endsWith(ext));
1531
+ }
1532
+ /** Helper: filter files by WU YAML extensions */
1533
+ function hasWUExtension(filename) {
1534
+ return WU_FILE_EXTENSIONS.some((ext) => filename.endsWith(ext));
1535
+ }
1536
+ /** Helper: extract Zod issue messages from safeParse error */
1537
+ function formatZodIssues(zodError) {
1538
+ return (zodError.issues?.map((i) => i.message).join('; ') ??
1539
+ VALIDATION_TOOL_MESSAGES.VALIDATE_INVALID_WU);
1540
+ }
1541
+ /**
1542
+ * validate handler — delegates to @lumenflow/core validateWU per file
1543
+ */
1544
+ const validateInProcess = async (rawInput, context) => {
1545
+ const parsedInput = VALIDATE_INPUT_SCHEMA.safeParse(rawInput ?? {});
1546
+ if (!parsedInput.success) {
1547
+ return createFailureOutput(VALIDATION_TOOL_ERROR_CODES.INVALID_INPUT, parsedInput.error.message);
1548
+ }
1549
+ try {
1550
+ const core = await getCoreLazy();
1551
+ const projectRoot = resolveWorkspaceRoot(context);
1552
+ const config = core.getConfig({ projectRoot });
1553
+ const wuDir = path.join(projectRoot, config.directories.wuDir);
1554
+ if (parsedInput.data.id) {
1555
+ const wuPath = path.join(wuDir, `${parsedInput.data.id}.yaml`);
1556
+ const result = core.validateWU(core.parseYAML(await readFile(wuPath, UTF8_ENCODING)));
1557
+ return result.success
1558
+ ? createSuccessOutput({
1559
+ message: `${parsedInput.data.id} ${VALIDATION_TOOL_MESSAGES.VALIDATE_PASSED}`,
1560
+ })
1561
+ : createFailureOutput(VALIDATION_TOOL_ERROR_CODES.VALIDATE_ERROR, formatZodIssues(result.error));
1562
+ }
1563
+ // validateAllWUs is not exported from core — inline the aggregation
1564
+ let files;
1565
+ try {
1566
+ files = (await readdir(wuDir)).filter(hasWUExtension);
1567
+ }
1568
+ catch {
1569
+ return createSuccessOutput({ message: VALIDATION_TOOL_MESSAGES.NO_WU_DIR });
1570
+ }
1571
+ let totalValid = 0;
1572
+ let totalInvalid = 0;
1573
+ const errors = [];
1574
+ const STATUS_DONE = 'done';
1575
+ for (const file of files) {
1576
+ const content = await readFile(path.join(wuDir, file), UTF8_ENCODING);
1577
+ const parsed = core.parseYAML(content);
1578
+ if (parsedInput.data.done_only) {
1579
+ if (parsed &&
1580
+ typeof parsed === 'object' &&
1581
+ 'status' in parsed &&
1582
+ parsed.status !== STATUS_DONE)
1583
+ continue;
1584
+ }
1585
+ const result = core.validateWU(parsed);
1586
+ if (result.success) {
1587
+ totalValid++;
1588
+ }
1589
+ else {
1590
+ totalInvalid++;
1591
+ errors.push(`${file}: ${formatZodIssues(result.error)}`);
1592
+ }
1593
+ }
1594
+ if (totalInvalid === 0) {
1595
+ return createSuccessOutput({
1596
+ message: `${VALIDATION_TOOL_MESSAGES.VALIDATE_PASSED}: ${totalValid} valid`,
1597
+ totalValid,
1598
+ });
1599
+ }
1600
+ return createFailureOutput(VALIDATION_TOOL_ERROR_CODES.VALIDATE_ERROR, `${totalInvalid} invalid of ${totalValid + totalInvalid} total\n${errors.join('\n')}`);
1601
+ }
1602
+ catch (err) {
1603
+ return createFailureOutput(VALIDATION_TOOL_ERROR_CODES.VALIDATE_ERROR, err.message);
1604
+ }
1605
+ };
1606
+ /**
1607
+ * validate:agent-skills handler — scans skill YAML files for required fields
1608
+ */
1609
+ const validateAgentSkillsInProcess = async (rawInput, context) => {
1610
+ const parsedInput = VALIDATE_AGENT_SKILLS_INPUT_SCHEMA.safeParse(rawInput ?? {});
1611
+ if (!parsedInput.success) {
1612
+ return createFailureOutput(VALIDATION_TOOL_ERROR_CODES.INVALID_INPUT, parsedInput.error.message);
1613
+ }
1614
+ try {
1615
+ const projectRoot = resolveWorkspaceRoot(context);
1616
+ const skillsDir = path.join(projectRoot, SKILLS_DIR_RELATIVE);
1617
+ let skillFiles;
1618
+ try {
1619
+ skillFiles = (await readdir(skillsDir)).filter(hasValidationExtension);
1620
+ }
1621
+ catch {
1622
+ return createSuccessOutput({ message: VALIDATION_TOOL_MESSAGES.NO_SKILLS_DIR, valid: true });
1623
+ }
1624
+ if (parsedInput.data.skill) {
1625
+ skillFiles = skillFiles.filter((f) => f.includes(parsedInput.data.skill));
1626
+ }
1627
+ const issues = [];
1628
+ for (const file of skillFiles) {
1629
+ const content = await readFile(path.join(skillsDir, file), UTF8_ENCODING);
1630
+ if (content.trim().length === 0) {
1631
+ issues.push(`${file}: ${VALIDATION_TOOL_MESSAGES.EMPTY_FILE}`);
1632
+ }
1633
+ }
1634
+ if (issues.length > 0) {
1635
+ return createFailureOutput(VALIDATION_TOOL_ERROR_CODES.VALIDATE_AGENT_SKILLS_ERROR, `${VALIDATION_TOOL_MESSAGES.AGENT_SKILLS_FAILED}:\n${issues.join('\n')}`);
1636
+ }
1637
+ return createSuccessOutput({
1638
+ message: validationCountMsg('Agent skills valid', skillFiles.length),
1639
+ valid: true,
1640
+ count: skillFiles.length,
1641
+ });
1642
+ }
1643
+ catch (err) {
1644
+ return createFailureOutput(VALIDATION_TOOL_ERROR_CODES.VALIDATE_AGENT_SKILLS_ERROR, err.message);
1645
+ }
1646
+ };
1647
+ /**
1648
+ * validate:agent-sync handler — checks agent config files are in sync
1649
+ */
1650
+ const validateAgentSyncInProcess = async (_rawInput, context) => {
1651
+ try {
1652
+ const projectRoot = resolveWorkspaceRoot(context);
1653
+ const agentsDir = path.join(projectRoot, AGENTS_DIR_RELATIVE);
1654
+ let agentFiles;
1655
+ try {
1656
+ agentFiles = (await readdir(agentsDir)).filter(hasValidationExtension);
1657
+ }
1658
+ catch {
1659
+ return createSuccessOutput({ message: VALIDATION_TOOL_MESSAGES.NO_AGENTS_DIR, valid: true });
1660
+ }
1661
+ const issues = [];
1662
+ for (const file of agentFiles) {
1663
+ const content = await readFile(path.join(agentsDir, file), UTF8_ENCODING);
1664
+ if (content.trim().length === 0) {
1665
+ issues.push(`${file}: ${VALIDATION_TOOL_MESSAGES.EMPTY_AGENT_CONFIG}`);
1666
+ }
1667
+ }
1668
+ if (issues.length > 0) {
1669
+ return createFailureOutput(VALIDATION_TOOL_ERROR_CODES.VALIDATE_AGENT_SYNC_ERROR, `${VALIDATION_TOOL_MESSAGES.AGENT_SYNC_FAILED}:\n${issues.join('\n')}`);
1670
+ }
1671
+ return createSuccessOutput({
1672
+ message: validationCountMsg('Agent sync valid', agentFiles.length),
1673
+ valid: true,
1674
+ count: agentFiles.length,
1675
+ });
1676
+ }
1677
+ catch (err) {
1678
+ return createFailureOutput(VALIDATION_TOOL_ERROR_CODES.VALIDATE_AGENT_SYNC_ERROR, err.message);
1679
+ }
1680
+ };
1681
+ /**
1682
+ * validate:backlog-sync handler — delegates to @lumenflow/core validateBacklogSync
1683
+ */
1684
+ const validateBacklogSyncInProcess = async (_rawInput, context) => {
1685
+ try {
1686
+ const core = await getCoreLazy();
1687
+ const projectRoot = resolveWorkspaceRoot(context);
1688
+ const config = core.getConfig({ projectRoot });
1689
+ const backlogFilePath = path.join(projectRoot, config.directories.backlogPath);
1690
+ const result = core.validateBacklogSync(backlogFilePath);
1691
+ if (result.valid) {
1692
+ return createSuccessOutput({
1693
+ message: VALIDATION_TOOL_MESSAGES.BACKLOG_SYNC_VALID,
1694
+ ...result,
1695
+ });
1696
+ }
1697
+ return createFailureOutput(VALIDATION_TOOL_ERROR_CODES.VALIDATE_BACKLOG_SYNC_ERROR, result.errors?.join('\n') ?? VALIDATION_TOOL_MESSAGES.BACKLOG_SYNC_FAILED);
1698
+ }
1699
+ catch (err) {
1700
+ return createFailureOutput(VALIDATION_TOOL_ERROR_CODES.VALIDATE_BACKLOG_SYNC_ERROR, err.message);
1701
+ }
1702
+ };
1703
+ /**
1704
+ * validate:skills-spec handler — validates skills specification YAML files
1705
+ */
1706
+ const validateSkillsSpecInProcess = async (_rawInput, context) => {
1707
+ try {
1708
+ const projectRoot = resolveWorkspaceRoot(context);
1709
+ const skillsDir = path.join(projectRoot, SKILLS_DIR_RELATIVE);
1710
+ let skillFiles;
1711
+ try {
1712
+ skillFiles = (await readdir(skillsDir)).filter(hasValidationExtension);
1713
+ }
1714
+ catch {
1715
+ return createSuccessOutput({ message: VALIDATION_TOOL_MESSAGES.NO_SKILLS_DIR, valid: true });
1716
+ }
1717
+ const issues = [];
1718
+ for (const file of skillFiles) {
1719
+ const filePath = path.join(skillsDir, file);
1720
+ const fileStat = await stat(filePath);
1721
+ if (fileStat.size === 0) {
1722
+ issues.push(`${file}: ${VALIDATION_TOOL_MESSAGES.EMPTY_SKILLS_SPEC}`);
1723
+ }
1724
+ }
1725
+ if (issues.length > 0) {
1726
+ return createFailureOutput(VALIDATION_TOOL_ERROR_CODES.VALIDATE_SKILLS_SPEC_ERROR, `${VALIDATION_TOOL_MESSAGES.SKILLS_SPEC_FAILED}:\n${issues.join('\n')}`);
1727
+ }
1728
+ return createSuccessOutput({
1729
+ message: validationCountMsg('Skills spec valid', skillFiles.length),
1730
+ valid: true,
1731
+ count: skillFiles.length,
1732
+ });
1733
+ }
1734
+ catch (err) {
1735
+ return createFailureOutput(VALIDATION_TOOL_ERROR_CODES.VALIDATE_SKILLS_SPEC_ERROR, err.message);
1736
+ }
1737
+ };
1738
+ /**
1739
+ * lane:health handler — delegates to @lumenflow/core lane checker
1740
+ */
1741
+ const laneHealthInProcess = async (rawInput, context) => {
1742
+ const parsedInput = LANE_HEALTH_INPUT_SCHEMA.safeParse(rawInput ?? {});
1743
+ if (!parsedInput.success) {
1744
+ return createFailureOutput(VALIDATION_TOOL_ERROR_CODES.INVALID_INPUT, parsedInput.error.message);
1745
+ }
1746
+ try {
1747
+ const core = await getCoreLazy();
1748
+ const projectRoot = resolveWorkspaceRoot(context);
1749
+ const config = core.getConfig({ projectRoot });
1750
+ const laneConfigMap = resolveLanePolicyConfig(config);
1751
+ const allWUs = await core.listWUs({ projectRoot });
1752
+ const laneOccupancy = {};
1753
+ for (const wu of allWUs) {
1754
+ if (!wu.lane)
1755
+ continue;
1756
+ const existing = laneOccupancy[wu.lane];
1757
+ const entry = existing ?? { in_progress: [], blocked: [] };
1758
+ if (!existing) {
1759
+ laneOccupancy[wu.lane] = entry;
1760
+ }
1761
+ if (wu.status === STATUS_IN_PROGRESS) {
1762
+ entry.in_progress.push(wu.id);
1763
+ }
1764
+ else if (wu.status === STATUS_BLOCKED) {
1765
+ entry.blocked.push(wu.id);
1766
+ }
1767
+ }
1768
+ const overlaps = [];
1769
+ const lanes = Object.keys(laneConfigMap);
1770
+ for (const lane of lanes) {
1771
+ const occupancy = laneOccupancy[lane];
1772
+ const lanePolicy = laneConfigMap[lane];
1773
+ if (!occupancy || !lanePolicy)
1774
+ continue;
1775
+ if (lanePolicy.lockPolicy !== LOCK_POLICY_NONE &&
1776
+ occupancy.in_progress.length > lanePolicy.wipLimit) {
1777
+ overlaps.push(`${lane}: ${occupancy.in_progress.length} in-progress (limit ${lanePolicy.wipLimit})`);
1778
+ }
1779
+ }
1780
+ const healthResult = {
1781
+ lanes: Object.keys(laneConfigMap).length,
1782
+ occupied: Object.keys(laneOccupancy).length,
1783
+ overlaps,
1784
+ lane_details: Object.entries(laneConfigMap).map(([name, cfg]) => ({
1785
+ name,
1786
+ policy: cfg.lockPolicy,
1787
+ wip_limit: cfg.wipLimit,
1788
+ in_progress: laneOccupancy[name]?.in_progress.length ?? 0,
1789
+ blocked: laneOccupancy[name]?.blocked.length ?? 0,
1790
+ })),
1791
+ };
1792
+ if (overlaps.length > 0) {
1793
+ return createFailureOutput(VALIDATION_TOOL_ERROR_CODES.LANE_HEALTH_ERROR, overlaps.join('\n'));
1794
+ }
1795
+ return createSuccessOutput({
1796
+ message: VALIDATION_TOOL_MESSAGES.LANE_HEALTH_PASSED,
1797
+ ...healthResult,
1798
+ });
1799
+ }
1800
+ catch (err) {
1801
+ return createFailureOutput(VALIDATION_TOOL_ERROR_CODES.LANE_HEALTH_ERROR, err.message);
1802
+ }
1803
+ };
1804
+ /**
1805
+ * lane:suggest handler — generates lane suggestions from codebase context
1806
+ */
1807
+ const laneSuggestInProcess = async (rawInput, context) => {
1808
+ const parsedInput = LANE_SUGGEST_INPUT_SCHEMA.safeParse(rawInput ?? {});
1809
+ if (!parsedInput.success) {
1810
+ return createFailureOutput(VALIDATION_TOOL_ERROR_CODES.INVALID_INPUT, parsedInput.error.message);
1811
+ }
1812
+ try {
1813
+ const core = await getCoreLazy();
1814
+ const projectRoot = resolveWorkspaceRoot(context);
1815
+ const config = core.getConfig({ projectRoot });
1816
+ const laneConfigMap = resolveLanePolicyConfig(config);
1817
+ const existingLanes = Object.keys(laneConfigMap);
1818
+ // WU-1856: Use core's real filesystem scanner instead of empty stub.
1819
+ // Override existingLanes with policy-resolved lanes (more authoritative).
1820
+ const projectContext = core.gatherProjectContext(projectRoot);
1821
+ projectContext.existingLanes = existingLanes;
1822
+ const suggestions = core.getDefaultSuggestions(projectContext);
1823
+ return createSuccessOutput({
1824
+ message: validationCountMsg('Lane suggestions generated', suggestions.length),
1825
+ suggestions,
1826
+ existing_lanes: existingLanes,
1827
+ });
1828
+ }
1829
+ catch (err) {
1830
+ return createFailureOutput(VALIDATION_TOOL_ERROR_CODES.LANE_SUGGEST_ERROR, err.message);
1831
+ }
1832
+ };
1833
+ /**
1834
+ * WU-1805: WU query in-process handlers
1835
+ */
1836
+ const WU_QUERY_MESSAGES = {
1837
+ ID_REQUIRED: 'id parameter is required',
1838
+ RUNTIME_CLI_FALLBACK: 'Runtime in-process path not available; falling back to CLI',
1839
+ STATUS_FAILED: 'wu:status failed',
1840
+ PREFLIGHT_PASSED: 'Preflight checks passed',
1841
+ PREFLIGHT_FAILED: 'wu:preflight failed',
1842
+ VALIDATE_PASSED: 'WU is valid',
1843
+ VALIDATE_FAILED: 'wu:validate failed',
1844
+ INFER_LANE_FAILED: 'wu:infer-lane failed',
1845
+ };
1846
+ const lumenflowInProcess = async () => createFailureOutput(VALIDATION_TOOL_ERROR_CODES.LUMENFLOW_INIT_ERROR, WU_QUERY_MESSAGES.RUNTIME_CLI_FALLBACK);
1847
+ const lumenflowDoctorInProcess = async () => createFailureOutput(VALIDATION_TOOL_ERROR_CODES.LUMENFLOW_DOCTOR_ERROR, WU_QUERY_MESSAGES.RUNTIME_CLI_FALLBACK);
1848
+ const lumenflowIntegrateInProcess = async () => createFailureOutput(VALIDATION_TOOL_ERROR_CODES.LUMENFLOW_INTEGRATE_ERROR, WU_QUERY_MESSAGES.RUNTIME_CLI_FALLBACK);
1849
+ const lumenflowUpgradeInProcess = async () => createFailureOutput(VALIDATION_TOOL_ERROR_CODES.LUMENFLOW_UPGRADE_ERROR, WU_QUERY_MESSAGES.RUNTIME_CLI_FALLBACK);
1850
+ const lumenflowReleaseInProcess = async () => createFailureOutput(VALIDATION_TOOL_ERROR_CODES.LUMENFLOW_RELEASE_ERROR, WU_QUERY_MESSAGES.RUNTIME_CLI_FALLBACK);
1851
+ const docsSyncInProcess = async () => createFailureOutput(VALIDATION_TOOL_ERROR_CODES.DOCS_SYNC_ERROR, WU_QUERY_MESSAGES.RUNTIME_CLI_FALLBACK);
1852
+ const syncTemplatesInProcess = async () => createFailureOutput(VALIDATION_TOOL_ERROR_CODES.SYNC_TEMPLATES_ALIAS_ERROR, WU_QUERY_MESSAGES.RUNTIME_CLI_FALLBACK);
1853
+ const planCreateInProcess = async () => createFailureOutput(VALIDATION_TOOL_ERROR_CODES.PLAN_CREATE_ERROR, WU_QUERY_MESSAGES.RUNTIME_CLI_FALLBACK);
1854
+ const planEditInProcess = async () => createFailureOutput(VALIDATION_TOOL_ERROR_CODES.PLAN_EDIT_ERROR, WU_QUERY_MESSAGES.RUNTIME_CLI_FALLBACK);
1855
+ const planLinkInProcess = async () => createFailureOutput(VALIDATION_TOOL_ERROR_CODES.PLAN_LINK_ERROR, WU_QUERY_MESSAGES.RUNTIME_CLI_FALLBACK);
1856
+ const planPromoteInProcess = async () => createFailureOutput(VALIDATION_TOOL_ERROR_CODES.PLAN_PROMOTE_ERROR, WU_QUERY_MESSAGES.RUNTIME_CLI_FALLBACK);
1857
+ const initiativeListInProcess = async () => createFailureOutput(VALIDATION_TOOL_ERROR_CODES.INITIATIVE_LIST_ERROR, WU_QUERY_MESSAGES.RUNTIME_CLI_FALLBACK);
1858
+ const initiativeStatusInProcess = async () => createFailureOutput(VALIDATION_TOOL_ERROR_CODES.INITIATIVE_STATUS_ERROR, WU_QUERY_MESSAGES.RUNTIME_CLI_FALLBACK);
1859
+ const initiativeCreateInProcess = async () => createFailureOutput(VALIDATION_TOOL_ERROR_CODES.INITIATIVE_CREATE_ERROR, WU_QUERY_MESSAGES.RUNTIME_CLI_FALLBACK);
1860
+ const initiativeEditInProcess = async () => createFailureOutput(VALIDATION_TOOL_ERROR_CODES.INITIATIVE_EDIT_ERROR, WU_QUERY_MESSAGES.RUNTIME_CLI_FALLBACK);
1861
+ const initiativeAddWuInProcess = async () => createFailureOutput(VALIDATION_TOOL_ERROR_CODES.INITIATIVE_ADD_WU_ERROR, WU_QUERY_MESSAGES.RUNTIME_CLI_FALLBACK);
1862
+ const initiativeRemoveWuInProcess = async () => createFailureOutput(VALIDATION_TOOL_ERROR_CODES.INITIATIVE_REMOVE_WU_ERROR, WU_QUERY_MESSAGES.RUNTIME_CLI_FALLBACK);
1863
+ const initiativeBulkAssignInProcess = async () => createFailureOutput(VALIDATION_TOOL_ERROR_CODES.INITIATIVE_BULK_ASSIGN_ERROR, WU_QUERY_MESSAGES.RUNTIME_CLI_FALLBACK);
1864
+ const initiativePlanInProcess = async () => createFailureOutput(VALIDATION_TOOL_ERROR_CODES.INITIATIVE_PLAN_ERROR, WU_QUERY_MESSAGES.RUNTIME_CLI_FALLBACK);
1865
+ const initPlanInProcess = async () => createFailureOutput(VALIDATION_TOOL_ERROR_CODES.INIT_PLAN_ERROR, WU_QUERY_MESSAGES.RUNTIME_CLI_FALLBACK);
1866
+ const orchestrateInitiativeInProcess = async () => createFailureOutput(VALIDATION_TOOL_ERROR_CODES.ORCHESTRATE_INITIATIVE_ERROR, WU_QUERY_MESSAGES.RUNTIME_CLI_FALLBACK);
1867
+ const wuInferLaneInProcess = async (rawInput, context) => {
1868
+ const input = (rawInput ?? {});
1869
+ try {
1870
+ const core = await getCoreLazy();
1871
+ const projectRoot = resolveWorkspaceRoot(context);
1872
+ let codePaths = [];
1873
+ let description = '';
1874
+ if (Array.isArray(input.paths)) {
1875
+ codePaths = input.paths.filter((p) => typeof p === 'string');
1876
+ }
1877
+ if (typeof input.desc === 'string') {
1878
+ description = input.desc;
1879
+ }
1880
+ // If id provided and no explicit paths, read from WU YAML
1881
+ if (typeof input.id === 'string' && codePaths.length === 0) {
1882
+ const wuFile = path.join(projectRoot, 'docs/04-operations/tasks/wu', `${input.id}.yaml`);
1883
+ try {
1884
+ const content = await readFile(wuFile, UTF8_ENCODING);
1885
+ const parsed = core.parseYAML(content);
1886
+ if (parsed && typeof parsed === 'object') {
1887
+ const wuData = parsed;
1888
+ if (Array.isArray(wuData.code_paths)) {
1889
+ codePaths = wuData.code_paths.filter((p) => typeof p === 'string');
1890
+ }
1891
+ if (!description && typeof wuData.description === 'string') {
1892
+ description = wuData.description;
1893
+ }
1894
+ }
1895
+ }
1896
+ catch {
1897
+ // WU file not found or unreadable — continue with provided inputs
1898
+ }
1899
+ }
1900
+ const result = core.inferSubLane(codePaths, description);
1901
+ return createSuccessOutput({ lane: result.lane, confidence: result.confidence });
1902
+ }
1903
+ catch (err) {
1904
+ return createFailureOutput(VALIDATION_TOOL_ERROR_CODES.WU_INFER_LANE_ERROR, err.message);
1905
+ }
1906
+ };
1907
+ // WU-1897: These in-process implementations are retained temporarily for
1908
+ // reference parity but are intentionally removed from resolver registration.
1909
+ const retiredWu1897InProcessHandlers = [
1910
+ lumenflowInProcess,
1911
+ lumenflowDoctorInProcess,
1912
+ lumenflowIntegrateInProcess,
1913
+ lumenflowUpgradeInProcess,
1914
+ lumenflowReleaseInProcess,
1915
+ docsSyncInProcess,
1916
+ syncTemplatesInProcess,
1917
+ planCreateInProcess,
1918
+ planEditInProcess,
1919
+ planLinkInProcess,
1920
+ planPromoteInProcess,
1921
+ initiativeListInProcess,
1922
+ initiativeStatusInProcess,
1923
+ initiativeCreateInProcess,
1924
+ initiativeEditInProcess,
1925
+ initiativeAddWuInProcess,
1926
+ initiativeRemoveWuInProcess,
1927
+ initiativeBulkAssignInProcess,
1928
+ initiativePlanInProcess,
1929
+ initPlanInProcess,
1930
+ orchestrateInitiativeInProcess,
1931
+ orchestrateInitStatusInProcess,
1932
+ orchestrateMonitorInProcess,
1933
+ delegationListInProcess,
1934
+ ];
1935
+ void retiredWu1897InProcessHandlers;
1936
+ // WU-1890: Remaining file/git/state/validation/lane surfaces are now migrated to
1937
+ // pack handler implementations. Keep legacy implementations for reference parity.
1938
+ const retiredWu1890InProcessHandlers = [
1939
+ wuInferLaneInProcess,
1940
+ fileReadInProcess,
1941
+ fileWriteInProcess,
1942
+ fileEditInProcess,
1943
+ fileDeleteInProcess,
1944
+ backlogPruneInProcess,
1945
+ stateBootstrapInProcess,
1946
+ stateCleanupInProcess,
1947
+ stateDoctorInProcess,
1948
+ signalCleanupInProcess,
1949
+ validateInProcess,
1950
+ validateAgentSkillsInProcess,
1951
+ validateAgentSyncInProcess,
1952
+ validateBacklogSyncInProcess,
1953
+ validateSkillsSpecInProcess,
1954
+ laneHealthInProcess,
1955
+ laneSuggestInProcess,
1956
+ ];
1957
+ void retiredWu1890InProcessHandlers;
1958
+ const retiredWu1890InProcessSchemas = [
1959
+ DEFAULT_IN_PROCESS_OUTPUT_SCHEMA,
1960
+ VALIDATE_INPUT_SCHEMA,
1961
+ FILE_READ_INPUT_SCHEMA,
1962
+ FILE_READ_OUTPUT_SCHEMA,
1963
+ FILE_WRITE_INPUT_SCHEMA,
1964
+ FILE_WRITE_OUTPUT_SCHEMA,
1965
+ FILE_EDIT_INPUT_SCHEMA,
1966
+ FILE_EDIT_OUTPUT_SCHEMA,
1967
+ FILE_DELETE_INPUT_SCHEMA,
1968
+ FILE_DELETE_OUTPUT_SCHEMA,
1969
+ BACKLOG_PRUNE_INPUT_SCHEMA,
1970
+ STATE_BOOTSTRAP_INPUT_SCHEMA,
1971
+ STATE_CLEANUP_INPUT_SCHEMA,
1972
+ STATE_DOCTOR_INPUT_SCHEMA,
1973
+ SIGNAL_CLEANUP_INPUT_SCHEMA,
1974
+ VALIDATE_AGENT_SKILLS_INPUT_SCHEMA,
1975
+ VALIDATE_AGENT_SYNC_INPUT_SCHEMA,
1976
+ VALIDATE_BACKLOG_SYNC_INPUT_SCHEMA,
1977
+ VALIDATE_SKILLS_SPEC_INPUT_SCHEMA,
1978
+ LANE_HEALTH_INPUT_SCHEMA,
1979
+ LANE_SUGGEST_INPUT_SCHEMA,
1980
+ ];
1981
+ void retiredWu1890InProcessSchemas;
1982
+ const registeredInProcessToolHandlers = new Map([
1983
+ // WU-1905: flow:bottlenecks, flow:report, metrics:snapshot, metrics, and lumenflow:metrics
1984
+ // have been migrated to pack handler implementations. Their resolver registrations
1985
+ // are removed; they now execute through the pack handler path.
1986
+ [
1987
+ 'context:get',
1988
+ {
1989
+ description: 'Get current LumenFlow context via in-process computation',
1990
+ inputSchema: DEFAULT_IN_PROCESS_INPUT_SCHEMA,
1991
+ fn: contextGetHandler,
1992
+ },
1993
+ ],
1994
+ [
1995
+ 'wu:list',
1996
+ {
1997
+ description: 'List WUs via in-process state store + YAML merge',
1998
+ inputSchema: DEFAULT_IN_PROCESS_INPUT_SCHEMA,
1999
+ fn: wuListHandler,
2000
+ },
2001
+ ],
2002
+ ]);
2003
+ export function isInProcessPackToolRegistered(toolName) {
2004
+ return registeredInProcessToolHandlers.has(toolName);
2005
+ }
2006
+ export function listInProcessPackTools() {
2007
+ return [...registeredInProcessToolHandlers.keys()].sort();
2008
+ }
2009
+ export const packToolCapabilityResolver = async (input) => {
2010
+ const registeredHandler = registeredInProcessToolHandlers.get(input.tool.name);
2011
+ if (!registeredHandler) {
2012
+ return defaultRuntimeToolCapabilityResolver(input);
2013
+ }
2014
+ return {
2015
+ name: input.tool.name,
2016
+ domain: input.loadedPack.manifest.id,
2017
+ version: input.loadedPack.manifest.version,
2018
+ input_schema: registeredHandler.inputSchema,
2019
+ output_schema: registeredHandler.outputSchema,
2020
+ permission: input.tool.permission,
2021
+ required_scopes: input.tool.required_scopes,
2022
+ handler: {
2023
+ kind: TOOL_HANDLER_KINDS.IN_PROCESS,
2024
+ fn: registeredHandler.fn,
2025
+ },
2026
+ description: registeredHandler.description,
2027
+ pack: input.loadedPack.pin.id,
2028
+ };
2029
+ };
2030
+ //# sourceMappingURL=runtime-tool-resolver.js.map