@lumenflow/cli 3.18.1 → 3.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs-sync.js +123 -6
- package/dist/docs-sync.js.map +1 -1
- package/dist/gate-co-change.js +23 -4
- package/dist/gate-co-change.js.map +1 -1
- package/dist/gates-runners.js +113 -16
- package/dist/gates-runners.js.map +1 -1
- package/dist/gates-utils.js +71 -0
- package/dist/gates-utils.js.map +1 -1
- package/dist/lumenflow-upgrade.js +1 -0
- package/dist/lumenflow-upgrade.js.map +1 -1
- package/dist/public-manifest.js +1 -1
- package/dist/public-manifest.js.map +1 -1
- package/dist/sync-templates.js +13 -0
- package/dist/sync-templates.js.map +1 -1
- package/dist/wu-block.js +10 -0
- package/dist/wu-block.js.map +1 -1
- package/dist/wu-claim-validation.js +3 -1
- package/dist/wu-claim-validation.js.map +1 -1
- package/dist/wu-claim.js +3 -1
- package/dist/wu-claim.js.map +1 -1
- package/dist/wu-done-memory-telemetry.js +5 -1
- package/dist/wu-done-memory-telemetry.js.map +1 -1
- package/dist/wu-done-ownership.js +6 -0
- package/dist/wu-done-ownership.js.map +1 -1
- package/dist/wu-edit-operations.js +4 -4
- package/dist/wu-edit-operations.js.map +1 -1
- package/dist/wu-prep.js +88 -13
- package/dist/wu-prep.js.map +1 -1
- package/dist/wu-prune.js +2 -2
- package/dist/wu-prune.js.map +1 -1
- package/dist/wu-recover.js +15 -0
- package/dist/wu-recover.js.map +1 -1
- package/dist/wu-release.js +10 -1
- package/dist/wu-release.js.map +1 -1
- package/dist/wu-spawn-prompt-builders.js +27 -2
- package/dist/wu-spawn-prompt-builders.js.map +1 -1
- package/dist/wu-state-mutation-ownership.js +136 -0
- package/dist/wu-state-mutation-ownership.js.map +1 -0
- package/dist/wu-unblock.js +10 -0
- package/dist/wu-unblock.js.map +1 -1
- package/dist/wu-verify.js +22 -17
- package/dist/wu-verify.js.map +1 -1
- package/package.json +111 -110
- package/packs/agent-runtime/.turbo/turbo-build.log +1 -1
- package/packs/agent-runtime/package.json +1 -1
- package/packs/sidekick/.turbo/turbo-build.log +1 -1
- package/packs/sidekick/README.md +118 -113
- package/packs/sidekick/manifest-schema.ts +15 -228
- package/packs/sidekick/manifest.ts +107 -7
- package/packs/sidekick/manifest.yaml +199 -1
- package/packs/sidekick/package.json +4 -1
- package/packs/sidekick/policy-factory.ts +38 -0
- package/packs/sidekick/tool-impl/channel-tools.ts +99 -0
- package/packs/sidekick/tool-impl/memory-tools.ts +86 -1
- package/packs/sidekick/tool-impl/routine-tools.ts +156 -2
- package/packs/sidekick/tool-impl/storage.ts +6 -5
- package/packs/sidekick/tool-impl/task-tools.ts +186 -4
- package/packs/software-delivery/.turbo/turbo-build.log +1 -1
- package/packs/software-delivery/package.json +1 -1
- package/templates/core/AGENTS.md.template +157 -32
- package/templates/core/LUMENFLOW.md.template +44 -29
- package/templates/core/_frameworks/lumenflow/wu-sizing-guide.md.template +644 -0
- package/templates/core/ai/onboarding/agent-invocation-guide.md.template +5 -5
- package/templates/core/ai/onboarding/agent-safety-card.md.template +1 -0
- package/templates/core/ai/onboarding/docs-generation.md.template +94 -4
- package/templates/core/ai/onboarding/first-15-mins.md.template +1 -1
- package/templates/core/ai/onboarding/first-wu-mistakes.md.template +2 -1
- package/templates/core/ai/onboarding/initiative-orchestration.md.template +21 -21
- package/templates/core/ai/onboarding/quick-ref-commands.md.template +126 -109
- package/templates/core/ai/onboarding/release-process.md.template +12 -12
- package/templates/core/ai/onboarding/starting-prompt.md.template +33 -32
- package/templates/vendors/claude/.claude/skills/initiative-management/SKILL.md.template +2 -2
- package/templates/vendors/claude/.claude/skills/multi-agent-coordination/SKILL.md.template +2 -2
- package/templates/vendors/claude/.claude/skills/orchestration/SKILL.md.template +3 -3
- package/dist/chunk-2D2VOCA4.js +0 -37
- package/dist/chunk-2D5KFYGX.js +0 -284
- package/dist/chunk-2GXVIN57.js +0 -14072
- package/dist/chunk-2MQ7HZWZ.js +0 -26
- package/dist/chunk-2UFQ3A3C.js +0 -643
- package/dist/chunk-3RG5ZIWI.js +0 -10
- package/dist/chunk-4N74J3UT.js +0 -15
- package/dist/chunk-5GTOXFYR.js +0 -392
- package/dist/chunk-5VY6MQMC.js +0 -240
- package/dist/chunk-67XVPMRY.js +0 -1297
- package/dist/chunk-6HO4GWJE.js +0 -164
- package/dist/chunk-6W5XHWYV.js +0 -1890
- package/dist/chunk-6X4EMYJQ.js +0 -64
- package/dist/chunk-6XYXI2NQ.js +0 -772
- package/dist/chunk-7ANSOV6Q.js +0 -285
- package/dist/chunk-A624LFLB.js +0 -1380
- package/dist/chunk-ADN5NHG4.js +0 -126
- package/dist/chunk-B7YJYJKG.js +0 -33
- package/dist/chunk-CCLHCPKG.js +0 -210
- package/dist/chunk-CK36VROC.js +0 -1584
- package/dist/chunk-D3UOFRSB.js +0 -81
- package/dist/chunk-DFR4DJBM.js +0 -230
- package/dist/chunk-DSYBDHYH.js +0 -79
- package/dist/chunk-DWMLTXKQ.js +0 -1176
- package/dist/chunk-E3REJTAJ.js +0 -28
- package/dist/chunk-EA3IVO64.js +0 -633
- package/dist/chunk-EK2AKZKD.js +0 -55
- package/dist/chunk-ELD7JTTT.js +0 -343
- package/dist/chunk-EX6TT2XI.js +0 -195
- package/dist/chunk-EXINSFZE.js +0 -82
- package/dist/chunk-EZ6ZBYBM.js +0 -510
- package/dist/chunk-FBKAPTJ2.js +0 -16
- package/dist/chunk-FVLV5RYH.js +0 -1118
- package/dist/chunk-GDNSBQVK.js +0 -2485
- package/dist/chunk-GPQHMBNN.js +0 -278
- package/dist/chunk-GTFJB67L.js +0 -68
- package/dist/chunk-HANJXVKW.js +0 -1127
- package/dist/chunk-HEVS5YLD.js +0 -269
- package/dist/chunk-HMEVZKPQ.js +0 -9
- package/dist/chunk-HRGSYNLM.js +0 -3511
- package/dist/chunk-ISZR5N4K.js +0 -60
- package/dist/chunk-J6SUPR2C.js +0 -226
- package/dist/chunk-JERYVEIZ.js +0 -244
- package/dist/chunk-JHHWGL2N.js +0 -87
- package/dist/chunk-JONWQUB5.js +0 -775
- package/dist/chunk-K2DIWWDM.js +0 -1766
- package/dist/chunk-KY4PGL5V.js +0 -969
- package/dist/chunk-L737LQ4C.js +0 -1285
- package/dist/chunk-LFTWYIB2.js +0 -497
- package/dist/chunk-LV47RFNJ.js +0 -41
- package/dist/chunk-MKSAITI7.js +0 -15
- package/dist/chunk-MZ7RKIX4.js +0 -212
- package/dist/chunk-NAP6CFSO.js +0 -84
- package/dist/chunk-ND6MY37M.js +0 -16
- package/dist/chunk-NMG736UR.js +0 -683
- package/dist/chunk-NRAXROED.js +0 -32
- package/dist/chunk-NRIZR3A7.js +0 -690
- package/dist/chunk-NX43BG3M.js +0 -233
- package/dist/chunk-O645XLSI.js +0 -297
- package/dist/chunk-OMJD6A3S.js +0 -235
- package/dist/chunk-QB6SJD4T.js +0 -430
- package/dist/chunk-QFSTL4J3.js +0 -276
- package/dist/chunk-QLGDFMFX.js +0 -212
- package/dist/chunk-RIAAGL2E.js +0 -13
- package/dist/chunk-RWO5XMZ6.js +0 -86
- package/dist/chunk-RXRKBBSM.js +0 -149
- package/dist/chunk-RZOZMML6.js +0 -363
- package/dist/chunk-U7I7FS7T.js +0 -113
- package/dist/chunk-UI42RODY.js +0 -717
- package/dist/chunk-UTVMVSCO.js +0 -519
- package/dist/chunk-V6OJGLBA.js +0 -1746
- package/dist/chunk-W2JHVH7D.js +0 -152
- package/dist/chunk-WD3Y7VQN.js +0 -280
- package/dist/chunk-WOCTQ5MS.js +0 -303
- package/dist/chunk-WZR3ZUNN.js +0 -696
- package/dist/chunk-XGI665H7.js +0 -150
- package/dist/chunk-XKY65P2T.js +0 -304
- package/dist/chunk-Y4CQZY65.js +0 -57
- package/dist/chunk-YFEXKLVE.js +0 -194
- package/dist/chunk-YHO3HS5X.js +0 -287
- package/dist/chunk-YLS7AZSX.js +0 -738
- package/dist/chunk-ZE473AO6.js +0 -49
- package/dist/chunk-ZF747T3O.js +0 -644
- package/dist/chunk-ZHCZHZH3.js +0 -43
- package/dist/chunk-ZZNZX2XY.js +0 -87
- package/dist/constants-7QAP3VQ4.js +0 -23
- package/dist/dist-IY3UUMWK.js +0 -33
- package/dist/invariants-runner-W5RGHCSU.js +0 -27
- package/dist/lane-lock-6J36HD5O.js +0 -35
- package/dist/mem-checkpoint-core-EANG2GVN.js +0 -14
- package/dist/mem-signal-core-2LZ2WYHW.js +0 -19
- package/dist/memory-store-OLB5FO7K.js +0 -18
- package/dist/service-6BYCOCO5.js +0 -13
- package/dist/spawn-policy-resolver-NTSZYQ6R.js +0 -17
- package/dist/spawn-task-builder-R4E2BHSW.js +0 -22
- package/dist/wu-done-pr-WLFFFEPJ.js +0 -25
- package/dist/wu-done-validation-3J5E36FE.js +0 -30
- package/dist/wu-duplicate-id-detector-5S7JHELK.js +0 -232
- package/packs/sidekick/.turbo/turbo-test.log +0 -12
- package/packs/sidekick/.turbo/turbo-typecheck.log +0 -4
- package/packs/software-delivery/.turbo/turbo-typecheck.log +0 -4
|
@@ -23,8 +23,11 @@ import {
|
|
|
23
23
|
const TOOL_NAMES = {
|
|
24
24
|
CREATE: 'routine:create',
|
|
25
25
|
LIST: 'routine:list',
|
|
26
|
+
UPDATE: 'routine:update',
|
|
27
|
+
DELETE: 'routine:delete',
|
|
26
28
|
RUN: 'routine:run',
|
|
27
29
|
} as const;
|
|
30
|
+
const ROUTINE_ID_REQUIRED_MESSAGE = 'id is required.';
|
|
28
31
|
|
|
29
32
|
// ---------------------------------------------------------------------------
|
|
30
33
|
// Helpers
|
|
@@ -80,6 +83,13 @@ function normalizeSteps(value: unknown): NormalizeStepsResult {
|
|
|
80
83
|
return { steps, warnings };
|
|
81
84
|
}
|
|
82
85
|
|
|
86
|
+
function asOptionalBoolean(value: unknown): boolean | null {
|
|
87
|
+
if (value === true || value === false) {
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
83
93
|
// ---------------------------------------------------------------------------
|
|
84
94
|
// routine:create
|
|
85
95
|
// ---------------------------------------------------------------------------
|
|
@@ -105,6 +115,8 @@ async function routineCreateTool(input: unknown, context?: ToolContextLike): Pro
|
|
|
105
115
|
id: createId('routine'),
|
|
106
116
|
name,
|
|
107
117
|
steps,
|
|
118
|
+
cron: asNonEmptyString(parsed.cron) ?? undefined,
|
|
119
|
+
enabled: asOptionalBoolean(parsed.enabled) ?? true,
|
|
108
120
|
created_at: now,
|
|
109
121
|
updated_at: now,
|
|
110
122
|
};
|
|
@@ -145,11 +157,13 @@ async function routineCreateTool(input: unknown, context?: ToolContextLike): Pro
|
|
|
145
157
|
async function routineListTool(input: unknown, _context?: ToolContextLike): Promise<ToolOutput> {
|
|
146
158
|
const parsed = toRecord(input);
|
|
147
159
|
const limit = asInteger(parsed.limit);
|
|
160
|
+
const enabledOnly = parsed.enabled_only === true;
|
|
148
161
|
|
|
149
162
|
const storage = getStoragePort();
|
|
150
163
|
const routines = await storage.readStore('routines');
|
|
151
164
|
|
|
152
|
-
const
|
|
165
|
+
const filtered = enabledOnly ? routines.filter((routine) => routine.enabled) : routines;
|
|
166
|
+
const sorted = filtered.toSorted((a, b) => Date.parse(b.updated_at) - Date.parse(a.updated_at));
|
|
153
167
|
|
|
154
168
|
const items = limit && limit > 0 ? sorted.slice(0, limit) : sorted;
|
|
155
169
|
|
|
@@ -159,6 +173,142 @@ async function routineListTool(input: unknown, _context?: ToolContextLike): Prom
|
|
|
159
173
|
});
|
|
160
174
|
}
|
|
161
175
|
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
// routine:update
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
async function routineUpdateTool(input: unknown, context?: ToolContextLike): Promise<ToolOutput> {
|
|
181
|
+
const parsed = toRecord(input);
|
|
182
|
+
const id = asNonEmptyString(parsed.id);
|
|
183
|
+
|
|
184
|
+
if (!id) {
|
|
185
|
+
return failure('INVALID_INPUT', ROUTINE_ID_REQUIRED_MESSAGE);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const patch: Partial<RoutineRecord> = {};
|
|
189
|
+
const name = asNonEmptyString(parsed.name);
|
|
190
|
+
const cron =
|
|
191
|
+
typeof parsed.cron === 'string' ? (asNonEmptyString(parsed.cron) ?? undefined) : null;
|
|
192
|
+
const enabled = asOptionalBoolean(parsed.enabled);
|
|
193
|
+
|
|
194
|
+
if (name) {
|
|
195
|
+
patch.name = name;
|
|
196
|
+
}
|
|
197
|
+
if (cron !== null) {
|
|
198
|
+
patch.cron = cron;
|
|
199
|
+
}
|
|
200
|
+
if (enabled !== null) {
|
|
201
|
+
patch.enabled = enabled;
|
|
202
|
+
}
|
|
203
|
+
if (parsed.steps !== undefined) {
|
|
204
|
+
const { steps, warnings } = normalizeSteps(parsed.steps);
|
|
205
|
+
if (steps.length === 0) {
|
|
206
|
+
const detail =
|
|
207
|
+
warnings.length > 0
|
|
208
|
+
? `steps must include at least one tool step. Issues: ${warnings.join('; ')}`
|
|
209
|
+
: 'steps must include at least one tool step.';
|
|
210
|
+
return failure('INVALID_INPUT', detail);
|
|
211
|
+
}
|
|
212
|
+
patch.steps = steps;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (Object.keys(patch).length === 0) {
|
|
216
|
+
return failure(
|
|
217
|
+
'INVALID_INPUT',
|
|
218
|
+
'routine:update requires at least one of name, steps, cron, or enabled.',
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const storage = getStoragePort();
|
|
223
|
+
const routines = await storage.readStore('routines');
|
|
224
|
+
const routine = routines.find((entry) => entry.id === id);
|
|
225
|
+
|
|
226
|
+
if (!routine) {
|
|
227
|
+
return failure('NOT_FOUND', `routine ${id} was not found.`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const preview: RoutineRecord = {
|
|
231
|
+
...routine,
|
|
232
|
+
...patch,
|
|
233
|
+
updated_at: nowIso(),
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
if (isDryRun(parsed)) {
|
|
237
|
+
return success({
|
|
238
|
+
dry_run: true,
|
|
239
|
+
routine: preview as unknown as Record<string, unknown>,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
await storage.withLock(async () => {
|
|
244
|
+
const latest = await storage.readStore('routines');
|
|
245
|
+
const target = latest.find((entry) => entry.id === id);
|
|
246
|
+
if (!target) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
Object.assign(target, preview);
|
|
250
|
+
await storage.writeStore('routines', latest);
|
|
251
|
+
await storage.appendAudit(
|
|
252
|
+
buildAuditEvent({
|
|
253
|
+
tool: TOOL_NAMES.UPDATE,
|
|
254
|
+
op: 'update',
|
|
255
|
+
context,
|
|
256
|
+
ids: [id],
|
|
257
|
+
}),
|
|
258
|
+
);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const updated = await storage.readStore('routines');
|
|
262
|
+
const updatedRoutine = updated.find((entry) => entry.id === id);
|
|
263
|
+
return success({ routine: updatedRoutine as unknown as Record<string, unknown> });
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
// routine:delete
|
|
268
|
+
// ---------------------------------------------------------------------------
|
|
269
|
+
|
|
270
|
+
async function routineDeleteTool(input: unknown, context?: ToolContextLike): Promise<ToolOutput> {
|
|
271
|
+
const parsed = toRecord(input);
|
|
272
|
+
const id = asNonEmptyString(parsed.id);
|
|
273
|
+
|
|
274
|
+
if (!id) {
|
|
275
|
+
return failure('INVALID_INPUT', ROUTINE_ID_REQUIRED_MESSAGE);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const storage = getStoragePort();
|
|
279
|
+
const routines = await storage.readStore('routines');
|
|
280
|
+
const routine = routines.find((entry) => entry.id === id);
|
|
281
|
+
|
|
282
|
+
if (!routine) {
|
|
283
|
+
return failure('NOT_FOUND', `routine ${id} was not found.`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (isDryRun(parsed)) {
|
|
287
|
+
return success({
|
|
288
|
+
dry_run: true,
|
|
289
|
+
deleted_id: id,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
await storage.withLock(async () => {
|
|
294
|
+
const latest = await storage.readStore('routines');
|
|
295
|
+
await storage.writeStore(
|
|
296
|
+
'routines',
|
|
297
|
+
latest.filter((entry) => entry.id !== id),
|
|
298
|
+
);
|
|
299
|
+
await storage.appendAudit(
|
|
300
|
+
buildAuditEvent({
|
|
301
|
+
tool: TOOL_NAMES.DELETE,
|
|
302
|
+
op: 'delete',
|
|
303
|
+
context,
|
|
304
|
+
ids: [id],
|
|
305
|
+
}),
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
return success({ deleted_id: id });
|
|
310
|
+
}
|
|
311
|
+
|
|
162
312
|
// ---------------------------------------------------------------------------
|
|
163
313
|
// routine:run (PLAN-ONLY -- does NOT execute tool steps)
|
|
164
314
|
// ---------------------------------------------------------------------------
|
|
@@ -168,7 +318,7 @@ async function routineRunTool(input: unknown, context?: ToolContextLike): Promis
|
|
|
168
318
|
const id = asNonEmptyString(parsed.id);
|
|
169
319
|
|
|
170
320
|
if (!id) {
|
|
171
|
-
return failure('INVALID_INPUT',
|
|
321
|
+
return failure('INVALID_INPUT', ROUTINE_ID_REQUIRED_MESSAGE);
|
|
172
322
|
}
|
|
173
323
|
|
|
174
324
|
const storage = getStoragePort();
|
|
@@ -218,6 +368,10 @@ export default async function routineTools(
|
|
|
218
368
|
return routineCreateTool(input, context);
|
|
219
369
|
case TOOL_NAMES.LIST:
|
|
220
370
|
return routineListTool(input, context);
|
|
371
|
+
case TOOL_NAMES.UPDATE:
|
|
372
|
+
return routineUpdateTool(input, context);
|
|
373
|
+
case TOOL_NAMES.DELETE:
|
|
374
|
+
return routineDeleteTool(input, context);
|
|
221
375
|
case TOOL_NAMES.RUN:
|
|
222
376
|
return routineRunTool(input, context);
|
|
223
377
|
default:
|
|
@@ -21,8 +21,8 @@ const RANDOM_BYTES_LENGTH = 4;
|
|
|
21
21
|
// ---------------------------------------------------------------------------
|
|
22
22
|
|
|
23
23
|
export type TaskPriority = 'P0' | 'P1' | 'P2' | 'P3';
|
|
24
|
-
export type TaskStatus = 'pending' | 'done';
|
|
25
|
-
export type MemoryType = 'fact' | 'preference' | 'note';
|
|
24
|
+
export type TaskStatus = 'pending' | 'done' | 'canceled';
|
|
25
|
+
export type MemoryType = 'fact' | 'preference' | 'note' | 'snippet';
|
|
26
26
|
|
|
27
27
|
export interface TaskRecord {
|
|
28
28
|
id: string;
|
|
@@ -32,10 +32,12 @@ export interface TaskRecord {
|
|
|
32
32
|
status: TaskStatus;
|
|
33
33
|
tags: string[];
|
|
34
34
|
due_at?: string;
|
|
35
|
+
cron?: string;
|
|
35
36
|
note?: string;
|
|
36
37
|
created_at: string;
|
|
37
38
|
updated_at: string;
|
|
38
39
|
completed_at?: string;
|
|
40
|
+
canceled_at?: string;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
export interface MemoryRecord {
|
|
@@ -71,6 +73,8 @@ export interface RoutineRecord {
|
|
|
71
73
|
id: string;
|
|
72
74
|
name: string;
|
|
73
75
|
steps: RoutineStepRecord[];
|
|
76
|
+
cron?: string;
|
|
77
|
+
enabled: boolean;
|
|
74
78
|
created_at: string;
|
|
75
79
|
updated_at: string;
|
|
76
80
|
}
|
|
@@ -140,9 +144,6 @@ const LOCK_FILE_PATH = '.lock';
|
|
|
140
144
|
// ---------------------------------------------------------------------------
|
|
141
145
|
|
|
142
146
|
function cloneStore<K extends StoreName>(store: K, value: SidekickStores[K]): SidekickStores[K] {
|
|
143
|
-
if (Array.isArray(value)) {
|
|
144
|
-
return value.map((entry) => ({ ...entry })) as SidekickStores[K];
|
|
145
|
-
}
|
|
146
147
|
return structuredClone(value) as SidekickStores[K];
|
|
147
148
|
}
|
|
148
149
|
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
buildAuditEvent,
|
|
10
10
|
createId,
|
|
11
11
|
failure,
|
|
12
|
+
includesText,
|
|
12
13
|
isDryRun,
|
|
13
14
|
matchesTags,
|
|
14
15
|
nowIso,
|
|
@@ -25,12 +26,15 @@ import {
|
|
|
25
26
|
const TOOL_NAMES = {
|
|
26
27
|
CREATE: 'task:create',
|
|
27
28
|
LIST: 'task:list',
|
|
29
|
+
UPDATE: 'task:update',
|
|
30
|
+
CANCEL: 'task:cancel',
|
|
28
31
|
COMPLETE: 'task:complete',
|
|
29
32
|
SCHEDULE: 'task:schedule',
|
|
30
33
|
} as const;
|
|
31
34
|
|
|
32
35
|
const VALID_PRIORITIES: TaskPriority[] = ['P0', 'P1', 'P2', 'P3'];
|
|
33
36
|
const DEFAULT_PRIORITY: TaskPriority = 'P2';
|
|
37
|
+
const TASK_ID_REQUIRED_MESSAGE = 'id is required.';
|
|
34
38
|
|
|
35
39
|
// ---------------------------------------------------------------------------
|
|
36
40
|
// Helpers
|
|
@@ -42,6 +46,57 @@ function asPriority(value: unknown): TaskPriority {
|
|
|
42
46
|
: DEFAULT_PRIORITY;
|
|
43
47
|
}
|
|
44
48
|
|
|
49
|
+
function resolveTaskUpdatePatch(parsed: Record<string, unknown>): Partial<TaskRecord> {
|
|
50
|
+
const patch: Partial<TaskRecord> = {};
|
|
51
|
+
const title = asNonEmptyString(parsed.title);
|
|
52
|
+
const description =
|
|
53
|
+
typeof parsed.description === 'string'
|
|
54
|
+
? (asNonEmptyString(parsed.description) ?? undefined)
|
|
55
|
+
: null;
|
|
56
|
+
const dueAt =
|
|
57
|
+
typeof parsed.due_at === 'string' ? (asNonEmptyString(parsed.due_at) ?? undefined) : null;
|
|
58
|
+
const cron =
|
|
59
|
+
typeof parsed.cron === 'string' ? (asNonEmptyString(parsed.cron) ?? undefined) : null;
|
|
60
|
+
|
|
61
|
+
if (title) {
|
|
62
|
+
patch.title = title;
|
|
63
|
+
}
|
|
64
|
+
if (description !== null) {
|
|
65
|
+
patch.description = description;
|
|
66
|
+
}
|
|
67
|
+
if (dueAt !== null) {
|
|
68
|
+
patch.due_at = dueAt;
|
|
69
|
+
}
|
|
70
|
+
if (cron !== null) {
|
|
71
|
+
patch.cron = cron;
|
|
72
|
+
}
|
|
73
|
+
if (parsed.priority !== undefined) {
|
|
74
|
+
patch.priority = asPriority(parsed.priority);
|
|
75
|
+
}
|
|
76
|
+
if (parsed.tags !== undefined) {
|
|
77
|
+
patch.tags = asStringArray(parsed.tags);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return patch;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function applyTaskPatch(task: TaskRecord, patch: Partial<TaskRecord>): TaskRecord {
|
|
84
|
+
return {
|
|
85
|
+
...task,
|
|
86
|
+
...patch,
|
|
87
|
+
updated_at: nowIso(),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function buildCanceledTask(task: TaskRecord): TaskRecord {
|
|
92
|
+
return {
|
|
93
|
+
...task,
|
|
94
|
+
status: 'canceled',
|
|
95
|
+
canceled_at: nowIso(),
|
|
96
|
+
updated_at: nowIso(),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
45
100
|
// ---------------------------------------------------------------------------
|
|
46
101
|
// task:create
|
|
47
102
|
// ---------------------------------------------------------------------------
|
|
@@ -97,6 +152,7 @@ async function taskListTool(input: unknown, _context?: ToolContextLike): Promise
|
|
|
97
152
|
const statusFilter = asNonEmptyString(parsed.status);
|
|
98
153
|
const priorityFilter = asNonEmptyString(parsed.priority);
|
|
99
154
|
const tags = asStringArray(parsed.tags);
|
|
155
|
+
const search = asNonEmptyString(parsed.search);
|
|
100
156
|
const dueBefore = asNonEmptyString(parsed.due_before);
|
|
101
157
|
const limit = asInteger(parsed.limit);
|
|
102
158
|
|
|
@@ -113,6 +169,9 @@ async function taskListTool(input: unknown, _context?: ToolContextLike): Promise
|
|
|
113
169
|
if (!matchesTags(tags, task.tags)) {
|
|
114
170
|
return false;
|
|
115
171
|
}
|
|
172
|
+
if (!includesText(`${task.title}\n${task.description ?? ''}`, search)) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
116
175
|
if (dueBefore && task.due_at) {
|
|
117
176
|
if (Date.parse(task.due_at) >= Date.parse(dueBefore)) {
|
|
118
177
|
return false;
|
|
@@ -134,6 +193,70 @@ async function taskListTool(input: unknown, _context?: ToolContextLike): Promise
|
|
|
134
193
|
});
|
|
135
194
|
}
|
|
136
195
|
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// task:update
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
async function taskUpdateTool(input: unknown, context?: ToolContextLike): Promise<ToolOutput> {
|
|
201
|
+
const parsed = toRecord(input);
|
|
202
|
+
const id = asNonEmptyString(parsed.id);
|
|
203
|
+
|
|
204
|
+
if (!id) {
|
|
205
|
+
return failure('INVALID_INPUT', TASK_ID_REQUIRED_MESSAGE);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const patch = resolveTaskUpdatePatch(parsed);
|
|
209
|
+
if (Object.keys(patch).length === 0) {
|
|
210
|
+
return failure(
|
|
211
|
+
'INVALID_INPUT',
|
|
212
|
+
'task:update requires at least one of title, description, priority, tags, due_at, or cron.',
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const storage = getStoragePort();
|
|
217
|
+
const tasks = await storage.readStore('tasks');
|
|
218
|
+
const task = tasks.find((entry) => entry.id === id);
|
|
219
|
+
|
|
220
|
+
if (!task) {
|
|
221
|
+
return failure('NOT_FOUND', `task ${id} was not found.`);
|
|
222
|
+
}
|
|
223
|
+
if (task.status === 'canceled') {
|
|
224
|
+
return failure(
|
|
225
|
+
'INVALID_STATE',
|
|
226
|
+
`task ${id} is canceled and cannot be completed. Reopen it with task:update before completing.`,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (isDryRun(parsed)) {
|
|
231
|
+
return success({
|
|
232
|
+
dry_run: true,
|
|
233
|
+
task: applyTaskPatch(task, patch) as unknown as Record<string, unknown>,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
await storage.withLock(async () => {
|
|
238
|
+
const latest = await storage.readStore('tasks');
|
|
239
|
+
const target = latest.find((entry) => entry.id === id);
|
|
240
|
+
if (!target) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
Object.assign(target, applyTaskPatch(target, patch));
|
|
244
|
+
await storage.writeStore('tasks', latest);
|
|
245
|
+
await storage.appendAudit(
|
|
246
|
+
buildAuditEvent({
|
|
247
|
+
tool: TOOL_NAMES.UPDATE,
|
|
248
|
+
op: 'update',
|
|
249
|
+
context,
|
|
250
|
+
ids: [id],
|
|
251
|
+
}),
|
|
252
|
+
);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const updated = await storage.readStore('tasks');
|
|
256
|
+
const updatedTask = updated.find((entry) => entry.id === id);
|
|
257
|
+
return success({ task: updatedTask as unknown as Record<string, unknown> });
|
|
258
|
+
}
|
|
259
|
+
|
|
137
260
|
// ---------------------------------------------------------------------------
|
|
138
261
|
// task:complete
|
|
139
262
|
// ---------------------------------------------------------------------------
|
|
@@ -143,7 +266,7 @@ async function taskCompleteTool(input: unknown, context?: ToolContextLike): Prom
|
|
|
143
266
|
const id = asNonEmptyString(parsed.id);
|
|
144
267
|
|
|
145
268
|
if (!id) {
|
|
146
|
-
return failure('INVALID_INPUT',
|
|
269
|
+
return failure('INVALID_INPUT', TASK_ID_REQUIRED_MESSAGE);
|
|
147
270
|
}
|
|
148
271
|
|
|
149
272
|
const storage = getStoragePort();
|
|
@@ -190,6 +313,62 @@ async function taskCompleteTool(input: unknown, context?: ToolContextLike): Prom
|
|
|
190
313
|
return success({ task: completedTask as unknown as Record<string, unknown> });
|
|
191
314
|
}
|
|
192
315
|
|
|
316
|
+
// ---------------------------------------------------------------------------
|
|
317
|
+
// task:cancel
|
|
318
|
+
// ---------------------------------------------------------------------------
|
|
319
|
+
|
|
320
|
+
async function taskCancelTool(input: unknown, context?: ToolContextLike): Promise<ToolOutput> {
|
|
321
|
+
const parsed = toRecord(input);
|
|
322
|
+
const id = asNonEmptyString(parsed.id);
|
|
323
|
+
|
|
324
|
+
if (!id) {
|
|
325
|
+
return failure('INVALID_INPUT', TASK_ID_REQUIRED_MESSAGE);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const storage = getStoragePort();
|
|
329
|
+
const tasks = await storage.readStore('tasks');
|
|
330
|
+
const task = tasks.find((entry) => entry.id === id);
|
|
331
|
+
|
|
332
|
+
if (!task) {
|
|
333
|
+
return failure('NOT_FOUND', `task ${id} was not found.`);
|
|
334
|
+
}
|
|
335
|
+
if (task.status === 'done') {
|
|
336
|
+
return failure(
|
|
337
|
+
'INVALID_STATE',
|
|
338
|
+
`task ${id} is already done and cannot be canceled. Use task:update if you only need to adjust metadata.`,
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (isDryRun(parsed)) {
|
|
343
|
+
return success({
|
|
344
|
+
dry_run: true,
|
|
345
|
+
task: buildCanceledTask(task) as unknown as Record<string, unknown>,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
await storage.withLock(async () => {
|
|
350
|
+
const latest = await storage.readStore('tasks');
|
|
351
|
+
const target = latest.find((entry) => entry.id === id);
|
|
352
|
+
if (!target) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
Object.assign(target, buildCanceledTask(target));
|
|
356
|
+
await storage.writeStore('tasks', latest);
|
|
357
|
+
await storage.appendAudit(
|
|
358
|
+
buildAuditEvent({
|
|
359
|
+
tool: TOOL_NAMES.CANCEL,
|
|
360
|
+
op: 'update',
|
|
361
|
+
context,
|
|
362
|
+
ids: [id],
|
|
363
|
+
}),
|
|
364
|
+
);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
const updated = await storage.readStore('tasks');
|
|
368
|
+
const canceledTask = updated.find((entry) => entry.id === id);
|
|
369
|
+
return success({ task: canceledTask as unknown as Record<string, unknown> });
|
|
370
|
+
}
|
|
371
|
+
|
|
193
372
|
// ---------------------------------------------------------------------------
|
|
194
373
|
// task:schedule
|
|
195
374
|
// ---------------------------------------------------------------------------
|
|
@@ -199,7 +378,7 @@ async function taskScheduleTool(input: unknown, context?: ToolContextLike): Prom
|
|
|
199
378
|
const id = asNonEmptyString(parsed.id);
|
|
200
379
|
|
|
201
380
|
if (!id) {
|
|
202
|
-
return failure('INVALID_INPUT',
|
|
381
|
+
return failure('INVALID_INPUT', TASK_ID_REQUIRED_MESSAGE);
|
|
203
382
|
}
|
|
204
383
|
|
|
205
384
|
const storage = getStoragePort();
|
|
@@ -232,9 +411,8 @@ async function taskScheduleTool(input: unknown, context?: ToolContextLike): Prom
|
|
|
232
411
|
if (dueAt) {
|
|
233
412
|
target.due_at = dueAt;
|
|
234
413
|
}
|
|
235
|
-
// cron is stored but TaskRecord doesn't have it yet -- extend inline
|
|
236
414
|
if (cron) {
|
|
237
|
-
|
|
415
|
+
target.cron = cron;
|
|
238
416
|
}
|
|
239
417
|
target.updated_at = nowIso();
|
|
240
418
|
await storage.writeStore('tasks', latest);
|
|
@@ -268,6 +446,10 @@ export default async function taskTools(
|
|
|
268
446
|
return taskCreateTool(input, context);
|
|
269
447
|
case TOOL_NAMES.LIST:
|
|
270
448
|
return taskListTool(input, context);
|
|
449
|
+
case TOOL_NAMES.UPDATE:
|
|
450
|
+
return taskUpdateTool(input, context);
|
|
451
|
+
case TOOL_NAMES.CANCEL:
|
|
452
|
+
return taskCancelTool(input, context);
|
|
271
453
|
case TOOL_NAMES.COMPLETE:
|
|
272
454
|
return taskCompleteTool(input, context);
|
|
273
455
|
case TOOL_NAMES.SCHEDULE:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
> @lumenflow/packs-software-delivery@3.
|
|
2
|
+
> @lumenflow/packs-software-delivery@3.20.0 build /home/runner/work/lumenflow-dev/lumenflow-dev/packages/@lumenflow/packs/software-delivery
|
|
3
3
|
> tsc
|
|
4
4
|
|