@workflow-cannon/workspace-kit 0.18.0 → 0.24.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.
Files changed (140) hide show
  1. package/README.md +23 -9
  2. package/dist/cli/doctor-planning-issues.js +3 -22
  3. package/dist/cli/run-command.js +22 -38
  4. package/dist/cli.js +95 -4
  5. package/dist/contracts/command-manifest.d.ts +17 -0
  6. package/dist/contracts/command-manifest.js +1 -0
  7. package/dist/contracts/index.d.ts +1 -1
  8. package/dist/contracts/module-contract.d.ts +12 -11
  9. package/dist/core/agent-instruction-surface.d.ts +33 -0
  10. package/dist/core/agent-instruction-surface.js +46 -0
  11. package/dist/core/config-cli.js +13 -17
  12. package/dist/core/config-metadata.js +61 -2
  13. package/dist/core/index.d.ts +4 -1
  14. package/dist/core/index.js +3 -0
  15. package/dist/core/module-command-router.js +19 -1
  16. package/dist/core/module-registry-resolve.d.ts +27 -0
  17. package/dist/core/module-registry-resolve.js +91 -0
  18. package/dist/core/module-registry.d.ts +14 -0
  19. package/dist/core/module-registry.js +57 -0
  20. package/dist/core/planning/build-plan-session-file.d.ts +29 -0
  21. package/dist/core/planning/build-plan-session-file.js +58 -0
  22. package/dist/core/planning/index.d.ts +17 -0
  23. package/dist/core/planning/index.js +15 -0
  24. package/dist/core/policy.js +18 -8
  25. package/dist/core/state/unified-state-db.d.ts +21 -0
  26. package/dist/core/state/unified-state-db.js +80 -0
  27. package/dist/core/workspace-kit-config.js +8 -0
  28. package/dist/modules/agent-behavior/builtins.d.ts +3 -0
  29. package/dist/modules/agent-behavior/builtins.js +71 -0
  30. package/dist/modules/agent-behavior/explain.d.ts +6 -0
  31. package/dist/modules/agent-behavior/explain.js +46 -0
  32. package/dist/modules/agent-behavior/index.d.ts +4 -0
  33. package/dist/modules/agent-behavior/index.js +461 -0
  34. package/dist/modules/agent-behavior/interview-session-file.d.ts +9 -0
  35. package/dist/modules/agent-behavior/interview-session-file.js +43 -0
  36. package/dist/modules/agent-behavior/interview.d.ts +13 -0
  37. package/dist/modules/agent-behavior/interview.js +88 -0
  38. package/dist/modules/agent-behavior/persistence.d.ts +6 -0
  39. package/dist/modules/agent-behavior/persistence.js +89 -0
  40. package/dist/modules/agent-behavior/store.d.ts +34 -0
  41. package/dist/modules/agent-behavior/store.js +119 -0
  42. package/dist/modules/agent-behavior/types.d.ts +28 -0
  43. package/dist/modules/agent-behavior/types.js +1 -0
  44. package/dist/modules/agent-behavior/validate.d.ts +11 -0
  45. package/dist/modules/agent-behavior/validate.js +123 -0
  46. package/dist/modules/approvals/index.js +54 -51
  47. package/dist/modules/approvals/policy-sensitive-commands.d.ts +4 -0
  48. package/dist/modules/approvals/policy-sensitive-commands.js +4 -0
  49. package/dist/modules/approvals/review-runtime.js +1 -2
  50. package/dist/modules/documentation/index.js +47 -45
  51. package/dist/modules/documentation/normalizer.d.ts +3 -0
  52. package/dist/modules/documentation/normalizer.js +171 -0
  53. package/dist/modules/documentation/parser.d.ts +7 -0
  54. package/dist/modules/documentation/parser.js +39 -0
  55. package/dist/modules/documentation/policy-sensitive-commands.d.ts +5 -0
  56. package/dist/modules/documentation/policy-sensitive-commands.js +8 -0
  57. package/dist/modules/documentation/renderer.d.ts +23 -0
  58. package/dist/modules/documentation/renderer.js +105 -0
  59. package/dist/modules/documentation/runtime-batch.d.ts +10 -0
  60. package/dist/modules/documentation/runtime-batch.js +67 -0
  61. package/dist/modules/documentation/runtime-config.d.ts +11 -0
  62. package/dist/modules/documentation/runtime-config.js +54 -0
  63. package/dist/modules/documentation/runtime-render-support.d.ts +8 -0
  64. package/dist/modules/documentation/runtime-render-support.js +36 -0
  65. package/dist/modules/documentation/runtime.js +22 -510
  66. package/dist/modules/documentation/types.d.ts +182 -0
  67. package/dist/modules/documentation/validator.d.ts +8 -0
  68. package/dist/modules/documentation/validator.js +234 -0
  69. package/dist/modules/documentation/view-models.d.ts +3 -0
  70. package/dist/modules/documentation/view-models.js +124 -0
  71. package/dist/modules/improvement/generate-recommendations-runtime.js +3 -3
  72. package/dist/modules/improvement/improvement-state.d.ts +2 -2
  73. package/dist/modules/improvement/improvement-state.js +52 -23
  74. package/dist/modules/improvement/index.js +140 -138
  75. package/dist/modules/improvement/ingest.d.ts +1 -1
  76. package/dist/modules/improvement/policy-sensitive-commands.d.ts +4 -0
  77. package/dist/modules/improvement/policy-sensitive-commands.js +7 -0
  78. package/dist/modules/index.d.ts +6 -0
  79. package/dist/modules/index.js +17 -0
  80. package/dist/modules/planning/index.js +384 -50
  81. package/dist/modules/planning/question-engine.d.ts +2 -0
  82. package/dist/modules/planning/question-engine.js +8 -1
  83. package/dist/modules/task-engine/doctor-planning-persistence.js +21 -13
  84. package/dist/modules/task-engine/index.d.ts +1 -2
  85. package/dist/modules/task-engine/index.js +1 -1143
  86. package/dist/modules/task-engine/migrate-task-persistence-runtime.js +31 -4
  87. package/dist/modules/task-engine/migrate-wishlist-intake-runtime.d.ts +2 -0
  88. package/dist/modules/task-engine/migrate-wishlist-intake-runtime.js +146 -0
  89. package/dist/modules/task-engine/planning-open.d.ts +2 -9
  90. package/dist/modules/task-engine/planning-open.js +4 -15
  91. package/dist/modules/task-engine/policy-sensitive-commands.d.ts +5 -0
  92. package/dist/modules/task-engine/policy-sensitive-commands.js +5 -0
  93. package/dist/modules/task-engine/sqlite-dual-planning.d.ts +11 -2
  94. package/dist/modules/task-engine/sqlite-dual-planning.js +134 -28
  95. package/dist/modules/task-engine/strict-task-validation.js +3 -0
  96. package/dist/modules/task-engine/suggestions.js +2 -1
  97. package/dist/modules/task-engine/task-engine-internal.d.ts +2 -0
  98. package/dist/modules/task-engine/task-engine-internal.js +1304 -0
  99. package/dist/modules/task-engine/task-type-validation.js +40 -0
  100. package/dist/modules/task-engine/wishlist-intake.d.ts +22 -0
  101. package/dist/modules/task-engine/wishlist-intake.js +180 -0
  102. package/dist/modules/task-engine/wishlist-validation.d.ts +4 -0
  103. package/dist/modules/task-engine/wishlist-validation.js +19 -0
  104. package/dist/modules/workspace-config/index.js +9 -11
  105. package/package.json +2 -2
  106. package/schemas/agent-behavior-profile.schema.json +52 -0
  107. package/schemas/task-engine-run-contracts.schema.json +80 -5
  108. package/src/modules/documentation/README.md +16 -25
  109. package/src/modules/documentation/RULES.md +9 -9
  110. package/src/modules/documentation/index.ts +54 -49
  111. package/src/modules/documentation/instructions/document-project.md +6 -6
  112. package/src/modules/documentation/instructions/generate-document.md +4 -4
  113. package/src/modules/documentation/normalizer.ts +187 -0
  114. package/src/modules/documentation/parser.ts +41 -0
  115. package/src/modules/documentation/policy-sensitive-commands.ts +8 -0
  116. package/src/modules/documentation/renderer.ts +121 -0
  117. package/src/modules/documentation/runtime-batch.ts +74 -0
  118. package/src/modules/documentation/runtime-config.ts +68 -0
  119. package/src/modules/documentation/runtime-render-support.ts +39 -0
  120. package/src/modules/documentation/runtime.ts +28 -600
  121. package/src/modules/documentation/schemas/documentation-schema.md +37 -54
  122. package/src/modules/documentation/types.ts +228 -0
  123. package/src/modules/documentation/validator.ts +247 -0
  124. package/src/modules/documentation/view-models.ts +132 -0
  125. package/src/modules/documentation/views/agents.view.yaml +18 -0
  126. package/src/modules/documentation/views/architecture.view.yaml +18 -0
  127. package/src/modules/documentation/views/principles.view.yaml +18 -0
  128. package/src/modules/documentation/views/readme.view.yaml +18 -0
  129. package/src/modules/documentation/views/releasing.view.yaml +18 -0
  130. package/src/modules/documentation/views/roadmap.view.yaml +18 -0
  131. package/src/modules/documentation/views/runbooks-consumer-cadence.view.yaml +18 -0
  132. package/src/modules/documentation/views/runbooks-parity-validation-flow.view.yaml +18 -0
  133. package/src/modules/documentation/views/runbooks-release-channels.view.yaml +18 -0
  134. package/src/modules/documentation/views/security.view.yaml +18 -0
  135. package/src/modules/documentation/views/support.view.yaml +18 -0
  136. package/src/modules/documentation/views/terms.view.yaml +18 -0
  137. package/src/modules/documentation/views/workbooks-phase2-config-policy-workbook.view.yaml +18 -0
  138. package/src/modules/documentation/views/workbooks-task-engine-workbook.view.yaml +18 -0
  139. package/src/modules/documentation/views/workbooks-transcript-automation-baseline.view.yaml +18 -0
  140. package/src/modules/documentation/state.md +0 -8
@@ -1,15 +1,3 @@
1
- import crypto from "node:crypto";
2
- import { maybeSpawnTranscriptHookAfterCompletion } from "../../core/transcript-completion-hook.js";
3
- import { TransitionService } from "./service.js";
4
- import { TaskEngineError, getAllowedTransitionsFrom } from "./transitions.js";
5
- import { getNextActions } from "./suggestions.js";
6
- import { readWorkspaceStatusSnapshot } from "./dashboard-status.js";
7
- import { openPlanningStores } from "./planning-open.js";
8
- import { runMigrateTaskPersistence } from "./migrate-task-persistence-runtime.js";
9
- import { planningStrictValidationEnabled } from "./planning-config.js";
10
- import { validateTaskSetForStrictMode } from "./strict-task-validation.js";
11
- import { validateKnownTaskTypeRequirements } from "./task-type-validation.js";
12
- import { buildWishlistItemFromIntake, validateWishlistIntakePayload, validateWishlistUpdatePayload, WISHLIST_ID_RE } from "./wishlist-validation.js";
13
1
  export { TaskStore } from "./store.js";
14
2
  export { TransitionService } from "./service.js";
15
3
  export { TaskEngineError, TransitionValidator, isTransitionAllowed, getTransitionAction, resolveTargetState, getAllowedTransitionsFrom, stateValidityGuard, dependencyCheckGuard } from "./transitions.js";
@@ -19,1134 +7,4 @@ export { WishlistStore } from "./wishlist-store.js";
19
7
  export { validateWishlistIntakePayload, validateWishlistUpdatePayload, buildWishlistItemFromIntake, WISHLIST_ID_RE } from "./wishlist-validation.js";
20
8
  export { openPlanningStores } from "./planning-open.js";
21
9
  export { getTaskPersistenceBackend, planningSqliteDatabaseRelativePath, planningTaskStoreRelativePath, planningWishlistStoreRelativePath } from "./planning-config.js";
22
- const TASK_ID_RE = /^T\d+$/;
23
- const SAFE_METADATA_PATH_RE = /^[a-zA-Z0-9_-]+(?:\.[a-zA-Z0-9_-]+)*$/;
24
- const MUTABLE_TASK_FIELDS = new Set([
25
- "title",
26
- "type",
27
- "priority",
28
- "dependsOn",
29
- "unblocks",
30
- "phase",
31
- "metadata",
32
- "ownership",
33
- "approach",
34
- "technicalScope",
35
- "acceptanceCriteria"
36
- ]);
37
- function isRecordLike(value) {
38
- return typeof value === "object" && value !== null && !Array.isArray(value);
39
- }
40
- function readMetadataPath(metadata, path) {
41
- if (!metadata || !SAFE_METADATA_PATH_RE.test(path)) {
42
- return undefined;
43
- }
44
- const parts = path.split(".");
45
- let current = metadata;
46
- for (const part of parts) {
47
- if (!isRecordLike(current)) {
48
- return undefined;
49
- }
50
- if (!Object.prototype.hasOwnProperty.call(current, part)) {
51
- return undefined;
52
- }
53
- current = current[part];
54
- }
55
- return current;
56
- }
57
- function stableStringify(value) {
58
- if (Array.isArray(value)) {
59
- return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
60
- }
61
- if (value && typeof value === "object") {
62
- const record = value;
63
- const keys = Object.keys(record).sort();
64
- return `{${keys.map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
65
- }
66
- return JSON.stringify(value);
67
- }
68
- function digestPayload(value) {
69
- return crypto.createHash("sha256").update(stableStringify(value)).digest("hex");
70
- }
71
- function readIdempotencyValue(args) {
72
- const raw = args.clientMutationId;
73
- if (typeof raw !== "string") {
74
- return undefined;
75
- }
76
- const trimmed = raw.trim();
77
- return trimmed.length > 0 ? trimmed : undefined;
78
- }
79
- function findIdempotentMutation(store, mutationType, taskId, clientMutationId) {
80
- const log = store.getMutationLog();
81
- for (let idx = log.length - 1; idx >= 0; idx -= 1) {
82
- const entry = log[idx];
83
- if (entry.mutationType !== mutationType || entry.taskId !== taskId) {
84
- continue;
85
- }
86
- if (!entry.details || entry.details.clientMutationId !== clientMutationId) {
87
- continue;
88
- }
89
- return {
90
- payloadDigest: typeof entry.details.payloadDigest === "string" ? entry.details.payloadDigest : undefined
91
- };
92
- }
93
- return null;
94
- }
95
- function strictValidationError(store, effectiveConfig) {
96
- if (!planningStrictValidationEnabled({ effectiveConfig })) {
97
- return null;
98
- }
99
- return validateTaskSetForStrictMode(store.getAllTasks());
100
- }
101
- function nowIso() {
102
- return new Date().toISOString();
103
- }
104
- function parseConversionDecomposition(raw) {
105
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
106
- return { ok: false, message: "convert-wishlist requires 'decomposition' object" };
107
- }
108
- const o = raw;
109
- const rationale = typeof o.rationale === "string" ? o.rationale.trim() : "";
110
- const boundaries = typeof o.boundaries === "string" ? o.boundaries.trim() : "";
111
- const dependencyIntent = typeof o.dependencyIntent === "string" ? o.dependencyIntent.trim() : "";
112
- if (!rationale || !boundaries || !dependencyIntent) {
113
- return {
114
- ok: false,
115
- message: "decomposition requires non-empty rationale, boundaries, and dependencyIntent"
116
- };
117
- }
118
- return { ok: true, value: { rationale, boundaries, dependencyIntent } };
119
- }
120
- function buildTaskFromConversionPayload(row, timestamp) {
121
- const id = typeof row.id === "string" ? row.id.trim() : "";
122
- if (!TASK_ID_RE.test(id)) {
123
- return { ok: false, message: "Each converted task requires 'id' matching T<number>" };
124
- }
125
- const title = typeof row.title === "string" ? row.title.trim() : "";
126
- if (!title) {
127
- return { ok: false, message: `Task '${id}' requires non-empty title` };
128
- }
129
- const phase = typeof row.phase === "string" ? row.phase.trim() : "";
130
- if (!phase) {
131
- return { ok: false, message: `Task '${id}' requires 'phase' for workable tasks` };
132
- }
133
- const type = typeof row.type === "string" && row.type.trim() ? row.type.trim() : "workspace-kit";
134
- const priority = typeof row.priority === "string" && ["P1", "P2", "P3"].includes(row.priority)
135
- ? row.priority
136
- : undefined;
137
- const approach = typeof row.approach === "string" ? row.approach.trim() : "";
138
- if (!approach) {
139
- return { ok: false, message: `Task '${id}' requires 'approach'` };
140
- }
141
- const technicalScope = Array.isArray(row.technicalScope)
142
- ? row.technicalScope.filter((x) => typeof x === "string")
143
- : [];
144
- const acceptanceCriteria = Array.isArray(row.acceptanceCriteria)
145
- ? row.acceptanceCriteria.filter((x) => typeof x === "string")
146
- : [];
147
- if (technicalScope.length === 0) {
148
- return { ok: false, message: `Task '${id}' requires non-empty technicalScope array` };
149
- }
150
- if (acceptanceCriteria.length === 0) {
151
- return { ok: false, message: `Task '${id}' requires non-empty acceptanceCriteria array` };
152
- }
153
- const task = {
154
- id,
155
- title,
156
- type,
157
- status: "proposed",
158
- createdAt: timestamp,
159
- updatedAt: timestamp,
160
- priority,
161
- dependsOn: Array.isArray(row.dependsOn) ? row.dependsOn.filter((x) => typeof x === "string") : undefined,
162
- unblocks: Array.isArray(row.unblocks) ? row.unblocks.filter((x) => typeof x === "string") : undefined,
163
- phase,
164
- approach,
165
- technicalScope,
166
- acceptanceCriteria
167
- };
168
- return { ok: true, task };
169
- }
170
- function mutationEvidence(mutationType, taskId, actor, details) {
171
- return {
172
- mutationId: `${mutationType}-${taskId}-${nowIso()}-${crypto.randomUUID().slice(0, 8)}`,
173
- mutationType,
174
- taskId,
175
- timestamp: nowIso(),
176
- actor,
177
- details
178
- };
179
- }
180
- export const taskEngineModule = {
181
- registration: {
182
- id: "task-engine",
183
- version: "0.6.0",
184
- contractVersion: "1",
185
- capabilities: ["task-engine"],
186
- dependsOn: [],
187
- enabledByDefault: true,
188
- config: {
189
- path: "src/modules/task-engine/config.md",
190
- format: "md",
191
- description: "Task Engine configuration contract."
192
- },
193
- state: {
194
- path: "src/modules/task-engine/state.md",
195
- format: "md",
196
- description: "Task Engine runtime state contract."
197
- },
198
- instructions: {
199
- directory: "src/modules/task-engine/instructions",
200
- entries: [
201
- {
202
- name: "run-transition",
203
- file: "run-transition.md",
204
- description: "Execute a validated task status transition."
205
- },
206
- {
207
- name: "create-task",
208
- file: "create-task.md",
209
- description: "Create a new task through validated task-engine persistence."
210
- },
211
- {
212
- name: "update-task",
213
- file: "update-task.md",
214
- description: "Update mutable task fields without lifecycle bypass."
215
- },
216
- {
217
- name: "update-wishlist",
218
- file: "update-wishlist.md",
219
- description: "Update mutable fields on an open Wishlist item."
220
- },
221
- {
222
- name: "archive-task",
223
- file: "archive-task.md",
224
- description: "Archive a task without destructive deletion."
225
- },
226
- {
227
- name: "add-dependency",
228
- file: "add-dependency.md",
229
- description: "Add a dependency edge between tasks with cycle checks."
230
- },
231
- {
232
- name: "remove-dependency",
233
- file: "remove-dependency.md",
234
- description: "Remove a dependency edge between tasks."
235
- },
236
- {
237
- name: "get-dependency-graph",
238
- file: "get-dependency-graph.md",
239
- description: "Get dependency graph data for one task or the full store."
240
- },
241
- {
242
- name: "get-task-history",
243
- file: "get-task-history.md",
244
- description: "Get transition and mutation history for a task."
245
- },
246
- {
247
- name: "get-recent-task-activity",
248
- file: "get-recent-task-activity.md",
249
- description: "List recent transition and mutation activity across tasks."
250
- },
251
- {
252
- name: "get-task-summary",
253
- file: "get-task-summary.md",
254
- description: "Get aggregate task-state summary for active tasks."
255
- },
256
- {
257
- name: "get-blocked-summary",
258
- file: "get-blocked-summary.md",
259
- description: "Get blocked-task dependency summary for active tasks."
260
- },
261
- {
262
- name: "create-task-from-plan",
263
- file: "create-task-from-plan.md",
264
- description: "Promote planning output into a canonical task."
265
- },
266
- {
267
- name: "convert-wishlist",
268
- file: "convert-wishlist.md",
269
- description: "Convert a Wishlist item into one or more phased tasks and close the wishlist item."
270
- },
271
- {
272
- name: "create-wishlist",
273
- file: "create-wishlist.md",
274
- description: "Create a Wishlist ideation item with strict required fields (separate namespace from tasks)."
275
- },
276
- {
277
- name: "get-wishlist",
278
- file: "get-wishlist.md",
279
- description: "Retrieve a single Wishlist item by ID."
280
- },
281
- {
282
- name: "get-task",
283
- file: "get-task.md",
284
- description: "Retrieve a single task by ID."
285
- },
286
- {
287
- name: "list-tasks",
288
- file: "list-tasks.md",
289
- description: "List tasks with optional status/phase filters."
290
- },
291
- {
292
- name: "list-wishlist",
293
- file: "list-wishlist.md",
294
- description: "List Wishlist items (ideation-only; not part of task execution queues)."
295
- },
296
- {
297
- name: "get-ready-queue",
298
- file: "get-ready-queue.md",
299
- description: "Get ready tasks sorted by priority."
300
- },
301
- {
302
- name: "get-next-actions",
303
- file: "get-next-actions.md",
304
- description: "Get prioritized next-action suggestions with blocking analysis."
305
- },
306
- {
307
- name: "migrate-task-persistence",
308
- file: "migrate-task-persistence.md",
309
- description: "Copy task + wishlist state between JSON files and a single SQLite database (offline migration)."
310
- },
311
- {
312
- name: "dashboard-summary",
313
- file: "dashboard-summary.md",
314
- description: "Stable JSON cockpit summary for UI clients (tasks + maintainer status snapshot)."
315
- },
316
- {
317
- name: "explain-task-engine-model",
318
- file: "explain-task-engine-model.md",
319
- description: "Explain model variants, planning boundaries, lifecycle transitions, and required fields."
320
- }
321
- ]
322
- }
323
- },
324
- async onCommand(command, ctx) {
325
- const args = command.args ?? {};
326
- if (command.name === "migrate-task-persistence") {
327
- return runMigrateTaskPersistence(ctx, args);
328
- }
329
- let planning;
330
- try {
331
- planning = await openPlanningStores(ctx);
332
- }
333
- catch (err) {
334
- if (err instanceof TaskEngineError) {
335
- return { ok: false, code: err.code, message: err.message };
336
- }
337
- return {
338
- ok: false,
339
- code: "storage-read-error",
340
- message: `Failed to open task planning stores: ${err.message}`
341
- };
342
- }
343
- const store = planning.taskStore;
344
- if (command.name === "run-transition") {
345
- const taskId = typeof args.taskId === "string" ? args.taskId : undefined;
346
- const action = typeof args.action === "string" ? args.action : undefined;
347
- const actor = typeof args.actor === "string"
348
- ? args.actor
349
- : ctx.resolvedActor !== undefined
350
- ? ctx.resolvedActor
351
- : undefined;
352
- if (!taskId || !action) {
353
- return {
354
- ok: false,
355
- code: "invalid-task-schema",
356
- message: "run-transition requires 'taskId' and 'action' arguments"
357
- };
358
- }
359
- try {
360
- const service = new TransitionService(store);
361
- const result = await service.runTransition({ taskId, action, actor });
362
- if (result.evidence.toState === "completed") {
363
- maybeSpawnTranscriptHookAfterCompletion(ctx.workspacePath, (ctx.effectiveConfig ?? {}));
364
- }
365
- return {
366
- ok: true,
367
- code: "transition-applied",
368
- message: `${taskId}: ${result.evidence.fromState} → ${result.evidence.toState} (${action})`,
369
- data: {
370
- evidence: result.evidence,
371
- autoUnblocked: result.autoUnblocked
372
- }
373
- };
374
- }
375
- catch (err) {
376
- if (err instanceof TaskEngineError) {
377
- return { ok: false, code: err.code, message: err.message };
378
- }
379
- return {
380
- ok: false,
381
- code: "invalid-transition",
382
- message: err.message
383
- };
384
- }
385
- }
386
- if (command.name === "create-task" || command.name === "create-task-from-plan") {
387
- const actor = typeof args.actor === "string"
388
- ? args.actor
389
- : ctx.resolvedActor !== undefined
390
- ? ctx.resolvedActor
391
- : undefined;
392
- const id = typeof args.id === "string" && args.id.trim().length > 0 ? args.id.trim() : undefined;
393
- const title = typeof args.title === "string" && args.title.trim().length > 0 ? args.title.trim() : undefined;
394
- const type = typeof args.type === "string" && args.type.trim().length > 0 ? args.type.trim() : "workspace-kit";
395
- const status = typeof args.status === "string" ? args.status : "proposed";
396
- const priority = typeof args.priority === "string" && ["P1", "P2", "P3"].includes(args.priority)
397
- ? args.priority
398
- : undefined;
399
- const clientMutationId = readIdempotencyValue(args);
400
- if (!id || !title || !TASK_ID_RE.test(id) || !["proposed", "ready"].includes(status)) {
401
- return {
402
- ok: false,
403
- code: "invalid-task-schema",
404
- message: "create-task requires id/title, id format T<number>, and status of proposed or ready"
405
- };
406
- }
407
- const evidenceType = command.name === "create-task-from-plan" ? "create-task-from-plan" : "create-task";
408
- const timestamp = nowIso();
409
- const task = {
410
- id,
411
- title,
412
- type,
413
- status: status,
414
- createdAt: timestamp,
415
- updatedAt: timestamp,
416
- priority,
417
- dependsOn: Array.isArray(args.dependsOn) ? args.dependsOn.filter((x) => typeof x === "string") : undefined,
418
- unblocks: Array.isArray(args.unblocks) ? args.unblocks.filter((x) => typeof x === "string") : undefined,
419
- phase: typeof args.phase === "string" ? args.phase : undefined,
420
- metadata: typeof args.metadata === "object" && args.metadata !== null ? args.metadata : undefined,
421
- ownership: typeof args.ownership === "string" ? args.ownership : undefined,
422
- approach: typeof args.approach === "string" ? args.approach : undefined,
423
- technicalScope: Array.isArray(args.technicalScope) ? args.technicalScope.filter((x) => typeof x === "string") : undefined,
424
- acceptanceCriteria: Array.isArray(args.acceptanceCriteria) ? args.acceptanceCriteria.filter((x) => typeof x === "string") : undefined
425
- };
426
- if (command.name === "create-task-from-plan") {
427
- const planRef = typeof args.planRef === "string" && args.planRef.trim().length > 0 ? args.planRef.trim() : undefined;
428
- if (!planRef) {
429
- return {
430
- ok: false,
431
- code: "invalid-task-schema",
432
- message: "create-task-from-plan requires 'planRef'"
433
- };
434
- }
435
- task.metadata = { ...(task.metadata ?? {}), planRef };
436
- }
437
- const createPayloadForDigest = {
438
- id: task.id,
439
- title: task.title,
440
- type: task.type,
441
- status: task.status,
442
- priority: task.priority,
443
- dependsOn: task.dependsOn ?? [],
444
- unblocks: task.unblocks ?? [],
445
- phase: task.phase ?? null,
446
- metadata: task.metadata ?? null,
447
- ownership: task.ownership ?? null,
448
- approach: task.approach ?? null,
449
- technicalScope: task.technicalScope ?? [],
450
- acceptanceCriteria: task.acceptanceCriteria ?? []
451
- };
452
- const payloadDigest = digestPayload(createPayloadForDigest);
453
- if (clientMutationId) {
454
- const prior = findIdempotentMutation(store, evidenceType, id, clientMutationId);
455
- if (prior) {
456
- if (prior.payloadDigest !== payloadDigest) {
457
- return {
458
- ok: false,
459
- code: "idempotency-key-conflict",
460
- message: `clientMutationId '${clientMutationId}' was already used for a different ${evidenceType} payload on ${id}`
461
- };
462
- }
463
- return {
464
- ok: true,
465
- code: "task-create-idempotent-replay",
466
- message: `Idempotent create replay for task '${id}'`,
467
- data: { task: store.getTask(id), replayed: true }
468
- };
469
- }
470
- }
471
- if (store.getTask(id)) {
472
- return { ok: false, code: "duplicate-task-id", message: `Task '${id}' already exists` };
473
- }
474
- const knownTypeValidationError = validateKnownTaskTypeRequirements(task);
475
- if (knownTypeValidationError) {
476
- return {
477
- ok: false,
478
- code: knownTypeValidationError.code,
479
- message: knownTypeValidationError.message
480
- };
481
- }
482
- store.addTask(task);
483
- store.addMutationEvidence(mutationEvidence(evidenceType, id, actor, {
484
- initialStatus: task.status,
485
- source: command.name,
486
- clientMutationId,
487
- payloadDigest
488
- }));
489
- const strictIssue = strictValidationError(store, ctx.effectiveConfig);
490
- if (strictIssue) {
491
- return { ok: false, code: "strict-task-validation-failed", message: strictIssue };
492
- }
493
- await store.save();
494
- return {
495
- ok: true,
496
- code: "task-created",
497
- message: `Created task '${id}'`,
498
- data: { task }
499
- };
500
- }
501
- if (command.name === "update-task") {
502
- const taskId = typeof args.taskId === "string" ? args.taskId : undefined;
503
- const updates = typeof args.updates === "object" && args.updates !== null ? args.updates : undefined;
504
- const actor = typeof args.actor === "string"
505
- ? args.actor
506
- : ctx.resolvedActor !== undefined
507
- ? ctx.resolvedActor
508
- : undefined;
509
- if (!taskId || !updates) {
510
- return { ok: false, code: "invalid-task-schema", message: "update-task requires taskId and updates object" };
511
- }
512
- const clientMutationId = readIdempotencyValue(args);
513
- const task = store.getTask(taskId);
514
- if (!task) {
515
- return { ok: false, code: "task-not-found", message: `Task '${taskId}' not found` };
516
- }
517
- const invalidKeys = Object.keys(updates).filter((key) => !MUTABLE_TASK_FIELDS.has(key));
518
- if (invalidKeys.length > 0) {
519
- return {
520
- ok: false,
521
- code: "invalid-task-update",
522
- message: `update-task cannot mutate immutable fields: ${invalidKeys.join(", ")}`
523
- };
524
- }
525
- const updatedTask = { ...task, ...updates, updatedAt: nowIso() };
526
- const payloadDigest = digestPayload({ taskId, updates });
527
- if (clientMutationId) {
528
- const prior = findIdempotentMutation(store, "update-task", taskId, clientMutationId);
529
- if (prior) {
530
- if (prior.payloadDigest !== payloadDigest) {
531
- return {
532
- ok: false,
533
- code: "idempotency-key-conflict",
534
- message: `clientMutationId '${clientMutationId}' was already used for a different update-task payload on ${taskId}`
535
- };
536
- }
537
- return {
538
- ok: true,
539
- code: "task-update-idempotent-replay",
540
- message: `Idempotent update replay for task '${taskId}'`,
541
- data: { task, replayed: true }
542
- };
543
- }
544
- }
545
- const knownTypeValidationError = validateKnownTaskTypeRequirements(updatedTask);
546
- if (knownTypeValidationError) {
547
- return {
548
- ok: false,
549
- code: knownTypeValidationError.code,
550
- message: knownTypeValidationError.message
551
- };
552
- }
553
- store.updateTask(updatedTask);
554
- store.addMutationEvidence(mutationEvidence("update-task", taskId, actor, {
555
- updatedFields: Object.keys(updates),
556
- clientMutationId,
557
- payloadDigest
558
- }));
559
- const strictIssue = strictValidationError(store, ctx.effectiveConfig);
560
- if (strictIssue) {
561
- return { ok: false, code: "strict-task-validation-failed", message: strictIssue };
562
- }
563
- await store.save();
564
- return { ok: true, code: "task-updated", message: `Updated task '${taskId}'`, data: { task: updatedTask } };
565
- }
566
- if (command.name === "archive-task") {
567
- const taskId = typeof args.taskId === "string" ? args.taskId : undefined;
568
- const actor = typeof args.actor === "string"
569
- ? args.actor
570
- : ctx.resolvedActor !== undefined
571
- ? ctx.resolvedActor
572
- : undefined;
573
- if (!taskId) {
574
- return { ok: false, code: "invalid-task-schema", message: "archive-task requires taskId" };
575
- }
576
- const task = store.getTask(taskId);
577
- if (!task) {
578
- return { ok: false, code: "task-not-found", message: `Task '${taskId}' not found` };
579
- }
580
- const archivedAt = nowIso();
581
- const updatedTask = { ...task, archived: true, archivedAt, updatedAt: archivedAt };
582
- store.updateTask(updatedTask);
583
- store.addMutationEvidence(mutationEvidence("archive-task", taskId, actor));
584
- const strictIssue = strictValidationError(store, ctx.effectiveConfig);
585
- if (strictIssue) {
586
- return { ok: false, code: "strict-task-validation-failed", message: strictIssue };
587
- }
588
- await store.save();
589
- return { ok: true, code: "task-archived", message: `Archived task '${taskId}'`, data: { task: updatedTask } };
590
- }
591
- if (command.name === "get-task") {
592
- const taskId = typeof args.taskId === "string" ? args.taskId : undefined;
593
- if (!taskId) {
594
- return {
595
- ok: false,
596
- code: "invalid-task-schema",
597
- message: "get-task requires 'taskId' argument"
598
- };
599
- }
600
- const task = store.getTask(taskId);
601
- if (!task) {
602
- return {
603
- ok: false,
604
- code: "task-not-found",
605
- message: `Task '${taskId}' not found`
606
- };
607
- }
608
- const historyLimitRaw = args.historyLimit;
609
- const historyLimit = typeof historyLimitRaw === "number" && Number.isFinite(historyLimitRaw) && historyLimitRaw > 0
610
- ? Math.min(Math.floor(historyLimitRaw), 200)
611
- : 50;
612
- const log = store.getTransitionLog();
613
- const recentTransitions = log
614
- .filter((e) => e.taskId === taskId)
615
- .sort((a, b) => (a.timestamp < b.timestamp ? 1 : -1))
616
- .slice(0, historyLimit);
617
- const allowedActions = getAllowedTransitionsFrom(task.status).map(({ to, action }) => ({
618
- action,
619
- targetStatus: to
620
- }));
621
- return {
622
- ok: true,
623
- code: "task-retrieved",
624
- data: { task, recentTransitions, allowedActions }
625
- };
626
- }
627
- if (command.name === "add-dependency" || command.name === "remove-dependency") {
628
- const taskId = typeof args.taskId === "string" ? args.taskId : undefined;
629
- const dependencyTaskId = typeof args.dependencyTaskId === "string" ? args.dependencyTaskId : undefined;
630
- const actor = typeof args.actor === "string"
631
- ? args.actor
632
- : ctx.resolvedActor !== undefined
633
- ? ctx.resolvedActor
634
- : undefined;
635
- if (!taskId || !dependencyTaskId) {
636
- return {
637
- ok: false,
638
- code: "invalid-task-schema",
639
- message: `${command.name} requires taskId and dependencyTaskId`
640
- };
641
- }
642
- if (taskId === dependencyTaskId) {
643
- return { ok: false, code: "dependency-cycle", message: "Task cannot depend on itself" };
644
- }
645
- const task = store.getTask(taskId);
646
- const dep = store.getTask(dependencyTaskId);
647
- if (!task || !dep) {
648
- return { ok: false, code: "task-not-found", message: "taskId or dependencyTaskId not found" };
649
- }
650
- const deps = new Set(task.dependsOn ?? []);
651
- if (command.name === "add-dependency") {
652
- if (deps.has(dependencyTaskId)) {
653
- return { ok: false, code: "duplicate-dependency", message: "Dependency already exists" };
654
- }
655
- deps.add(dependencyTaskId);
656
- }
657
- else {
658
- deps.delete(dependencyTaskId);
659
- }
660
- const updatedTask = { ...task, dependsOn: [...deps], updatedAt: nowIso() };
661
- store.updateTask(updatedTask);
662
- const mutationType = command.name === "add-dependency" ? "add-dependency" : "remove-dependency";
663
- store.addMutationEvidence(mutationEvidence(mutationType, taskId, actor, { dependencyTaskId }));
664
- const strictIssue = strictValidationError(store, ctx.effectiveConfig);
665
- if (strictIssue) {
666
- return { ok: false, code: "strict-task-validation-failed", message: strictIssue };
667
- }
668
- await store.save();
669
- return {
670
- ok: true,
671
- code: command.name === "add-dependency" ? "dependency-added" : "dependency-removed",
672
- message: `${command.name} applied for '${taskId}'`,
673
- data: { task: updatedTask }
674
- };
675
- }
676
- if (command.name === "get-dependency-graph") {
677
- const taskId = typeof args.taskId === "string" ? args.taskId : undefined;
678
- const tasks = store.getActiveTasks();
679
- const nodes = tasks.map((task) => ({ id: task.id, status: task.status }));
680
- const edges = tasks.flatMap((task) => (task.dependsOn ?? []).map((depId) => ({ from: task.id, to: depId })));
681
- if (!taskId) {
682
- return { ok: true, code: "dependency-graph", data: { nodes, edges } };
683
- }
684
- const task = tasks.find((candidate) => candidate.id === taskId);
685
- if (!task) {
686
- return { ok: false, code: "task-not-found", message: `Task '${taskId}' not found` };
687
- }
688
- return {
689
- ok: true,
690
- code: "dependency-graph",
691
- data: {
692
- taskId,
693
- dependsOn: task.dependsOn ?? [],
694
- directDependents: tasks.filter((candidate) => (candidate.dependsOn ?? []).includes(taskId)).map((x) => x.id),
695
- nodes,
696
- edges
697
- }
698
- };
699
- }
700
- if (command.name === "get-task-history" || command.name === "get-recent-task-activity") {
701
- const limitRaw = args.limit;
702
- const limit = typeof limitRaw === "number" && Number.isFinite(limitRaw) && limitRaw > 0
703
- ? Math.min(Math.floor(limitRaw), 500)
704
- : 50;
705
- const taskId = typeof args.taskId === "string" ? args.taskId : undefined;
706
- const transitions = store.getTransitionLog().map((entry) => ({ kind: "transition", ...entry }));
707
- const mutations = store.getMutationLog().map((entry) => ({ kind: "mutation", ...entry }));
708
- const merged = [...transitions, ...mutations]
709
- .filter((entry) => (taskId ? entry.taskId === taskId : true))
710
- .sort((a, b) => (a.timestamp < b.timestamp ? 1 : -1))
711
- .slice(0, limit);
712
- return {
713
- ok: true,
714
- code: command.name === "get-task-history" ? "task-history" : "recent-task-activity",
715
- data: { taskId: taskId ?? null, items: merged, count: merged.length }
716
- };
717
- }
718
- if (command.name === "dashboard-summary") {
719
- const tasks = store.getActiveTasks();
720
- const suggestion = getNextActions(tasks);
721
- const workspaceStatus = await readWorkspaceStatusSnapshot(ctx.workspacePath);
722
- const readyTop = suggestion.readyQueue.slice(0, 15).map((t) => ({
723
- id: t.id,
724
- title: t.title,
725
- priority: t.priority ?? null,
726
- phase: t.phase ?? null
727
- }));
728
- const blockedTop = suggestion.blockingAnalysis.slice(0, 15);
729
- let wishlistItems = [];
730
- try {
731
- const wishlistStore = await planning.openWishlist();
732
- wishlistItems = wishlistStore.getAllItems();
733
- }
734
- catch {
735
- /* wishlist store optional */
736
- }
737
- const wishlistOpenCount = wishlistItems.filter((i) => i.status === "open").length;
738
- const data = {
739
- schemaVersion: 1,
740
- taskStoreLastUpdated: store.getLastUpdated(),
741
- workspaceStatus,
742
- stateSummary: suggestion.stateSummary,
743
- readyQueueTop: readyTop,
744
- readyQueueCount: suggestion.readyQueue.length,
745
- executionPlanningScope: "tasks-only",
746
- wishlist: {
747
- schemaVersion: 1,
748
- openCount: wishlistOpenCount,
749
- totalCount: wishlistItems.length
750
- },
751
- blockedSummary: {
752
- count: suggestion.blockingAnalysis.length,
753
- top: blockedTop
754
- },
755
- suggestedNext: suggestion.suggestedNext
756
- ? {
757
- id: suggestion.suggestedNext.id,
758
- title: suggestion.suggestedNext.title,
759
- status: suggestion.suggestedNext.status,
760
- priority: suggestion.suggestedNext.priority ?? null,
761
- phase: suggestion.suggestedNext.phase ?? null
762
- }
763
- : null,
764
- blockingAnalysis: suggestion.blockingAnalysis
765
- };
766
- return {
767
- ok: true,
768
- code: "dashboard-summary",
769
- message: "Dashboard summary built from task store and maintainer status snapshot",
770
- data
771
- };
772
- }
773
- if (command.name === "list-tasks") {
774
- const statusFilter = typeof args.status === "string" ? args.status : undefined;
775
- const phaseFilter = typeof args.phase === "string" ? args.phase : undefined;
776
- const typeFilter = typeof args.type === "string" && args.type.trim().length > 0 ? args.type.trim() : undefined;
777
- const categoryFilter = typeof args.category === "string" && args.category.trim().length > 0 ? args.category.trim() : undefined;
778
- const tagsFilterRaw = args.tags;
779
- const tagsFilter = typeof tagsFilterRaw === "string"
780
- ? [tagsFilterRaw]
781
- : Array.isArray(tagsFilterRaw)
782
- ? tagsFilterRaw.filter((entry) => typeof entry === "string" && entry.trim().length > 0)
783
- : [];
784
- const metadataFilters = isRecordLike(args.metadataFilters)
785
- ? Object.entries(args.metadataFilters).filter(([path]) => SAFE_METADATA_PATH_RE.test(path))
786
- : [];
787
- const includeArchived = args.includeArchived === true;
788
- let tasks = includeArchived ? store.getAllTasks() : store.getActiveTasks();
789
- if (statusFilter) {
790
- tasks = tasks.filter((t) => t.status === statusFilter);
791
- }
792
- if (phaseFilter) {
793
- tasks = tasks.filter((t) => t.phase === phaseFilter);
794
- }
795
- if (typeFilter) {
796
- tasks = tasks.filter((t) => t.type === typeFilter);
797
- }
798
- if (categoryFilter) {
799
- tasks = tasks.filter((t) => readMetadataPath(t.metadata, "category") === categoryFilter);
800
- }
801
- if (tagsFilter.length > 0) {
802
- tasks = tasks.filter((t) => {
803
- const tags = readMetadataPath(t.metadata, "tags");
804
- if (!Array.isArray(tags)) {
805
- return false;
806
- }
807
- const normalized = tags.filter((entry) => typeof entry === "string");
808
- return tagsFilter.every((tag) => normalized.includes(tag));
809
- });
810
- }
811
- if (metadataFilters.length > 0) {
812
- tasks = tasks.filter((t) => metadataFilters.every(([path, expected]) => readMetadataPath(t.metadata, path) === expected));
813
- }
814
- return {
815
- ok: true,
816
- code: "tasks-listed",
817
- message: `Found ${tasks.length} tasks`,
818
- data: { tasks, count: tasks.length, scope: "tasks-only" }
819
- };
820
- }
821
- if (command.name === "get-ready-queue") {
822
- const tasks = store.getActiveTasks();
823
- const ready = tasks
824
- .filter((t) => t.status === "ready")
825
- .sort((a, b) => {
826
- const pa = a.priority ?? "P9";
827
- const pb = b.priority ?? "P9";
828
- return pa.localeCompare(pb);
829
- });
830
- return {
831
- ok: true,
832
- code: "ready-queue-retrieved",
833
- message: `${ready.length} tasks in ready queue`,
834
- data: { tasks: ready, count: ready.length, scope: "tasks-only" }
835
- };
836
- }
837
- if (command.name === "get-next-actions") {
838
- const tasks = store.getActiveTasks();
839
- const suggestion = getNextActions(tasks);
840
- return {
841
- ok: true,
842
- code: "next-actions-retrieved",
843
- message: suggestion.suggestedNext
844
- ? `Suggested next: ${suggestion.suggestedNext.id} — ${suggestion.suggestedNext.title}`
845
- : "No tasks in ready queue",
846
- data: { ...suggestion, scope: "tasks-only" }
847
- };
848
- }
849
- if (command.name === "explain-task-engine-model") {
850
- const allStatuses = ["proposed", "ready", "in_progress", "blocked", "completed", "cancelled"];
851
- const lifecycle = allStatuses.map((status) => ({
852
- status,
853
- allowedActions: getAllowedTransitionsFrom(status).map((entry) => ({
854
- action: entry.action,
855
- targetStatus: entry.to
856
- }))
857
- }));
858
- return {
859
- ok: true,
860
- code: "task-engine-model-explained",
861
- message: "Task Engine model variants, planning boundary, and lifecycle transitions.",
862
- data: {
863
- modelVersion: 1,
864
- variants: [
865
- {
866
- variant: "execution-task",
867
- idPattern: "^T[0-9]+$",
868
- appearsInExecutionPlanning: true,
869
- requiredFields: ["id", "title", "type", "status", "createdAt", "updatedAt"],
870
- optionalFields: [
871
- "priority",
872
- "dependsOn",
873
- "unblocks",
874
- "phase",
875
- "metadata",
876
- "ownership",
877
- "approach",
878
- "technicalScope",
879
- "acceptanceCriteria"
880
- ]
881
- },
882
- {
883
- variant: "wishlist-item",
884
- idPattern: "^W[0-9]+$",
885
- appearsInExecutionPlanning: false,
886
- requiredFields: [
887
- "id",
888
- "title",
889
- "problemStatement",
890
- "expectedOutcome",
891
- "impact",
892
- "constraints",
893
- "successSignals",
894
- "requestor",
895
- "evidenceRef",
896
- "status",
897
- "createdAt",
898
- "updatedAt"
899
- ],
900
- optionalFields: ["metadata", "convertedTaskIds", "closedAt", "closeReason"],
901
- notes: "Wishlist is ideation-only and excluded from task ready queues."
902
- }
903
- ],
904
- planningBoundary: {
905
- executionQueues: "tasks-only",
906
- wishlistScope: "separate-namespace"
907
- },
908
- executionTaskLifecycle: lifecycle
909
- }
910
- };
911
- }
912
- if (command.name === "get-task-summary") {
913
- const tasks = store.getActiveTasks();
914
- const suggestion = getNextActions(tasks);
915
- return {
916
- ok: true,
917
- code: "task-summary",
918
- data: {
919
- scope: "tasks-only",
920
- stateSummary: suggestion.stateSummary,
921
- readyQueueCount: suggestion.readyQueue.length,
922
- suggestedNext: suggestion.suggestedNext
923
- ? {
924
- id: suggestion.suggestedNext.id,
925
- title: suggestion.suggestedNext.title,
926
- priority: suggestion.suggestedNext.priority ?? null
927
- }
928
- : null
929
- }
930
- };
931
- }
932
- if (command.name === "get-blocked-summary") {
933
- const tasks = store.getActiveTasks();
934
- const suggestion = getNextActions(tasks);
935
- return {
936
- ok: true,
937
- code: "blocked-summary",
938
- data: {
939
- blockedCount: suggestion.blockingAnalysis.length,
940
- blockedItems: suggestion.blockingAnalysis,
941
- scope: "tasks-only"
942
- }
943
- };
944
- }
945
- if (command.name === "create-wishlist") {
946
- const wishlistStore = await planning.openWishlist();
947
- const raw = args;
948
- const v = validateWishlistIntakePayload(raw);
949
- if (!v.ok) {
950
- return { ok: false, code: "invalid-task-schema", message: v.errors.join(" ") };
951
- }
952
- const ts = nowIso();
953
- const item = buildWishlistItemFromIntake(raw, ts);
954
- try {
955
- wishlistStore.addItem(item);
956
- }
957
- catch (err) {
958
- if (err instanceof TaskEngineError) {
959
- return { ok: false, code: err.code, message: err.message };
960
- }
961
- throw err;
962
- }
963
- await wishlistStore.save();
964
- return {
965
- ok: true,
966
- code: "wishlist-created",
967
- message: `Created wishlist '${item.id}'`,
968
- data: { item }
969
- };
970
- }
971
- if (command.name === "list-wishlist") {
972
- const wishlistStore = await planning.openWishlist();
973
- const statusFilter = typeof args.status === "string" ? args.status : undefined;
974
- let items = wishlistStore.getAllItems();
975
- if (statusFilter && ["open", "converted", "cancelled"].includes(statusFilter)) {
976
- items = items.filter((i) => i.status === statusFilter);
977
- }
978
- return {
979
- ok: true,
980
- code: "wishlist-listed",
981
- message: `Found ${items.length} wishlist items`,
982
- data: { items, count: items.length, scope: "wishlist-only" }
983
- };
984
- }
985
- if (command.name === "get-wishlist") {
986
- const wishlistId = typeof args.wishlistId === "string" && args.wishlistId.trim().length > 0
987
- ? args.wishlistId.trim()
988
- : typeof args.id === "string" && args.id.trim().length > 0
989
- ? args.id.trim()
990
- : "";
991
- if (!wishlistId) {
992
- return { ok: false, code: "invalid-task-schema", message: "get-wishlist requires 'wishlistId' or 'id'" };
993
- }
994
- const wishlistStore = await planning.openWishlist();
995
- const item = wishlistStore.getItem(wishlistId);
996
- if (!item) {
997
- return { ok: false, code: "task-not-found", message: `Wishlist item '${wishlistId}' not found` };
998
- }
999
- return {
1000
- ok: true,
1001
- code: "wishlist-retrieved",
1002
- data: { item }
1003
- };
1004
- }
1005
- if (command.name === "update-wishlist") {
1006
- const wishlistId = typeof args.wishlistId === "string" ? args.wishlistId.trim() : "";
1007
- const updates = typeof args.updates === "object" && args.updates !== null ? args.updates : undefined;
1008
- if (!wishlistId || !updates) {
1009
- return { ok: false, code: "invalid-task-schema", message: "update-wishlist requires wishlistId and updates" };
1010
- }
1011
- const wishlistStore = await planning.openWishlist();
1012
- const existing = wishlistStore.getItem(wishlistId);
1013
- if (!existing) {
1014
- return { ok: false, code: "task-not-found", message: `Wishlist item '${wishlistId}' not found` };
1015
- }
1016
- if (existing.status !== "open") {
1017
- return { ok: false, code: "invalid-transition", message: "Only open wishlist items can be updated" };
1018
- }
1019
- const uv = validateWishlistUpdatePayload(updates);
1020
- if (!uv.ok) {
1021
- return { ok: false, code: "invalid-task-schema", message: uv.errors.join(" ") };
1022
- }
1023
- const merged = { ...existing, updatedAt: nowIso() };
1024
- const mutable = [
1025
- "title",
1026
- "problemStatement",
1027
- "expectedOutcome",
1028
- "impact",
1029
- "constraints",
1030
- "successSignals",
1031
- "requestor",
1032
- "evidenceRef"
1033
- ];
1034
- for (const key of mutable) {
1035
- if (key in updates && typeof updates[key] === "string") {
1036
- merged[key] = updates[key].trim();
1037
- }
1038
- }
1039
- wishlistStore.updateItem(merged);
1040
- await wishlistStore.save();
1041
- return {
1042
- ok: true,
1043
- code: "wishlist-updated",
1044
- message: `Updated wishlist '${wishlistId}'`,
1045
- data: { item: merged }
1046
- };
1047
- }
1048
- if (command.name === "convert-wishlist") {
1049
- const wishlistId = typeof args.wishlistId === "string" ? args.wishlistId.trim() : "";
1050
- if (!wishlistId || !WISHLIST_ID_RE.test(wishlistId)) {
1051
- return {
1052
- ok: false,
1053
- code: "invalid-task-schema",
1054
- message: "convert-wishlist requires wishlistId matching W<number>"
1055
- };
1056
- }
1057
- const dec = parseConversionDecomposition(args.decomposition);
1058
- if (!dec.ok) {
1059
- return { ok: false, code: "invalid-task-schema", message: dec.message };
1060
- }
1061
- const tasksRaw = args.tasks;
1062
- if (!Array.isArray(tasksRaw) || tasksRaw.length === 0) {
1063
- return {
1064
- ok: false,
1065
- code: "invalid-task-schema",
1066
- message: "convert-wishlist requires non-empty tasks array"
1067
- };
1068
- }
1069
- const wishlistStore = await planning.openWishlist();
1070
- const wlItem = wishlistStore.getItem(wishlistId);
1071
- if (!wlItem) {
1072
- return { ok: false, code: "task-not-found", message: `Wishlist item '${wishlistId}' not found` };
1073
- }
1074
- if (wlItem.status !== "open") {
1075
- return {
1076
- ok: false,
1077
- code: "invalid-transition",
1078
- message: "Only open wishlist items can be converted"
1079
- };
1080
- }
1081
- const actor = typeof args.actor === "string"
1082
- ? args.actor
1083
- : ctx.resolvedActor !== undefined
1084
- ? ctx.resolvedActor
1085
- : undefined;
1086
- const timestamp = nowIso();
1087
- const built = [];
1088
- for (const row of tasksRaw) {
1089
- if (!row || typeof row !== "object" || Array.isArray(row)) {
1090
- return { ok: false, code: "invalid-task-schema", message: "Each task must be an object" };
1091
- }
1092
- const bt = buildTaskFromConversionPayload(row, timestamp);
1093
- if (!bt.ok) {
1094
- return { ok: false, code: "invalid-task-schema", message: bt.message };
1095
- }
1096
- if (store.getTask(bt.task.id)) {
1097
- return {
1098
- ok: false,
1099
- code: "duplicate-task-id",
1100
- message: `Task '${bt.task.id}' already exists`
1101
- };
1102
- }
1103
- built.push(bt.task);
1104
- }
1105
- const convertedIds = built.map((t) => t.id);
1106
- const updatedWishlist = {
1107
- ...wlItem,
1108
- status: "converted",
1109
- updatedAt: timestamp,
1110
- convertedAt: timestamp,
1111
- convertedToTaskIds: convertedIds,
1112
- conversionDecomposition: dec.value
1113
- };
1114
- const applyConvertMutations = () => {
1115
- for (const t of built) {
1116
- store.addTask(t);
1117
- store.addMutationEvidence(mutationEvidence("create-task", t.id, actor, {
1118
- initialStatus: t.status,
1119
- source: "convert-wishlist",
1120
- wishlistId
1121
- }));
1122
- }
1123
- wishlistStore.updateItem(updatedWishlist);
1124
- };
1125
- if (planningStrictValidationEnabled({ effectiveConfig: ctx.effectiveConfig })) {
1126
- const strictIssue = validateTaskSetForStrictMode([...store.getAllTasks(), ...built]);
1127
- if (strictIssue) {
1128
- return { ok: false, code: "strict-task-validation-failed", message: strictIssue };
1129
- }
1130
- }
1131
- if (planning.kind === "sqlite") {
1132
- planning.sqliteDual.withTransaction(applyConvertMutations);
1133
- }
1134
- else {
1135
- applyConvertMutations();
1136
- await store.save();
1137
- await wishlistStore.save();
1138
- }
1139
- return {
1140
- ok: true,
1141
- code: "wishlist-converted",
1142
- message: `Converted wishlist '${wishlistId}' to tasks: ${convertedIds.join(", ")}`,
1143
- data: { wishlist: updatedWishlist, createdTasks: built }
1144
- };
1145
- }
1146
- return {
1147
- ok: false,
1148
- code: "unsupported-command",
1149
- message: `Task Engine does not support command '${command.name}'`
1150
- };
1151
- }
1152
- };
10
+ export { taskEngineModule } from "./task-engine-internal.js";