toolcraft 0.0.2 → 0.0.4

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 (85) hide show
  1. package/README.md +461 -58
  2. package/dist/cli.compile-check.js +1 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +768 -40
  5. package/dist/human-in-loop/approval-tasks.d.ts +31 -0
  6. package/dist/human-in-loop/approval-tasks.js +201 -0
  7. package/dist/human-in-loop/approvals-commands.d.ts +11 -0
  8. package/dist/human-in-loop/approvals-commands.js +191 -0
  9. package/dist/human-in-loop/config.d.ts +11 -0
  10. package/dist/human-in-loop/config.js +21 -0
  11. package/dist/human-in-loop/default-provider.d.ts +2 -0
  12. package/dist/human-in-loop/default-provider.js +26 -0
  13. package/dist/human-in-loop/gate.d.ts +4 -0
  14. package/dist/human-in-loop/gate.js +57 -0
  15. package/dist/human-in-loop/index.d.ts +7 -0
  16. package/dist/human-in-loop/index.js +4 -0
  17. package/dist/human-in-loop/runner.d.ts +3 -0
  18. package/dist/human-in-loop/runner.js +196 -0
  19. package/dist/human-in-loop/spawn.d.ts +3 -0
  20. package/dist/human-in-loop/spawn.js +16 -0
  21. package/dist/human-in-loop/state-machine.d.ts +4 -0
  22. package/dist/human-in-loop/state-machine.js +10 -0
  23. package/dist/human-in-loop/types.d.ts +41 -0
  24. package/dist/human-in-loop/types.js +13 -0
  25. package/dist/index.compile-check.js +24 -0
  26. package/dist/index.d.ts +32 -13
  27. package/dist/index.js +82 -17
  28. package/dist/json-schema-converter.d.ts +21 -0
  29. package/dist/json-schema-converter.js +432 -0
  30. package/dist/mcp-proxy.d.ts +8 -0
  31. package/dist/mcp-proxy.js +383 -0
  32. package/dist/mcp.compile-check.js +1 -0
  33. package/dist/mcp.d.ts +2 -0
  34. package/dist/mcp.js +103 -11
  35. package/dist/sdk.compile-check.js +77 -0
  36. package/dist/sdk.d.ts +14 -5
  37. package/dist/sdk.js +57 -6
  38. package/dist/user-error.d.ts +3 -0
  39. package/dist/user-error.js +6 -0
  40. package/node_modules/@poe-code/agent-human-in-loop/README.md +42 -0
  41. package/node_modules/@poe-code/agent-human-in-loop/dist/index.d.ts +5 -0
  42. package/node_modules/@poe-code/agent-human-in-loop/dist/index.js +3 -0
  43. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/mock.d.ts +2 -0
  44. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/mock.js +11 -0
  45. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript-script.d.ts +4 -0
  46. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript-script.js +40 -0
  47. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript.d.ts +6 -0
  48. package/node_modules/@poe-code/agent-human-in-loop/dist/providers/osascript.js +33 -0
  49. package/node_modules/@poe-code/agent-human-in-loop/dist/request-approval.d.ts +4 -0
  50. package/node_modules/@poe-code/agent-human-in-loop/dist/request-approval.js +4 -0
  51. package/node_modules/@poe-code/agent-human-in-loop/dist/types.d.ts +14 -0
  52. package/node_modules/@poe-code/agent-human-in-loop/dist/types.js +1 -0
  53. package/node_modules/@poe-code/agent-human-in-loop/package.json +25 -0
  54. package/node_modules/@poe-code/agent-mcp-config/dist/apply.d.ts +6 -0
  55. package/node_modules/@poe-code/agent-mcp-config/dist/apply.js +175 -0
  56. package/node_modules/@poe-code/agent-mcp-config/dist/configs.d.ts +22 -0
  57. package/node_modules/@poe-code/agent-mcp-config/dist/configs.js +74 -0
  58. package/node_modules/@poe-code/agent-mcp-config/dist/index.d.ts +3 -0
  59. package/node_modules/@poe-code/agent-mcp-config/dist/index.js +2 -0
  60. package/node_modules/@poe-code/agent-mcp-config/dist/shapes.d.ts +31 -0
  61. package/node_modules/@poe-code/agent-mcp-config/dist/shapes.js +87 -0
  62. package/node_modules/@poe-code/agent-mcp-config/dist/types.d.ts +25 -0
  63. package/node_modules/@poe-code/agent-mcp-config/dist/types.js +1 -0
  64. package/node_modules/@poe-code/agent-mcp-config/package.json +25 -0
  65. package/node_modules/@poe-code/task-list/README.md +114 -0
  66. package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.d.ts +2 -0
  67. package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +466 -0
  68. package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +8 -0
  69. package/node_modules/@poe-code/task-list/dist/backends/utils.js +58 -0
  70. package/node_modules/@poe-code/task-list/dist/backends/yaml-file.d.ts +2 -0
  71. package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +444 -0
  72. package/node_modules/@poe-code/task-list/dist/index.d.ts +4 -0
  73. package/node_modules/@poe-code/task-list/dist/index.js +4 -0
  74. package/node_modules/@poe-code/task-list/dist/open.d.ts +3 -0
  75. package/node_modules/@poe-code/task-list/dist/open.js +34 -0
  76. package/node_modules/@poe-code/task-list/dist/schema/store.schema.json +32 -0
  77. package/node_modules/@poe-code/task-list/dist/schema/task.schema.json +33 -0
  78. package/node_modules/@poe-code/task-list/dist/state-machine.d.ts +16 -0
  79. package/node_modules/@poe-code/task-list/dist/state-machine.js +67 -0
  80. package/node_modules/@poe-code/task-list/dist/state.d.ts +29 -0
  81. package/node_modules/@poe-code/task-list/dist/state.js +61 -0
  82. package/node_modules/@poe-code/task-list/dist/types.d.ts +116 -0
  83. package/node_modules/@poe-code/task-list/dist/types.js +37 -0
  84. package/node_modules/@poe-code/task-list/package.json +26 -0
  85. package/package.json +22 -7
@@ -0,0 +1,444 @@
1
+ import { acquireFileLock } from "@poe-code/file-lock";
2
+ import { parseDocument } from "yaml";
3
+ import storeSchema from "../schema/store.schema.json" with { type: "json" };
4
+ import taskSchema from "../schema/task.schema.json" with { type: "json" };
5
+ import { eventsFromState, findEvent } from "../state-machine.js";
6
+ import { resolveStateMachine } from "../state.js";
7
+ import { InvalidTransitionError, MalformedTaskError, TaskAlreadyExistsError, TaskNotFoundError } from "../types.js";
8
+ import { isRecord, sortStrings, sortTasks, statIfExists, validateTaskId, writeAtomically } from "./utils.js";
9
+ const STORE_KIND = "task-store";
10
+ const STORE_SCHEMA_ID = storeSchema.$id;
11
+ const STORE_VERSION = 1;
12
+ const TASK_KIND = "task";
13
+ const TASK_SCHEMA_ID = taskSchema.$id;
14
+ const TASK_VERSION = 1;
15
+ const RESERVED_TASK_KEYS = new Set(["$schema", "description", "kind", "name", "state", "version"]);
16
+ function malformedStore(filePath, field) {
17
+ return new MalformedTaskError(`Malformed task store "${filePath}": invalid "${field}".`);
18
+ }
19
+ function malformedTask(list, id, field) {
20
+ return new MalformedTaskError(`Malformed task "${list}/${id}": invalid "${field}".`);
21
+ }
22
+ function validateListName(name) {
23
+ if (name.length === 0 ||
24
+ name.startsWith(".") ||
25
+ name.includes("/") ||
26
+ name.includes("\\") ||
27
+ name.includes("..")) {
28
+ throw new Error(`Invalid task list name "${name}".`);
29
+ }
30
+ return name;
31
+ }
32
+ function parseQualifiedId(qualifiedId) {
33
+ const separatorIndex = qualifiedId.indexOf("/");
34
+ if (separatorIndex <= 0 ||
35
+ separatorIndex !== qualifiedId.lastIndexOf("/") ||
36
+ separatorIndex === qualifiedId.length - 1) {
37
+ throw new Error(`Invalid qualified task id "${qualifiedId}".`);
38
+ }
39
+ return {
40
+ list: validateListName(qualifiedId.slice(0, separatorIndex)),
41
+ id: validateTaskId(qualifiedId.slice(separatorIndex + 1))
42
+ };
43
+ }
44
+ function descriptionFromTaskRecord(taskRecord) {
45
+ return typeof taskRecord.description === "string" ? taskRecord.description : "";
46
+ }
47
+ function metadataFromTaskRecord(taskRecord) {
48
+ const metadata = {};
49
+ for (const [key, value] of Object.entries(taskRecord)) {
50
+ if (!RESERVED_TASK_KEYS.has(key)) {
51
+ metadata[key] = value;
52
+ }
53
+ }
54
+ return metadata;
55
+ }
56
+ function createTask(list, id, taskRecord) {
57
+ return {
58
+ list,
59
+ id,
60
+ qualifiedId: `${list}/${id}`,
61
+ name: taskRecord.name,
62
+ state: taskRecord.state,
63
+ description: descriptionFromTaskRecord(taskRecord),
64
+ metadata: metadataFromTaskRecord(taskRecord)
65
+ };
66
+ }
67
+ function matchesFilter(task, filter) {
68
+ if (!filter?.includeArchived && task.state === "archived") {
69
+ return false;
70
+ }
71
+ if (filter?.state !== undefined && task.state !== filter.state) {
72
+ return false;
73
+ }
74
+ return true;
75
+ }
76
+ function createTaskRecord(defaults, input, initialState) {
77
+ const taskRecord = {
78
+ name: input.name,
79
+ state: initialState,
80
+ description: input.description ?? ""
81
+ };
82
+ for (const [key, value] of Object.entries(defaults.metadata)) {
83
+ if (!RESERVED_TASK_KEYS.has(key)) {
84
+ taskRecord[key] = value;
85
+ }
86
+ }
87
+ for (const [key, value] of Object.entries(input.metadata ?? {})) {
88
+ if (!RESERVED_TASK_KEYS.has(key)) {
89
+ taskRecord[key] = value;
90
+ }
91
+ }
92
+ return taskRecord;
93
+ }
94
+ function assertCreateDoesNotSetState(input) {
95
+ if (Object.prototype.hasOwnProperty.call(input, "state")) {
96
+ throw new Error('Tasks.create() does not accept "state"; new tasks always start at stateMachine.initial.');
97
+ }
98
+ }
99
+ function assertUpdateDoesNotSetState(patch) {
100
+ if (Object.prototype.hasOwnProperty.call(patch, "state")) {
101
+ throw new Error('Tasks.update() does not accept "state"; use fire() to change task state.');
102
+ }
103
+ }
104
+ function buildUpdatedTaskRecord(existing, patch) {
105
+ const nextTaskRecord = {
106
+ ...existing,
107
+ name: patch.name ?? existing.name,
108
+ state: existing.state,
109
+ description: patch.description ?? descriptionFromTaskRecord(existing)
110
+ };
111
+ for (const [key, value] of Object.entries(patch.metadata ?? {})) {
112
+ if (!RESERVED_TASK_KEYS.has(key)) {
113
+ nextTaskRecord[key] = value;
114
+ }
115
+ }
116
+ return nextTaskRecord;
117
+ }
118
+ function buildTransitionedTaskRecord(existing, to) {
119
+ return {
120
+ ...existing,
121
+ state: to,
122
+ description: descriptionFromTaskRecord(existing)
123
+ };
124
+ }
125
+ function buildFiredTaskRecord(existing, to, metadataPatch) {
126
+ const nextTaskRecord = buildTransitionedTaskRecord(existing, to);
127
+ for (const [key, value] of Object.entries(metadataPatch ?? {})) {
128
+ if (!RESERVED_TASK_KEYS.has(key)) {
129
+ nextTaskRecord[key] = value;
130
+ }
131
+ }
132
+ return nextTaskRecord;
133
+ }
134
+ function parseStoreDocument(filePath, content) {
135
+ let document;
136
+ try {
137
+ document = parseDocument(content, { keepSourceTokens: true, prettyErrors: false });
138
+ }
139
+ catch {
140
+ throw malformedStore(filePath, "yaml");
141
+ }
142
+ if (document.errors.length > 0) {
143
+ throw malformedStore(filePath, "yaml");
144
+ }
145
+ return document;
146
+ }
147
+ function assertValidStoreRecord(store, filePath) {
148
+ if (!isRecord(store)) {
149
+ throw malformedStore(filePath, "store");
150
+ }
151
+ if (store.$schema !== STORE_SCHEMA_ID) {
152
+ throw malformedStore(filePath, "$schema");
153
+ }
154
+ if (store.kind !== STORE_KIND) {
155
+ throw malformedStore(filePath, "kind");
156
+ }
157
+ if (typeof store.version !== "number" || !Number.isInteger(store.version) || store.version !== STORE_VERSION) {
158
+ throw malformedStore(filePath, "version");
159
+ }
160
+ if (!isRecord(store.lists)) {
161
+ throw malformedStore(filePath, "lists");
162
+ }
163
+ }
164
+ function assertValidTaskRecord(taskRecord, list, id, validStates) {
165
+ if (!isRecord(taskRecord)) {
166
+ throw malformedTask(list, id, "task");
167
+ }
168
+ if ("$schema" in taskRecord && taskRecord.$schema !== TASK_SCHEMA_ID) {
169
+ throw malformedTask(list, id, "$schema");
170
+ }
171
+ if ("kind" in taskRecord && taskRecord.kind !== TASK_KIND) {
172
+ throw malformedTask(list, id, "kind");
173
+ }
174
+ if ("version" in taskRecord) {
175
+ if (typeof taskRecord.version !== "number" ||
176
+ !Number.isInteger(taskRecord.version) ||
177
+ taskRecord.version !== TASK_VERSION) {
178
+ throw malformedTask(list, id, "version");
179
+ }
180
+ }
181
+ if (typeof taskRecord.name !== "string" || taskRecord.name.length === 0) {
182
+ throw malformedTask(list, id, "name");
183
+ }
184
+ if (typeof taskRecord.state !== "string" || !validStates.has(taskRecord.state)) {
185
+ throw malformedTask(list, id, "state");
186
+ }
187
+ if ("description" in taskRecord && typeof taskRecord.description !== "string") {
188
+ throw malformedTask(list, id, "description");
189
+ }
190
+ }
191
+ function validateStoreEntries(store, filePath, validStates) {
192
+ const lists = store.lists;
193
+ if (!isRecord(lists)) {
194
+ throw malformedStore(filePath, "lists");
195
+ }
196
+ for (const [list, listRecord] of Object.entries(lists)) {
197
+ try {
198
+ validateListName(list);
199
+ }
200
+ catch {
201
+ throw malformedStore(filePath, `lists.${list}`);
202
+ }
203
+ if (!isRecord(listRecord)) {
204
+ throw malformedStore(filePath, `lists.${list}`);
205
+ }
206
+ for (const [id, taskRecord] of Object.entries(listRecord)) {
207
+ try {
208
+ validateTaskId(id);
209
+ }
210
+ catch {
211
+ throw malformedTask(list, id, "id");
212
+ }
213
+ assertValidTaskRecord(taskRecord, list, id, validStates);
214
+ }
215
+ }
216
+ }
217
+ function serializeDocument(document) {
218
+ const serialized = document.toString();
219
+ return serialized.endsWith("\n") ? serialized : `${serialized}\n`;
220
+ }
221
+ async function readStore(fs, filePath, validStates) {
222
+ const content = await fs.readFile(filePath, "utf8");
223
+ const document = parseStoreDocument(filePath, content);
224
+ const store = document.toJS();
225
+ assertValidStoreRecord(store, filePath);
226
+ validateStoreEntries(store, filePath, validStates);
227
+ return {
228
+ document,
229
+ store
230
+ };
231
+ }
232
+ function getListsRecord(store) {
233
+ return store.lists;
234
+ }
235
+ function getListRecord(store, list) {
236
+ const listRecord = getListsRecord(store)[list];
237
+ return isRecord(listRecord) ? listRecord : undefined;
238
+ }
239
+ function getTaskRecord(store, list, id) {
240
+ const listRecord = getListRecord(store, list);
241
+ const taskRecord = listRecord?.[id];
242
+ return isRecord(taskRecord) ? taskRecord : undefined;
243
+ }
244
+ function getTaskOrThrow(store, list, id) {
245
+ const taskRecord = getTaskRecord(store, list, id);
246
+ if (!taskRecord) {
247
+ throw new TaskNotFoundError(`Task "${list}/${id}" not found.`);
248
+ }
249
+ return taskRecord;
250
+ }
251
+ async function ensureStorePath(deps) {
252
+ if (!deps.create) {
253
+ await deps.fs.stat(deps.path);
254
+ return;
255
+ }
256
+ const existing = await statIfExists(deps.fs, deps.path);
257
+ if (existing !== undefined) {
258
+ return;
259
+ }
260
+ await writeAtomically(deps.fs, deps.path, serializeDocument(parseDocument([
261
+ `$schema: ${STORE_SCHEMA_ID}`,
262
+ `kind: ${STORE_KIND}`,
263
+ `version: ${STORE_VERSION}`,
264
+ "lists: {}",
265
+ ""
266
+ ].join("\n"))));
267
+ }
268
+ async function withStoreLock(deps, action) {
269
+ const release = await acquireFileLock(deps.path, {
270
+ fs: deps.fs,
271
+ staleMs: deps.lockStaleMs,
272
+ retries: deps.lockRetries
273
+ });
274
+ try {
275
+ return await action();
276
+ }
277
+ finally {
278
+ await release();
279
+ }
280
+ }
281
+ function createTasksView(deps, list) {
282
+ const stateMachine = resolveStateMachine(deps.stateMachine);
283
+ const validStates = new Set(stateMachine.states);
284
+ async function readTasks(filter) {
285
+ const { store } = await readStore(deps.fs, deps.path, validStates);
286
+ const listRecord = getListRecord(store, list);
287
+ if (!listRecord) {
288
+ return [];
289
+ }
290
+ const tasks = Object.entries(listRecord).map(([id, taskRecord]) => createTask(list, id, taskRecord));
291
+ return sortTasks(tasks.filter((task) => matchesFilter(task, filter)));
292
+ }
293
+ function assertFireableTaskEvent(task, eventName) {
294
+ const event = findEvent(stateMachine, task.state, eventName);
295
+ if (event === undefined) {
296
+ throw new InvalidTransitionError({
297
+ task,
298
+ event: eventName,
299
+ to: stateMachine.events[eventName]?.to,
300
+ reason: `Cannot fire event "${eventName}" from task state "${task.state}".`
301
+ });
302
+ }
303
+ return event;
304
+ }
305
+ return {
306
+ name: list,
307
+ stateMachine,
308
+ async all(filter) {
309
+ return readTasks(filter);
310
+ },
311
+ async get(id) {
312
+ validateTaskId(id);
313
+ const { store } = await readStore(deps.fs, deps.path, validStates);
314
+ return createTask(list, id, getTaskOrThrow(store, list, id));
315
+ },
316
+ async create(input) {
317
+ assertCreateDoesNotSetState(input);
318
+ validateTaskId(input.id);
319
+ return withStoreLock(deps, async () => {
320
+ const { document, store } = await readStore(deps.fs, deps.path, validStates);
321
+ if (getTaskRecord(store, list, input.id)) {
322
+ throw new TaskAlreadyExistsError(`Task "${list}/${input.id}" already exists.`);
323
+ }
324
+ const taskRecord = createTaskRecord(deps.defaults, input, stateMachine.initial);
325
+ document.setIn(["lists", list, input.id], taskRecord);
326
+ await writeAtomically(deps.fs, deps.path, serializeDocument(document));
327
+ return createTask(list, input.id, taskRecord);
328
+ });
329
+ },
330
+ async update(id, patch) {
331
+ assertUpdateDoesNotSetState(patch);
332
+ validateTaskId(id);
333
+ return withStoreLock(deps, async () => {
334
+ const { document, store } = await readStore(deps.fs, deps.path, validStates);
335
+ const existing = getTaskOrThrow(store, list, id);
336
+ const nextTaskRecord = buildUpdatedTaskRecord(existing, patch);
337
+ if (patch.name !== undefined) {
338
+ document.setIn(["lists", list, id, "name"], patch.name);
339
+ }
340
+ if (patch.description !== undefined) {
341
+ document.setIn(["lists", list, id, "description"], patch.description);
342
+ }
343
+ for (const [key, value] of Object.entries(patch.metadata ?? {})) {
344
+ if (!RESERVED_TASK_KEYS.has(key)) {
345
+ document.setIn(["lists", list, id, key], value);
346
+ }
347
+ }
348
+ await writeAtomically(deps.fs, deps.path, serializeDocument(document));
349
+ return createTask(list, id, nextTaskRecord);
350
+ });
351
+ },
352
+ async fire(id, eventName, opts) {
353
+ validateTaskId(id);
354
+ return withStoreLock(deps, async () => {
355
+ const { document, store } = await readStore(deps.fs, deps.path, validStates);
356
+ const existing = getTaskOrThrow(store, list, id);
357
+ const task = createTask(list, id, existing);
358
+ const event = assertFireableTaskEvent(task, eventName);
359
+ const guardResult = event.guard?.(task) ?? true;
360
+ if (guardResult !== true) {
361
+ throw new InvalidTransitionError({
362
+ task,
363
+ event: eventName,
364
+ to: event.to,
365
+ reason: guardResult
366
+ });
367
+ }
368
+ await event.onExit?.(task);
369
+ const nextTaskRecord = buildFiredTaskRecord(existing, event.to, opts?.metadataPatch);
370
+ document.setIn(["lists", list, id, "state"], event.to);
371
+ for (const [key, value] of Object.entries(opts?.metadataPatch ?? {})) {
372
+ if (!RESERVED_TASK_KEYS.has(key)) {
373
+ document.setIn(["lists", list, id, key], value);
374
+ }
375
+ }
376
+ await writeAtomically(deps.fs, deps.path, serializeDocument(document));
377
+ const nextTask = createTask(list, id, nextTaskRecord);
378
+ await event.onEnter?.(nextTask);
379
+ return nextTask;
380
+ });
381
+ },
382
+ async canFire(id, eventName) {
383
+ validateTaskId(id);
384
+ const { store } = await readStore(deps.fs, deps.path, validStates);
385
+ const task = createTask(list, id, getTaskOrThrow(store, list, id));
386
+ const event = findEvent(stateMachine, task.state, eventName);
387
+ if (event === undefined) {
388
+ return false;
389
+ }
390
+ return (event.guard?.(task) ?? true) === true;
391
+ },
392
+ async events(id) {
393
+ validateTaskId(id);
394
+ const { store } = await readStore(deps.fs, deps.path, validStates);
395
+ const task = createTask(list, id, getTaskOrThrow(store, list, id));
396
+ return eventsFromState(stateMachine, task.state);
397
+ },
398
+ async delete(id) {
399
+ validateTaskId(id);
400
+ await withStoreLock(deps, async () => {
401
+ const { document, store } = await readStore(deps.fs, deps.path, validStates);
402
+ getTaskOrThrow(store, list, id);
403
+ document.deleteIn(["lists", list, id]);
404
+ await writeAtomically(deps.fs, deps.path, serializeDocument(document));
405
+ });
406
+ }
407
+ };
408
+ }
409
+ export async function yamlFileBackend(deps) {
410
+ await ensureStorePath(deps);
411
+ const stateMachine = resolveStateMachine(deps.stateMachine);
412
+ const validStates = new Set(stateMachine.states);
413
+ const list = (name) => {
414
+ const listName = validateListName(name);
415
+ return createTasksView({ ...deps, stateMachine }, listName);
416
+ };
417
+ const lists = async () => {
418
+ const { store } = await readStore(deps.fs, deps.path, validStates);
419
+ return sortStrings(Object.keys(getListsRecord(store)));
420
+ };
421
+ const allTasks = async (filter) => {
422
+ const { store } = await readStore(deps.fs, deps.path, validStates);
423
+ const tasks = [];
424
+ for (const [listName, listRecord] of Object.entries(getListsRecord(store))) {
425
+ for (const [id, taskRecord] of Object.entries(listRecord)) {
426
+ const task = createTask(listName, id, taskRecord);
427
+ if (matchesFilter(task, filter)) {
428
+ tasks.push(task);
429
+ }
430
+ }
431
+ }
432
+ return sortTasks(tasks);
433
+ };
434
+ const get = async (qualifiedId) => {
435
+ const { list: listName, id } = parseQualifiedId(qualifiedId);
436
+ return list(listName).get(id);
437
+ };
438
+ return {
439
+ list,
440
+ lists,
441
+ allTasks,
442
+ get
443
+ };
444
+ }
@@ -0,0 +1,4 @@
1
+ export { openTaskList } from "./open.js";
2
+ export { assertEvent, assertTransition, defaultStateMachine, type TaskEvent } from "./state.js";
3
+ export { eventsFromState, findEvent, validateMachine, type EventDef, type StateMachineDef } from "./state-machine.js";
4
+ export { InvalidTransitionError, MalformedTaskError, TaskAlreadyExistsError, TaskNotFoundError, type ListFilter, type OpenTaskListOptions, type Task, type TaskCreate, type TaskDefaults, type TaskFireOptions, type TaskList, type TaskListFs, type TaskState, type Tasks, type TaskUpdate } from "./types.js";
@@ -0,0 +1,4 @@
1
+ export { openTaskList } from "./open.js";
2
+ export { assertEvent, assertTransition, defaultStateMachine } from "./state.js";
3
+ export { eventsFromState, findEvent, validateMachine } from "./state-machine.js";
4
+ export { InvalidTransitionError, MalformedTaskError, TaskAlreadyExistsError, TaskNotFoundError } from "./types.js";
@@ -0,0 +1,3 @@
1
+ import type { BackendFactory, OpenTaskListOptions, TaskList } from "./types.js";
2
+ export declare const backendFactories: Record<OpenTaskListOptions["type"], BackendFactory>;
3
+ export declare function openTaskList(options: OpenTaskListOptions): Promise<TaskList>;
@@ -0,0 +1,34 @@
1
+ import * as fsPromises from "node:fs/promises";
2
+ import { markdownDirBackend } from "./backends/markdown-dir.js";
3
+ import { yamlFileBackend } from "./backends/yaml-file.js";
4
+ import { validateMachine } from "./state-machine.js";
5
+ import { resolveStateMachine } from "./state.js";
6
+ const DEFAULT_LOCK_STALE_MS = 30_000;
7
+ const DEFAULT_LOCK_RETRIES = 20;
8
+ export const backendFactories = {
9
+ "markdown-dir": markdownDirBackend,
10
+ "yaml-file": yamlFileBackend
11
+ };
12
+ function createDefaultFs() {
13
+ return fsPromises;
14
+ }
15
+ export async function openTaskList(options) {
16
+ const factory = backendFactories[options.type];
17
+ if (factory === undefined) {
18
+ throw new Error(`Unknown task list backend type "${options.type}".`);
19
+ }
20
+ const stateMachine = resolveStateMachine(options.stateMachine);
21
+ validateMachine(stateMachine);
22
+ const deps = {
23
+ path: options.path,
24
+ defaults: {
25
+ metadata: { ...(options.defaults?.metadata ?? {}) }
26
+ },
27
+ lockStaleMs: options.lockStaleMs ?? DEFAULT_LOCK_STALE_MS,
28
+ lockRetries: options.lockRetries ?? DEFAULT_LOCK_RETRIES,
29
+ create: options.create ?? false,
30
+ fs: options.fs ?? createDefaultFs(),
31
+ stateMachine
32
+ };
33
+ return factory(deps);
34
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://poe-platform.github.io/poe-code/schemas/task-list/store.schema.json",
4
+ "title": "Task Store",
5
+ "description": "YAML multi-list task store.",
6
+ "type": "object",
7
+ "properties": {
8
+ "$schema": {
9
+ "type": "string",
10
+ "const": "https://poe-platform.github.io/poe-code/schemas/task-list/store.schema.json"
11
+ },
12
+ "kind": {
13
+ "type": "string",
14
+ "const": "task-store"
15
+ },
16
+ "version": {
17
+ "type": "integer",
18
+ "const": 1
19
+ },
20
+ "lists": {
21
+ "type": "object",
22
+ "additionalProperties": {
23
+ "type": "object",
24
+ "additionalProperties": {
25
+ "$ref": "./task.schema.json"
26
+ }
27
+ }
28
+ }
29
+ },
30
+ "required": ["$schema", "kind", "version", "lists"],
31
+ "additionalProperties": false
32
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://poe-platform.github.io/poe-code/schemas/task-list/task.schema.json",
4
+ "title": "Task",
5
+ "description": "Persisted task payload used by task-list backends.",
6
+ "type": "object",
7
+ "properties": {
8
+ "$schema": {
9
+ "type": "string",
10
+ "const": "https://poe-platform.github.io/poe-code/schemas/task-list/task.schema.json"
11
+ },
12
+ "kind": {
13
+ "type": "string",
14
+ "const": "task"
15
+ },
16
+ "version": {
17
+ "type": "integer",
18
+ "const": 1
19
+ },
20
+ "name": {
21
+ "type": "string",
22
+ "minLength": 1
23
+ },
24
+ "state": {
25
+ "type": "string"
26
+ },
27
+ "description": {
28
+ "type": "string"
29
+ }
30
+ },
31
+ "required": ["name", "state"],
32
+ "additionalProperties": true
33
+ }
@@ -0,0 +1,16 @@
1
+ import type { Task } from "./types.js";
2
+ export interface StateMachineDef<TState extends string = string, TEvent extends string = string> {
3
+ readonly initial: TState;
4
+ readonly states: readonly TState[];
5
+ readonly events: Readonly<Record<TEvent, EventDef<TState>>>;
6
+ }
7
+ export interface EventDef<TState extends string = string> {
8
+ readonly from: readonly TState[] | "*";
9
+ readonly to: TState;
10
+ readonly guard?: (task: Task) => true | string;
11
+ readonly onEnter?: (task: Task) => void | Promise<void>;
12
+ readonly onExit?: (task: Task) => void | Promise<void>;
13
+ }
14
+ export declare function validateMachine(machine: StateMachineDef): void;
15
+ export declare function eventsFromState<TState extends string, TEvent extends string>(machine: StateMachineDef<TState, TEvent>, fromState: TState): readonly TEvent[];
16
+ export declare function findEvent<TState extends string, TEvent extends string>(machine: StateMachineDef<TState, TEvent>, fromState: TState, eventName: TEvent): EventDef<TState> | undefined;
@@ -0,0 +1,67 @@
1
+ function isRecord(value) {
2
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3
+ }
4
+ function isStateList(value) {
5
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string");
6
+ }
7
+ function canFireFromState(event, fromState) {
8
+ if (event.from === "*") {
9
+ return event.to !== fromState;
10
+ }
11
+ return event.from.includes(fromState);
12
+ }
13
+ export function validateMachine(machine) {
14
+ if (!isRecord(machine)) {
15
+ throw new TypeError("State machine must be an object.");
16
+ }
17
+ if (!isStateList(machine.states)) {
18
+ throw new TypeError("State machine states must be a string array.");
19
+ }
20
+ const states = new Set(machine.states);
21
+ if (typeof machine.initial !== "string") {
22
+ throw new TypeError("State machine initial must be a string.");
23
+ }
24
+ if (!states.has(machine.initial)) {
25
+ throw new Error(`Initial state "${machine.initial}" is not declared.`);
26
+ }
27
+ if (!isRecord(machine.events)) {
28
+ throw new TypeError("State machine events must be an object.");
29
+ }
30
+ for (const [eventName, event] of Object.entries(machine.events)) {
31
+ if (!isRecord(event)) {
32
+ throw new TypeError(`Event "${eventName}" must be an object.`);
33
+ }
34
+ if (event.from !== "*" && !isStateList(event.from)) {
35
+ throw new TypeError(`Event "${eventName}" has an invalid "from" definition.`);
36
+ }
37
+ if (typeof event.to !== "string") {
38
+ throw new TypeError(`Event "${eventName}" target state must be a string.`);
39
+ }
40
+ if (!states.has(event.to)) {
41
+ throw new Error(`Event "${eventName}" references unknown target state "${event.to}".`);
42
+ }
43
+ if (event.from !== "*") {
44
+ for (const fromState of event.from) {
45
+ if (!states.has(fromState)) {
46
+ throw new Error(`Event "${eventName}" references unknown source state "${fromState}".`);
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
52
+ export function eventsFromState(machine, fromState) {
53
+ const events = [];
54
+ for (const [eventName, event] of Object.entries(machine.events)) {
55
+ if (canFireFromState(event, fromState)) {
56
+ events.push(eventName);
57
+ }
58
+ }
59
+ return events;
60
+ }
61
+ export function findEvent(machine, fromState, eventName) {
62
+ const event = machine.events[eventName];
63
+ if (event === undefined) {
64
+ return undefined;
65
+ }
66
+ return canFireFromState(event, fromState) ? event : undefined;
67
+ }
@@ -0,0 +1,29 @@
1
+ import { type EventDef, type StateMachineDef } from "./state-machine.js";
2
+ import { type TaskState } from "./types.js";
3
+ export type TaskEvent = "plan" | "start" | "complete" | "archive";
4
+ export declare const defaultStateMachine: {
5
+ readonly initial: "draft";
6
+ readonly states: readonly ["draft", "planned", "in-progress", "done", "archived"];
7
+ readonly events: {
8
+ readonly plan: {
9
+ readonly from: readonly ["draft"];
10
+ readonly to: "planned";
11
+ };
12
+ readonly start: {
13
+ readonly from: readonly ["planned"];
14
+ readonly to: "in-progress";
15
+ };
16
+ readonly complete: {
17
+ readonly from: readonly ["in-progress"];
18
+ readonly to: "done";
19
+ };
20
+ readonly archive: {
21
+ readonly from: "*";
22
+ readonly to: "archived";
23
+ };
24
+ };
25
+ };
26
+ export declare function resolveStateMachine(stateMachine?: StateMachineDef): StateMachineDef;
27
+ export declare function assertEvent<TState extends string, TEvent extends string>(machine: StateMachineDef<TState, TEvent>, fromState: TState, eventName: TEvent): EventDef<TState>;
28
+ export declare function assertTransition(from: TaskState, to: TaskState): void;
29
+ export declare function assertTransition<TState extends string, TEvent extends string>(machine: StateMachineDef<TState, TEvent>, from: TState, to: TState): void;