@lumenflow/cli 5.5.0 → 5.7.12

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 (213) hide show
  1. package/README.md +42 -40
  2. package/dist/db-journal-recover.js +400 -0
  3. package/dist/db-journal-recover.js.map +1 -0
  4. package/dist/docs-sync.js +8 -3
  5. package/dist/docs-sync.js.map +1 -1
  6. package/dist/gate-defaults.js +37 -0
  7. package/dist/gate-defaults.js.map +1 -1
  8. package/dist/gates/monolithic-file-contention-guard.js +167 -0
  9. package/dist/gates/monolithic-file-contention-guard.js.map +1 -0
  10. package/dist/gates/prod-migration-drift.js +207 -0
  11. package/dist/gates/prod-migration-drift.js.map +1 -0
  12. package/dist/gates/test-over-deletion-guard.js +255 -0
  13. package/dist/gates/test-over-deletion-guard.js.map +1 -0
  14. package/dist/gates-runners.js +44 -3
  15. package/dist/gates-runners.js.map +1 -1
  16. package/dist/gates.js +3 -2
  17. package/dist/gates.js.map +1 -1
  18. package/dist/lumenflow-setup.js +144 -0
  19. package/dist/lumenflow-setup.js.map +1 -0
  20. package/dist/lumenflow-upgrade.js +2 -1
  21. package/dist/lumenflow-upgrade.js.map +1 -1
  22. package/dist/mem-create.js +10 -1
  23. package/dist/mem-create.js.map +1 -1
  24. package/dist/mem-signal.js +21 -4
  25. package/dist/mem-signal.js.map +1 -1
  26. package/dist/orchestrate-initiative.js +28 -3
  27. package/dist/orchestrate-initiative.js.map +1 -1
  28. package/dist/public-manifest.js +17 -0
  29. package/dist/public-manifest.js.map +1 -1
  30. package/dist/release.js +53 -18
  31. package/dist/release.js.map +1 -1
  32. package/dist/wu-done-gates.js +13 -9
  33. package/dist/wu-done-gates.js.map +1 -1
  34. package/dist/wu-edit-operations.js +74 -0
  35. package/dist/wu-edit-operations.js.map +1 -1
  36. package/dist/wu-edit-validators.js +58 -0
  37. package/dist/wu-edit-validators.js.map +1 -1
  38. package/dist/wu-edit.js +106 -4
  39. package/dist/wu-edit.js.map +1 -1
  40. package/dist/wu-prep.js +41 -7
  41. package/dist/wu-prep.js.map +1 -1
  42. package/dist/wu-recover.js +6 -0
  43. package/dist/wu-recover.js.map +1 -1
  44. package/dist/wu-release.js +120 -2
  45. package/dist/wu-release.js.map +1 -1
  46. package/dist/wu-sizing-validation.js +47 -17
  47. package/dist/wu-sizing-validation.js.map +1 -1
  48. package/dist/wu-status.js +33 -0
  49. package/dist/wu-status.js.map +1 -1
  50. package/package.json +13 -11
  51. package/packs/agent-runtime/package.json +1 -1
  52. package/packs/sidekick/package.json +1 -1
  53. package/packs/software-delivery/package.json +1 -1
  54. package/templates/core/AGENTS.md.template +67 -3
  55. package/templates/core/LUMENFLOW.md.template +197 -47
  56. package/packs/agent-runtime/agent-heartbeat.ts +0 -163
  57. package/packs/agent-runtime/auto-session-integration.ts +0 -888
  58. package/packs/agent-runtime/capability-factory.ts +0 -104
  59. package/packs/agent-runtime/constants.ts +0 -21
  60. package/packs/agent-runtime/delegation-registry-schema.ts +0 -220
  61. package/packs/agent-runtime/delegation-registry-store.ts +0 -269
  62. package/packs/agent-runtime/delegation-tree.ts +0 -328
  63. package/packs/agent-runtime/index.ts +0 -20
  64. package/packs/agent-runtime/manifest.ts +0 -348
  65. package/packs/agent-runtime/memory-coordination-contract.ts +0 -86
  66. package/packs/agent-runtime/orchestration.ts +0 -2027
  67. package/packs/agent-runtime/pack-registration.ts +0 -110
  68. package/packs/agent-runtime/policy-factory.ts +0 -165
  69. package/packs/agent-runtime/remote-controls/index.ts +0 -7
  70. package/packs/agent-runtime/remote-controls/operations.ts +0 -405
  71. package/packs/agent-runtime/remote-controls/port.ts +0 -48
  72. package/packs/agent-runtime/remote-controls/state-store.ts +0 -258
  73. package/packs/agent-runtime/remote-controls/types.ts +0 -105
  74. package/packs/agent-runtime/session-schema.ts +0 -467
  75. package/packs/agent-runtime/tool-impl/agent-turn-tools.ts +0 -793
  76. package/packs/agent-runtime/tool-impl/index.ts +0 -6
  77. package/packs/agent-runtime/tool-impl/provider-adapters.ts +0 -1245
  78. package/packs/agent-runtime/tool-impl/remote-controls.mock.ts +0 -256
  79. package/packs/agent-runtime/tool-impl/remote-controls.ts +0 -273
  80. package/packs/agent-runtime/tools/index.ts +0 -4
  81. package/packs/agent-runtime/tools/types.ts +0 -47
  82. package/packs/agent-runtime/turn-lifecycle-events.ts +0 -590
  83. package/packs/agent-runtime/types.ts +0 -128
  84. package/packs/agent-runtime/vitest.config.ts +0 -11
  85. package/packs/sidekick/channel-ingress.ts +0 -137
  86. package/packs/sidekick/constants.ts +0 -10
  87. package/packs/sidekick/index.ts +0 -8
  88. package/packs/sidekick/manifest-schema.ts +0 -49
  89. package/packs/sidekick/manifest.ts +0 -512
  90. package/packs/sidekick/pack-registration.ts +0 -110
  91. package/packs/sidekick/policy-factory.ts +0 -38
  92. package/packs/sidekick/sidekick-events.ts +0 -694
  93. package/packs/sidekick/src/adapters/cloud-queue.ts +0 -101
  94. package/packs/sidekick/src/adapters/control-plane-bridge.adapter.ts +0 -386
  95. package/packs/sidekick/src/adapters/filesystem-bridge.adapter.ts +0 -228
  96. package/packs/sidekick/src/domain/channel.types.ts +0 -64
  97. package/packs/sidekick/src/ports/channel-bridge.port.ts +0 -92
  98. package/packs/sidekick/src/routines/commit.ts +0 -74
  99. package/packs/sidekick/tool-impl/channel-tools.ts +0 -577
  100. package/packs/sidekick/tool-impl/channel-transports.ts +0 -75
  101. package/packs/sidekick/tool-impl/index.ts +0 -29
  102. package/packs/sidekick/tool-impl/memory-tools.ts +0 -290
  103. package/packs/sidekick/tool-impl/routine-commit.ts +0 -102
  104. package/packs/sidekick/tool-impl/routine-tools.ts +0 -440
  105. package/packs/sidekick/tool-impl/runtime-context.ts +0 -28
  106. package/packs/sidekick/tool-impl/shared.ts +0 -125
  107. package/packs/sidekick/tool-impl/storage.ts +0 -325
  108. package/packs/sidekick/tool-impl/system-tools.ts +0 -160
  109. package/packs/sidekick/tool-impl/task-tools.ts +0 -506
  110. package/packs/sidekick/tools/channel-tools.ts +0 -53
  111. package/packs/sidekick/tools/index.ts +0 -9
  112. package/packs/sidekick/tools/memory-tools.ts +0 -53
  113. package/packs/sidekick/tools/routine-tools.ts +0 -53
  114. package/packs/sidekick/tools/system-tools.ts +0 -47
  115. package/packs/sidekick/tools/task-tools.ts +0 -61
  116. package/packs/sidekick/tools/types.ts +0 -57
  117. package/packs/sidekick/vitest.config.ts +0 -11
  118. package/packs/software-delivery/constants.ts +0 -10
  119. package/packs/software-delivery/extensions.ts +0 -140
  120. package/packs/software-delivery/gate-policies.ts +0 -134
  121. package/packs/software-delivery/index.ts +0 -8
  122. package/packs/software-delivery/manifest-schema.ts +0 -268
  123. package/packs/software-delivery/manifest.ts +0 -657
  124. package/packs/software-delivery/pack-registration.ts +0 -113
  125. package/packs/software-delivery/src/commands/index.ts +0 -5
  126. package/packs/software-delivery/src/config/delivery-review-contract.ts +0 -256
  127. package/packs/software-delivery/src/config/env-accessors.ts +0 -66
  128. package/packs/software-delivery/src/config/index.ts +0 -8
  129. package/packs/software-delivery/src/config/normalize-config-keys.ts +0 -9
  130. package/packs/software-delivery/src/config/schemas/lumenflow-config-schema-types.ts +0 -460
  131. package/packs/software-delivery/src/config/workspace-reader.ts +0 -375
  132. package/packs/software-delivery/src/constants/backlog-patterns.ts +0 -31
  133. package/packs/software-delivery/src/constants/client-ids.ts +0 -19
  134. package/packs/software-delivery/src/constants/config-contract.ts +0 -7
  135. package/packs/software-delivery/src/constants/docs-layout-presets.ts +0 -50
  136. package/packs/software-delivery/src/constants/duration-constants.ts +0 -20
  137. package/packs/software-delivery/src/constants/gate-constants.ts +0 -32
  138. package/packs/software-delivery/src/constants/index.ts +0 -29
  139. package/packs/software-delivery/src/constants/lock-constants.ts +0 -35
  140. package/packs/software-delivery/src/constants/object-guards.ts +0 -12
  141. package/packs/software-delivery/src/constants/section-headings.ts +0 -107
  142. package/packs/software-delivery/src/constants/wu-cli-constants.ts +0 -500
  143. package/packs/software-delivery/src/constants/wu-domain-constants.ts +0 -466
  144. package/packs/software-delivery/src/constants/wu-git-constants.ts +0 -7
  145. package/packs/software-delivery/src/constants/wu-id-format.ts +0 -327
  146. package/packs/software-delivery/src/constants/wu-paths-constants.ts +0 -384
  147. package/packs/software-delivery/src/constants/wu-statuses.ts +0 -287
  148. package/packs/software-delivery/src/constants/wu-type-helpers.ts +0 -67
  149. package/packs/software-delivery/src/constants/wu-ui-constants.ts +0 -267
  150. package/packs/software-delivery/src/constants/wu-validation-constants.ts +0 -73
  151. package/packs/software-delivery/src/domain/index.ts +0 -5
  152. package/packs/software-delivery/src/domain/orchestration.constants.ts +0 -166
  153. package/packs/software-delivery/src/domain/orchestration.schemas.ts +0 -238
  154. package/packs/software-delivery/src/domain/orchestration.types.ts +0 -176
  155. package/packs/software-delivery/src/methodology/incremental-test.ts +0 -122
  156. package/packs/software-delivery/src/methodology/index.ts +0 -6
  157. package/packs/software-delivery/src/methodology/manual-test-validator.ts +0 -292
  158. package/packs/software-delivery/src/policy/coverage-gate.ts +0 -270
  159. package/packs/software-delivery/src/policy/gates-agent-mode.ts +0 -223
  160. package/packs/software-delivery/src/policy/gates-config-internal.ts +0 -121
  161. package/packs/software-delivery/src/policy/gates-config.ts +0 -300
  162. package/packs/software-delivery/src/policy/gates-coverage.ts +0 -356
  163. package/packs/software-delivery/src/policy/gates-presets.ts +0 -134
  164. package/packs/software-delivery/src/policy/gates-schemas.ts +0 -173
  165. package/packs/software-delivery/src/policy/index.ts +0 -22
  166. package/packs/software-delivery/src/policy/package-manager-resolver.ts +0 -319
  167. package/packs/software-delivery/src/policy/resolve-policy.ts +0 -601
  168. package/packs/software-delivery/src/ports/config.ports.ts +0 -90
  169. package/packs/software-delivery/src/ports/dashboard-renderer.port.ts +0 -125
  170. package/packs/software-delivery/src/ports/index.ts +0 -10
  171. package/packs/software-delivery/src/ports/sync-validator.ports.ts +0 -59
  172. package/packs/software-delivery/src/ports/wu-helpers.ports.ts +0 -168
  173. package/packs/software-delivery/src/ports/wu-state.ports.ts +0 -241
  174. package/packs/software-delivery/src/primitives/index.ts +0 -5
  175. package/packs/software-delivery/src/runtime/index.ts +0 -6
  176. package/packs/software-delivery/src/runtime/work-classifier.ts +0 -561
  177. package/packs/software-delivery/src/sandbox/index.ts +0 -10
  178. package/packs/software-delivery/src/sandbox/sandbox-allowlist.ts +0 -118
  179. package/packs/software-delivery/src/sandbox/sandbox-backend-linux.ts +0 -88
  180. package/packs/software-delivery/src/sandbox/sandbox-backend-macos.ts +0 -154
  181. package/packs/software-delivery/src/sandbox/sandbox-backend-windows.ts +0 -47
  182. package/packs/software-delivery/src/sandbox/sandbox-profile.ts +0 -153
  183. package/packs/software-delivery/src/schemas/index.ts +0 -5
  184. package/packs/software-delivery/src/state/date-utils.ts +0 -158
  185. package/packs/software-delivery/src/state/index.ts +0 -15
  186. package/packs/software-delivery/src/state/state-machine.ts +0 -119
  187. package/packs/software-delivery/src/state/wu-doc-types.ts +0 -51
  188. package/packs/software-delivery/src/state/wu-paths.ts +0 -381
  189. package/packs/software-delivery/src/state/wu-schema.ts +0 -1139
  190. package/packs/software-delivery/src/state/wu-state-schema.ts +0 -255
  191. package/packs/software-delivery/src/state/wu-yaml.ts +0 -338
  192. package/packs/software-delivery/tool-impl/agent-tools.ts +0 -263
  193. package/packs/software-delivery/tool-impl/delegation-tools.ts +0 -66
  194. package/packs/software-delivery/tool-impl/flow-metrics-tools.ts +0 -219
  195. package/packs/software-delivery/tool-impl/git-runner.ts +0 -113
  196. package/packs/software-delivery/tool-impl/git-tools.ts +0 -316
  197. package/packs/software-delivery/tool-impl/index.ts +0 -15
  198. package/packs/software-delivery/tool-impl/initiative-orchestration-tools.ts +0 -720
  199. package/packs/software-delivery/tool-impl/lane-lock.ts +0 -246
  200. package/packs/software-delivery/tool-impl/memory-tools.ts +0 -470
  201. package/packs/software-delivery/tool-impl/pending-runtime-tools.ts +0 -21
  202. package/packs/software-delivery/tool-impl/runtime-cli-adapter.ts +0 -329
  203. package/packs/software-delivery/tool-impl/runtime-native-tools.ts +0 -687
  204. package/packs/software-delivery/tool-impl/worker-loader.ts +0 -52
  205. package/packs/software-delivery/tool-impl/worktree-tools.ts +0 -46
  206. package/packs/software-delivery/tool-impl/wu-lifecycle-tools.ts +0 -807
  207. package/packs/software-delivery/tools/delegation-tools.ts +0 -23
  208. package/packs/software-delivery/tools/git-tools.ts +0 -55
  209. package/packs/software-delivery/tools/index.ts +0 -8
  210. package/packs/software-delivery/tools/lane-lock-tool.ts +0 -37
  211. package/packs/software-delivery/tools/types.ts +0 -71
  212. package/packs/software-delivery/tools/worktree-tools.ts +0 -49
  213. package/packs/software-delivery/vitest.config.ts +0 -11
@@ -1,440 +0,0 @@
1
- // Copyright (c) 2026 Hellmai Ltd
2
- // SPDX-License-Identifier: LicenseRef-LumenFlow-Proprietary
3
-
4
- import { getStoragePort, type RoutineRecord, type RoutineStepRecord } from './storage.js';
5
- import {
6
- asInteger,
7
- asNonEmptyString,
8
- buildAuditEvent,
9
- createId,
10
- failure,
11
- isDryRun,
12
- nowIso,
13
- success,
14
- toRecord,
15
- type ToolContextLike,
16
- type ToolOutput,
17
- } from './shared.js';
18
- import {
19
- buildRoutineCommittedEvent,
20
- buildRoutineExecutedEvent,
21
- buildRoutinePlannedEvent,
22
- buildRoutineStepFailedEvent,
23
- emitSidekickEvent,
24
- } from '../sidekick-events.js';
25
-
26
- // ---------------------------------------------------------------------------
27
- // Constants
28
- // ---------------------------------------------------------------------------
29
-
30
- const TOOL_NAMES = {
31
- CREATE: 'routine:create',
32
- LIST: 'routine:list',
33
- UPDATE: 'routine:update',
34
- DELETE: 'routine:delete',
35
- RUN: 'routine:run',
36
- } as const;
37
- const ROUTINE_ID_REQUIRED_MESSAGE = 'id is required.';
38
-
39
- // ---------------------------------------------------------------------------
40
- // Helpers
41
- // ---------------------------------------------------------------------------
42
-
43
- interface NormalizeStepsResult {
44
- steps: RoutineStepRecord[];
45
- warnings: string[];
46
- failed_steps: Array<{ step_index: number; reason: string }>;
47
- }
48
-
49
- function normalizeSteps(value: unknown): NormalizeStepsResult {
50
- if (!Array.isArray(value)) {
51
- return { steps: [], warnings: [], failed_steps: [] };
52
- }
53
-
54
- const steps: RoutineStepRecord[] = [];
55
- const warnings: string[] = [];
56
- const failedSteps: Array<{ step_index: number; reason: string }> = [];
57
-
58
- for (let i = 0; i < value.length; i++) {
59
- const candidate = value[i];
60
-
61
- // String shorthand: coerce "tool:name" → { tool: "tool:name", input: {} }
62
- if (typeof candidate === 'string') {
63
- const tool = asNonEmptyString(candidate);
64
- if (tool) {
65
- steps.push({ tool, input: {} });
66
- } else {
67
- const reason = `steps[${i}] invalid: expected non-empty string or object with "tool".`;
68
- warnings.push(reason);
69
- failedSteps.push({ step_index: i, reason });
70
- }
71
- continue;
72
- }
73
-
74
- if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
75
- const reason = `steps[${i}] invalid: expected object with "tool" or a tool name string.`;
76
- warnings.push(reason);
77
- failedSteps.push({ step_index: i, reason });
78
- continue;
79
- }
80
-
81
- const record = candidate as Record<string, unknown>;
82
- const tool = asNonEmptyString(record.tool);
83
- if (!tool) {
84
- const reason = `steps[${i}] invalid: missing or empty "tool" property.`;
85
- warnings.push(reason);
86
- failedSteps.push({ step_index: i, reason });
87
- continue;
88
- }
89
-
90
- const input =
91
- record.input && typeof record.input === 'object' && !Array.isArray(record.input)
92
- ? (record.input as Record<string, unknown>)
93
- : {};
94
-
95
- steps.push({ tool, input });
96
- }
97
-
98
- return { steps, warnings, failed_steps: failedSteps };
99
- }
100
-
101
- function asOptionalBoolean(value: unknown): boolean | null {
102
- if (value === true || value === false) {
103
- return value;
104
- }
105
- return null;
106
- }
107
-
108
- async function emitRoutineStepFailures(
109
- failedSteps: Array<{ step_index: number; reason: string }>,
110
- metadata: { routine_id?: string; routine_name?: string },
111
- ): Promise<void> {
112
- for (const failedStep of failedSteps) {
113
- await emitSidekickEvent(
114
- buildRoutineStepFailedEvent({
115
- step_index: failedStep.step_index,
116
- reason: failedStep.reason,
117
- ...(metadata.routine_id ? { routine_id: metadata.routine_id } : {}),
118
- ...(metadata.routine_name ? { routine_name: metadata.routine_name } : {}),
119
- }),
120
- );
121
- }
122
- }
123
-
124
- // ---------------------------------------------------------------------------
125
- // routine:create
126
- // ---------------------------------------------------------------------------
127
-
128
- async function routineCreateTool(input: unknown, context?: ToolContextLike): Promise<ToolOutput> {
129
- const parsed = toRecord(input);
130
- const name = asNonEmptyString(parsed.name);
131
- const { steps, warnings, failed_steps } = normalizeSteps(parsed.steps);
132
-
133
- if (!name) {
134
- return failure('INVALID_INPUT', 'name is required.');
135
- }
136
- if (steps.length === 0) {
137
- if (!isDryRun(parsed)) {
138
- await emitRoutineStepFailures(failed_steps, { routine_name: name ?? undefined });
139
- }
140
- const detail =
141
- warnings.length > 0
142
- ? `steps must include at least one tool step. Issues: ${warnings.join('; ')}`
143
- : 'steps must include at least one tool step.';
144
- return failure('INVALID_INPUT', detail);
145
- }
146
-
147
- const now = nowIso();
148
- const routine: RoutineRecord = {
149
- id: createId('routine'),
150
- name,
151
- steps,
152
- cron: asNonEmptyString(parsed.cron) ?? undefined,
153
- enabled: asOptionalBoolean(parsed.enabled) ?? true,
154
- created_at: now,
155
- updated_at: now,
156
- };
157
-
158
- if (isDryRun(parsed)) {
159
- return success({
160
- dry_run: true,
161
- routine: routine as unknown as Record<string, unknown>,
162
- ...(warnings.length > 0 ? { warnings } : {}),
163
- });
164
- }
165
-
166
- const storage = getStoragePort();
167
- await storage.withLock(async () => {
168
- const routines = await storage.readStore('routines');
169
- routines.push(routine);
170
- await storage.writeStore('routines', routines);
171
- await storage.appendAudit(
172
- buildAuditEvent({
173
- tool: TOOL_NAMES.CREATE,
174
- op: 'create',
175
- context,
176
- ids: [routine.id],
177
- }),
178
- );
179
- });
180
-
181
- await emitRoutineStepFailures(failed_steps, {
182
- routine_id: routine.id,
183
- routine_name: routine.name,
184
- });
185
- await emitSidekickEvent(buildRoutinePlannedEvent(routine));
186
-
187
- return success({
188
- routine: routine as unknown as Record<string, unknown>,
189
- ...(warnings.length > 0 ? { warnings } : {}),
190
- });
191
- }
192
-
193
- // ---------------------------------------------------------------------------
194
- // routine:list
195
- // ---------------------------------------------------------------------------
196
-
197
- async function routineListTool(input: unknown, _context?: ToolContextLike): Promise<ToolOutput> {
198
- const parsed = toRecord(input);
199
- const limit = asInteger(parsed.limit);
200
- const enabledOnly = parsed.enabled_only === true;
201
-
202
- const storage = getStoragePort();
203
- const routines = await storage.readStore('routines');
204
-
205
- const filtered = enabledOnly ? routines.filter((routine) => routine.enabled) : routines;
206
- const sorted = filtered.toSorted((a, b) => Date.parse(b.updated_at) - Date.parse(a.updated_at));
207
-
208
- const items = limit && limit > 0 ? sorted.slice(0, limit) : sorted;
209
-
210
- return success({
211
- items: items as unknown as Record<string, unknown>,
212
- count: items.length,
213
- });
214
- }
215
-
216
- // ---------------------------------------------------------------------------
217
- // routine:update
218
- // ---------------------------------------------------------------------------
219
-
220
- async function routineUpdateTool(input: unknown, context?: ToolContextLike): Promise<ToolOutput> {
221
- const parsed = toRecord(input);
222
- const id = asNonEmptyString(parsed.id);
223
- let patchFailedSteps: Array<{ step_index: number; reason: string }> = [];
224
-
225
- if (!id) {
226
- return failure('INVALID_INPUT', ROUTINE_ID_REQUIRED_MESSAGE);
227
- }
228
-
229
- const patch: Partial<RoutineRecord> = {};
230
- const name = asNonEmptyString(parsed.name);
231
- const cron =
232
- typeof parsed.cron === 'string' ? (asNonEmptyString(parsed.cron) ?? undefined) : null;
233
- const enabled = asOptionalBoolean(parsed.enabled);
234
-
235
- if (name) {
236
- patch.name = name;
237
- }
238
- if (cron !== null) {
239
- patch.cron = cron;
240
- }
241
- if (enabled !== null) {
242
- patch.enabled = enabled;
243
- }
244
- if (parsed.steps !== undefined) {
245
- const { steps, warnings, failed_steps } = normalizeSteps(parsed.steps);
246
- patchFailedSteps = failed_steps;
247
- if (steps.length === 0) {
248
- if (!isDryRun(parsed)) {
249
- await emitRoutineStepFailures(failed_steps, { routine_id: id });
250
- }
251
- const detail =
252
- warnings.length > 0
253
- ? `steps must include at least one tool step. Issues: ${warnings.join('; ')}`
254
- : 'steps must include at least one tool step.';
255
- return failure('INVALID_INPUT', detail);
256
- }
257
- patch.steps = steps;
258
- }
259
-
260
- if (Object.keys(patch).length === 0) {
261
- return failure(
262
- 'INVALID_INPUT',
263
- 'routine:update requires at least one of name, steps, cron, or enabled.',
264
- );
265
- }
266
-
267
- const storage = getStoragePort();
268
- const routines = await storage.readStore('routines');
269
- const routine = routines.find((entry) => entry.id === id);
270
-
271
- if (!routine) {
272
- return failure('NOT_FOUND', `routine ${id} was not found.`);
273
- }
274
-
275
- const preview: RoutineRecord = {
276
- ...routine,
277
- ...patch,
278
- updated_at: nowIso(),
279
- };
280
-
281
- if (isDryRun(parsed)) {
282
- return success({
283
- dry_run: true,
284
- routine: preview as unknown as Record<string, unknown>,
285
- });
286
- }
287
-
288
- await storage.withLock(async () => {
289
- const latest = await storage.readStore('routines');
290
- const target = latest.find((entry) => entry.id === id);
291
- if (!target) {
292
- return;
293
- }
294
- Object.assign(target, preview);
295
- await storage.writeStore('routines', latest);
296
- await storage.appendAudit(
297
- buildAuditEvent({
298
- tool: TOOL_NAMES.UPDATE,
299
- op: 'update',
300
- context,
301
- ids: [id],
302
- }),
303
- );
304
- });
305
-
306
- const updated = await storage.readStore('routines');
307
- const updatedRoutine = updated.find((entry) => entry.id === id);
308
- await emitRoutineStepFailures(patchFailedSteps, {
309
- routine_id: id,
310
- routine_name: updatedRoutine?.name,
311
- });
312
- if (updatedRoutine) {
313
- await emitSidekickEvent(buildRoutineCommittedEvent(updatedRoutine));
314
- }
315
- return success({ routine: updatedRoutine as unknown as Record<string, unknown> });
316
- }
317
-
318
- // ---------------------------------------------------------------------------
319
- // routine:delete
320
- // ---------------------------------------------------------------------------
321
-
322
- async function routineDeleteTool(input: unknown, context?: ToolContextLike): Promise<ToolOutput> {
323
- const parsed = toRecord(input);
324
- const id = asNonEmptyString(parsed.id);
325
-
326
- if (!id) {
327
- return failure('INVALID_INPUT', ROUTINE_ID_REQUIRED_MESSAGE);
328
- }
329
-
330
- const storage = getStoragePort();
331
- const routines = await storage.readStore('routines');
332
- const routine = routines.find((entry) => entry.id === id);
333
-
334
- if (!routine) {
335
- return failure('NOT_FOUND', `routine ${id} was not found.`);
336
- }
337
-
338
- if (isDryRun(parsed)) {
339
- return success({
340
- dry_run: true,
341
- deleted_id: id,
342
- });
343
- }
344
-
345
- await storage.withLock(async () => {
346
- const latest = await storage.readStore('routines');
347
- await storage.writeStore(
348
- 'routines',
349
- latest.filter((entry) => entry.id !== id),
350
- );
351
- await storage.appendAudit(
352
- buildAuditEvent({
353
- tool: TOOL_NAMES.DELETE,
354
- op: 'delete',
355
- context,
356
- ids: [id],
357
- }),
358
- );
359
- });
360
-
361
- return success({ deleted_id: id });
362
- }
363
-
364
- // ---------------------------------------------------------------------------
365
- // routine:run (PLAN-ONLY -- does NOT execute tool steps)
366
- // ---------------------------------------------------------------------------
367
-
368
- async function routineRunTool(input: unknown, context?: ToolContextLike): Promise<ToolOutput> {
369
- const parsed = toRecord(input);
370
- const id = asNonEmptyString(parsed.id);
371
-
372
- if (!id) {
373
- return failure('INVALID_INPUT', ROUTINE_ID_REQUIRED_MESSAGE);
374
- }
375
-
376
- const storage = getStoragePort();
377
- const routines = await storage.readStore('routines');
378
- const routine = routines.find((r) => r.id === id);
379
-
380
- if (!routine) {
381
- return failure('NOT_FOUND', `routine ${id} was not found.`);
382
- }
383
-
384
- await storage.appendAudit(
385
- buildAuditEvent({
386
- tool: TOOL_NAMES.RUN,
387
- op: 'execute',
388
- context,
389
- ids: [id],
390
- details: { plan_only: true },
391
- }),
392
- );
393
-
394
- await emitSidekickEvent(
395
- buildRoutineExecutedEvent({
396
- routine_id: routine.id,
397
- name: routine.name,
398
- step_count: routine.steps.length,
399
- }),
400
- );
401
-
402
- return success({
403
- routine_id: routine.id,
404
- name: routine.name,
405
- plan_only: true,
406
- plan: routine.steps.map((step, index) => ({
407
- index,
408
- tool: step.tool,
409
- input: step.input,
410
- })),
411
- governance: {
412
- dispatch_required: true,
413
- execution: 'No tool steps were executed by routine:run. This endpoint only returns a plan.',
414
- },
415
- });
416
- }
417
-
418
- // ---------------------------------------------------------------------------
419
- // Router (default export)
420
- // ---------------------------------------------------------------------------
421
-
422
- export default async function routineTools(
423
- input: unknown,
424
- context?: ToolContextLike,
425
- ): Promise<ToolOutput> {
426
- switch (context?.tool_name) {
427
- case TOOL_NAMES.CREATE:
428
- return routineCreateTool(input, context);
429
- case TOOL_NAMES.LIST:
430
- return routineListTool(input, context);
431
- case TOOL_NAMES.UPDATE:
432
- return routineUpdateTool(input, context);
433
- case TOOL_NAMES.DELETE:
434
- return routineDeleteTool(input, context);
435
- case TOOL_NAMES.RUN:
436
- return routineRunTool(input, context);
437
- default:
438
- return failure('UNKNOWN_TOOL', `Unknown routine tool: ${context?.tool_name ?? 'unknown'}`);
439
- }
440
- }
@@ -1,28 +0,0 @@
1
- // Copyright (c) 2026 Hellmai Ltd
2
- // SPDX-License-Identifier: LicenseRef-LumenFlow-Proprietary
3
-
4
- import { AsyncLocalStorage } from 'node:async_hooks';
5
- import type { ChannelTransport } from './channel-transports.js';
6
- import type { StoragePort } from './storage.js';
7
- import type { SidekickEvent } from '../sidekick-events.js';
8
-
9
- export interface SidekickRuntimeContext {
10
- storagePort: StoragePort;
11
- channelTransports: Map<string, ChannelTransport>;
12
- workspaceRoot?: string;
13
- workspaceConfig?: unknown;
14
- eventSink?: (event: SidekickEvent) => void | Promise<void>;
15
- }
16
-
17
- const runtimeContext = new AsyncLocalStorage<SidekickRuntimeContext>();
18
-
19
- export function getSidekickRuntimeContext(): SidekickRuntimeContext | undefined {
20
- return runtimeContext.getStore();
21
- }
22
-
23
- export async function runWithSidekickRuntimeContext<T>(
24
- context: SidekickRuntimeContext,
25
- fn: () => Promise<T>,
26
- ): Promise<T> {
27
- return runtimeContext.run(context, fn);
28
- }
@@ -1,125 +0,0 @@
1
- // Copyright (c) 2026 Hellmai Ltd
2
- // SPDX-License-Identifier: LicenseRef-LumenFlow-Proprietary
3
-
4
- import { randomBytes } from 'node:crypto';
5
- import type { AuditEvent } from './storage.js';
6
-
7
- export interface ToolContextLike {
8
- tool_name?: string;
9
- receipt_id?: string;
10
- workspace_id?: string;
11
- }
12
-
13
- export interface ToolOutput {
14
- success: boolean;
15
- data?: Record<string, unknown>;
16
- error?: { code: string; message: string; details?: Record<string, unknown> };
17
- metadata?: Record<string, unknown>;
18
- }
19
-
20
- export function toRecord(input: unknown): Record<string, unknown> {
21
- if (input && typeof input === 'object' && !Array.isArray(input)) {
22
- return input as Record<string, unknown>;
23
- }
24
- return {};
25
- }
26
-
27
- export function asNonEmptyString(value: unknown): string | null {
28
- if (typeof value !== 'string') {
29
- return null;
30
- }
31
- const trimmed = value.trim();
32
- return trimmed.length > 0 ? trimmed : null;
33
- }
34
-
35
- export function asStringArray(value: unknown): string[] {
36
- if (!Array.isArray(value)) {
37
- return [];
38
- }
39
- const items: string[] = [];
40
- for (const entry of value) {
41
- const normalized = asNonEmptyString(entry);
42
- if (normalized) {
43
- items.push(normalized);
44
- }
45
- }
46
- return [...new Set(items)];
47
- }
48
-
49
- export function asInteger(value: unknown): number | null {
50
- if (typeof value === 'number' && Number.isFinite(value)) {
51
- return Math.trunc(value);
52
- }
53
- if (typeof value === 'string') {
54
- const parsed = Number.parseInt(value, 10);
55
- return Number.isFinite(parsed) ? parsed : null;
56
- }
57
- return null;
58
- }
59
-
60
- export function isDryRun(input: Record<string, unknown>): boolean {
61
- return input.dry_run === true;
62
- }
63
-
64
- export function nowIso(): string {
65
- return new Date().toISOString();
66
- }
67
-
68
- export function createId(prefix: string): string {
69
- return `${prefix}-${randomBytes(4).toString('hex')}`;
70
- }
71
-
72
- export function failure(
73
- code: string,
74
- message: string,
75
- details?: Record<string, unknown>,
76
- ): ToolOutput {
77
- return {
78
- success: false,
79
- error: {
80
- code,
81
- message,
82
- ...(details ? { details } : {}),
83
- },
84
- };
85
- }
86
-
87
- export function success(data: Record<string, unknown>): ToolOutput {
88
- return {
89
- success: true,
90
- data,
91
- };
92
- }
93
-
94
- export function matchesTags(requiredTags: string[], candidateTags: string[]): boolean {
95
- if (requiredTags.length === 0) {
96
- return true;
97
- }
98
- const candidateSet = new Set(candidateTags.map((tag) => tag.toLowerCase()));
99
- return requiredTags.every((tag) => candidateSet.has(tag.toLowerCase()));
100
- }
101
-
102
- export function includesText(haystack: string, needle: string | null): boolean {
103
- if (!needle) {
104
- return true;
105
- }
106
- return haystack.toLowerCase().includes(needle.toLowerCase());
107
- }
108
-
109
- export function buildAuditEvent(input: {
110
- tool: string;
111
- op: AuditEvent['op'];
112
- context?: ToolContextLike;
113
- ids?: string[];
114
- details?: Record<string, unknown>;
115
- }): AuditEvent {
116
- return {
117
- id: createId('evt'),
118
- ts: nowIso(),
119
- tool: input.tool,
120
- op: input.op,
121
- actor: input.context?.receipt_id,
122
- ids: input.ids,
123
- details: input.details,
124
- };
125
- }