@astrosheep/keiyaku 4.0.3 → 4.0.5
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/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/package.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +4 -4
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +15 -1
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +3 -1
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +34 -13
- package/build/src/akuma/akuma.d.ts +34 -12
- package/build/src/akuma/akuma.js +118 -63
- package/build/src/akuma/allowed.d.ts +7 -0
- package/build/src/akuma/allowed.js +33 -0
- package/build/src/akuma/archetype.d.ts +7 -4
- package/build/src/akuma/archetype.js +15 -16
- package/build/src/akuma/body.d.ts +6 -1
- package/build/src/akuma/body.js +36 -27
- package/build/src/akuma/heart/facts.d.ts +82 -14
- package/build/src/akuma/heart/index.d.ts +5 -18
- package/build/src/akuma/heart/index.js +14 -84
- package/build/src/akuma/heart/request-authority.d.ts +12 -0
- package/build/src/akuma/heart/request-authority.js +119 -0
- package/build/src/akuma/heart/request-rows.d.ts +16 -0
- package/build/src/akuma/heart/request-rows.js +88 -0
- package/build/src/akuma/heart/rows.d.ts +1 -26
- package/build/src/akuma/heart/rows.js +0 -50
- package/build/src/akuma/heart/schema.d.ts +1 -1
- package/build/src/akuma/heart/schema.js +22 -10
- package/build/src/akuma/heart/soul.js +2 -36
- package/build/src/akuma/heart/storage.d.ts +1 -0
- package/build/src/akuma/heart/storage.js +15 -0
- package/build/src/akuma/heart/timeline.d.ts +1 -5
- package/build/src/akuma/heart/timeline.js +8 -17
- package/build/src/akuma/index.d.ts +5 -2
- package/build/src/akuma/index.js +1 -0
- package/build/src/akuma/projection.d.ts +17 -3
- package/build/src/akuma/projection.js +40 -3
- package/build/src/akuma/provider-recipe.js +1 -11
- package/build/src/akuma/provider.d.ts +2 -7
- package/build/src/akuma/provider.js +2 -1
- package/build/src/akuma/providers/acp/core.d.ts +3 -1
- package/build/src/akuma/providers/acp/core.js +10 -6
- package/build/src/akuma/providers/acp/events.d.ts +11 -3
- package/build/src/akuma/providers/acp/events.js +78 -4
- package/build/src/akuma/providers/acp/index.js +0 -1
- package/build/src/akuma/providers/claude/events.js +62 -3
- package/build/src/akuma/providers/claude/index.d.ts +0 -4
- package/build/src/akuma/providers/claude/index.js +7 -3
- package/build/src/akuma/providers/codex-app-server/events.js +1 -13
- package/build/src/akuma/providers/codex-app-server/index.d.ts +2 -27
- package/build/src/akuma/providers/codex-app-server/index.js +0 -2
- package/build/src/akuma/providers/grok-build/index.d.ts +2 -1
- package/build/src/akuma/providers/grok-build/index.js +72 -3
- package/build/src/akuma/providers/opencode-sdk/events.js +71 -6
- package/build/src/akuma/providers/opencode-sdk/index.d.ts +2 -28
- package/build/src/akuma/providers/opencode-sdk/index.js +8 -4
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +1 -1
- package/build/src/akuma/providers/opencode-sdk/session.js +1 -1
- package/build/src/akuma/providers/pi/events.js +17 -3
- package/build/src/akuma/providers/pi/index.js +11 -4
- package/build/src/akuma/providers/unified-patch.d.ts +4 -0
- package/build/src/akuma/providers/unified-patch.js +14 -0
- package/build/src/akuma/publication.d.ts +1 -1
- package/build/src/akuma/publication.js +1 -1
- package/build/src/akuma/request-serve.d.ts +87 -0
- package/build/src/akuma/request-serve.js +484 -0
- package/build/src/akuma/request-wire.d.ts +124 -0
- package/build/src/akuma/request-wire.js +380 -0
- package/build/src/akuma/requests.d.ts +41 -41
- package/build/src/akuma/requests.js +75 -374
- package/build/src/akuma-body.d.ts +8 -0
- package/build/src/akuma-body.js +84 -0
- package/build/src/body/arc.js +4 -29
- package/build/src/body/decode.js +5 -39
- package/build/src/body/envelope.d.ts +7 -0
- package/build/src/body/envelope.js +37 -0
- package/build/src/cli/accepted.d.ts +4 -1
- package/build/src/cli/accepted.js +39 -9
- package/build/src/cli/commands/akuma-invoke.d.ts +8 -7
- package/build/src/cli/commands/akuma-invoke.js +18 -10
- package/build/src/cli/commands/akuma.d.ts +11 -2
- package/build/src/cli/commands/akuma.js +67 -28
- package/build/src/cli/commands/amend.d.ts +1 -1
- package/build/src/cli/commands/amend.js +1 -1
- package/build/src/cli/commands/contract.d.ts +8 -8
- package/build/src/cli/commands/contract.js +3 -3
- package/build/src/cli/commands/install.js +2 -2
- package/build/src/cli/commands/task-invoke.js +98 -9
- package/build/src/cli/commands/task-query.d.ts +1 -1
- package/build/src/cli/commands/task-query.js +11 -5
- package/build/src/cli/coordinates.d.ts +2 -0
- package/build/src/cli/coordinates.js +12 -5
- package/build/src/cli/invoke.d.ts +1 -0
- package/build/src/cli/invoke.js +116 -75
- package/build/src/cli/main.d.ts +2 -0
- package/build/src/cli/main.js +29 -4
- package/build/src/cli/parse.d.ts +3 -2
- package/build/src/cli/parse.js +25 -8
- package/build/src/cli/render/akuma.d.ts +2 -2
- package/build/src/cli/render/akuma.js +138 -24
- package/build/src/cli/render/audit.js +18 -8
- package/build/src/cli/render/contract.d.ts +2 -0
- package/build/src/cli/render/contract.js +243 -36
- package/build/src/cli/render/kanshi.js +64 -29
- package/build/src/cli/render/receipt.d.ts +2 -1
- package/build/src/cli/render/receipt.js +37 -2
- package/build/src/cli/render/terminal.d.ts +0 -2
- package/build/src/cli/render/terminal.js +0 -29
- package/build/src/cli/render/text.js +3 -1
- package/build/src/cli/result.d.ts +52 -9
- package/build/src/cli/selectors.d.ts +1 -0
- package/build/src/cli/selectors.js +9 -6
- package/build/src/contract-worktree.d.ts +1 -2
- package/build/src/contract-worktree.js +7 -9
- package/build/src/core/facts/codec.js +9 -0
- package/build/src/core/facts/fold.js +13 -1
- package/build/src/core/facts/types.d.ts +8 -1
- package/build/src/core/subject.js +4 -3
- package/build/src/core/verbs/placement.d.ts +9 -1
- package/build/src/core/verbs/placement.js +24 -6
- package/build/src/core/verbs/reintegrate.d.ts +13 -0
- package/build/src/core/verbs/reintegrate.js +24 -0
- package/build/src/dispatch/index.d.ts +1 -1
- package/build/src/dispatch/index.js +50 -39
- package/build/src/git/admission.d.ts +2 -1
- package/build/src/git/hooks.d.ts +0 -4
- package/build/src/git/hooks.js +0 -36
- package/build/src/git/integration.d.ts +7 -8
- package/build/src/git/integration.js +62 -9
- package/build/src/git/observe.d.ts +2 -1
- package/build/src/git/observe.js +6 -5
- package/build/src/git/process.d.ts +26 -0
- package/build/src/git/process.js +94 -0
- package/build/src/git/read-observation.d.ts +2 -1
- package/build/src/git/read-observation.js +3 -2
- package/build/src/git/reconcile.d.ts +8 -2
- package/build/src/git/reconcile.js +63 -14
- package/build/src/git/repository.d.ts +2 -29
- package/build/src/git/repository.js +4 -118
- package/build/src/git/scratch.d.ts +1 -1
- package/build/src/git/scratch.js +1 -1
- package/build/src/git/target-placement.d.ts +1 -1
- package/build/src/git/target-placement.js +9 -7
- package/build/src/git/tender.d.ts +20 -5
- package/build/src/git/tender.js +45 -13
- package/build/src/git/terminal-seal.d.ts +16 -2
- package/build/src/git/terminal-seal.js +15 -5
- package/build/src/git/workspace.d.ts +2 -1
- package/build/src/git/workspace.js +13 -5
- package/build/src/index.d.ts +1 -2
- package/build/src/index.js +1 -1
- package/build/src/kanshi/index.d.ts +1 -1
- package/build/src/kanshi/index.js +1 -1
- package/build/src/kanshi/read.d.ts +9 -1
- package/build/src/kanshi/read.js +91 -37
- package/build/src/kanshi/report.d.ts +1 -0
- package/build/src/library/address.d.ts +5 -14
- package/build/src/library/address.js +27 -23
- package/build/src/library/akuma-creation.d.ts +4 -2
- package/build/src/library/akuma-creation.js +43 -26
- package/build/src/library/audit.d.ts +2 -1
- package/build/src/library/audit.js +1 -1
- package/build/src/library/bind.d.ts +2 -1
- package/build/src/library/bind.js +4 -4
- package/build/src/library/catalog.d.ts +1 -2
- package/build/src/library/catalog.js +11 -6
- package/build/src/library/configuration.d.ts +5 -1
- package/build/src/library/configuration.js +71 -18
- package/build/src/library/contract.d.ts +70 -3
- package/build/src/library/contract.js +213 -27
- package/build/src/library/delivery.d.ts +1 -1
- package/build/src/library/fleet.d.ts +70 -12
- package/build/src/library/fleet.js +236 -56
- package/build/src/library/input.d.ts +0 -1
- package/build/src/library/input.js +1 -5
- package/build/src/library/keiyaku.d.ts +4 -4
- package/build/src/library/keiyaku.js +1 -2
- package/build/src/library/mutation.d.ts +2 -1
- package/build/src/library/reconcile.d.ts +2 -1
- package/build/src/library/reconcile.js +3 -1
- package/build/src/library/region.d.ts +1 -1
- package/build/src/library/repo.d.ts +3 -1
- package/build/src/library/repo.js +8 -4
- package/build/src/protocol/abandon.d.ts +9 -0
- package/build/src/protocol/abandon.js +15 -0
- package/build/src/protocol/amend.d.ts +16 -0
- package/build/src/protocol/amend.js +77 -0
- package/build/src/protocol/arc.d.ts +6 -0
- package/build/src/protocol/arc.js +12 -0
- package/build/src/protocol/attempt.d.ts +2 -1
- package/build/src/protocol/audit.d.ts +53 -0
- package/build/src/protocol/audit.js +140 -0
- package/build/src/protocol/bind.d.ts +2 -3
- package/build/src/protocol/bind.js +2 -16
- package/build/src/protocol/completion.d.ts +32 -0
- package/build/src/protocol/completion.js +130 -0
- package/build/src/protocol/deliver.d.ts +37 -0
- package/build/src/protocol/deliver.js +160 -0
- package/build/src/protocol/intent.d.ts +1 -1
- package/build/src/protocol/intent.js +1 -1
- package/build/src/protocol/operations.d.ts +65 -198
- package/build/src/protocol/operations.js +21 -634
- package/build/src/protocol/placement.d.ts +4 -13
- package/build/src/protocol/placement.js +3 -15
- package/build/src/protocol/read/status.d.ts +4 -2
- package/build/src/protocol/read/status.js +27 -15
- package/build/src/protocol/reconcile.d.ts +35 -0
- package/build/src/protocol/reconcile.js +41 -0
- package/build/src/protocol/reintegrate.d.ts +31 -0
- package/build/src/protocol/reintegrate.js +74 -0
- package/build/src/protocol/review.d.ts +33 -0
- package/build/src/protocol/review.js +148 -0
- package/build/src/protocol/run.d.ts +1 -1
- package/build/src/runtime/proc/run.d.ts +1 -0
- package/build/src/runtime/proc/run.js +9 -2
- package/build/src/settlement/fence.d.ts +1 -1
- package/build/src/settlement/holder.d.ts +1 -1
- package/build/src/settlement/settle.d.ts +2 -1
- package/build/src/settlement/settle.js +4 -1
- package/build/src/task/board.d.ts +18 -4
- package/build/src/task/board.js +73 -29
- package/build/src/task/compose.d.ts +1 -1
- package/build/src/task/compose.js +11 -4
- package/build/src/task/index.d.ts +4 -4
- package/build/src/task/index.js +8 -131
- package/build/src/task/input.d.ts +18 -0
- package/build/src/task/input.js +189 -0
- package/build/src/task/mutation.d.ts +39 -0
- package/build/src/task/mutation.js +85 -0
- package/build/src/task/operations.d.ts +4 -19
- package/build/src/task/operations.js +64 -18
- package/build/src/task/query.d.ts +9 -2
- package/build/src/task/query.js +32 -64
- package/build/src/task/store.js +0 -2
- package/build/src/verification/execution.d.ts +1 -1
- package/build/src/verification/execution.js +2 -1
- package/build/src/workspace-place.d.ts +1 -4
- package/build/src/workspace-place.js +0 -15
- package/package.json +5 -3
|
@@ -54,11 +54,19 @@ export type TaskDoctorIssue = Readonly<{
|
|
|
54
54
|
relation: "needs" | "parent" | "supersedes";
|
|
55
55
|
tasks: readonly TaskId[];
|
|
56
56
|
}>;
|
|
57
|
+
export declare function taskRef(board: TaskBoard, id: TaskId): TaskRef;
|
|
58
|
+
export type TaskRelationProjection = Readonly<{
|
|
59
|
+
children(id: TaskId): readonly TaskRef[];
|
|
60
|
+
blocks(id: TaskId): readonly TaskRef[];
|
|
61
|
+
supersededBy(id: TaskId): readonly TaskRef[];
|
|
62
|
+
related(id: TaskId): readonly TaskRef[];
|
|
63
|
+
}>;
|
|
64
|
+
export declare function createTaskRelations(board: TaskBoard): TaskRelationProjection;
|
|
57
65
|
export declare function taskDisposition(board: TaskBoard, task: TaskDocument): TaskDisposition;
|
|
58
|
-
export declare function
|
|
66
|
+
export declare function taskBlocked(board: TaskBoard, task: TaskDocument): boolean;
|
|
67
|
+
export declare function projectDetailFacts(board: TaskBoard, id: TaskId, relations: TaskRelationProjection): TaskDetailFacts | null;
|
|
59
68
|
export declare function projectRows(board: TaskBoard, scope: readonly string[] | null, selection: "active" | "closed" | "all"): readonly TaskRow[];
|
|
60
|
-
export declare function
|
|
61
|
-
export declare function projectBlocked(board: TaskBoard, scope: readonly string[] | null): readonly BlockedTaskRow[];
|
|
69
|
+
export declare function projectBlocked(board: TaskBoard, scope: readonly string[] | null, relations: TaskRelationProjection): readonly BlockedTaskRow[];
|
|
62
70
|
export declare function projectStatusRows(board: TaskBoard, scope: readonly string[] | null): (Readonly<{
|
|
63
71
|
id: TaskId;
|
|
64
72
|
title: string;
|
|
@@ -77,7 +85,13 @@ export declare function projectStatusRows(board: TaskBoard, scope: readonly stri
|
|
|
77
85
|
priority: TaskPriority;
|
|
78
86
|
disposition: TaskDisposition;
|
|
79
87
|
})[];
|
|
88
|
+
export type TaskBoardObservation = Readonly<{
|
|
89
|
+
statusRows: ReturnType<typeof projectStatusRows>;
|
|
90
|
+
selectNamespace(namespace: readonly string[]): readonly TaskRow[];
|
|
91
|
+
selectCreatedBy(createdBy: string): readonly TaskRow[];
|
|
92
|
+
}>;
|
|
93
|
+
export declare function projectTaskBoardObservation(board: TaskBoard): TaskBoardObservation;
|
|
80
94
|
export declare function relationProblem(board: TaskBoard, before: TaskDocument | null, after: TaskDocument): string | null;
|
|
81
95
|
export declare function diagnoseBoard(board: TaskBoard): readonly TaskDoctorIssue[];
|
|
82
|
-
export declare function buildTree(board: TaskBoard, root: TaskId): TaskTreeNode | null;
|
|
96
|
+
export declare function buildTree(board: TaskBoard, root: TaskId, relations: TaskRelationProjection): TaskTreeNode | null;
|
|
83
97
|
export {};
|
package/build/src/task/board.js
CHANGED
|
@@ -1,42 +1,81 @@
|
|
|
1
1
|
import { parseTaskId, sameNamespace } from "./identity.js";
|
|
2
2
|
const terminal = (state) => state === "done" || state === "drop";
|
|
3
|
+
const none = Object.freeze([]);
|
|
3
4
|
const sorted = (values) => [...values].sort((a, b) => Buffer.compare(Buffer.from(a.id), Buffer.from(b.id)));
|
|
4
|
-
function taskRef(board, id) {
|
|
5
|
+
export function taskRef(board, id) {
|
|
5
6
|
const task = board.tasks.get(id);
|
|
6
7
|
return task === undefined ? { id, title: null, state: "missing" } : { id, title: task.title, state: task.state };
|
|
7
8
|
}
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
function append(index, key, value) {
|
|
10
|
+
const values = index.get(key);
|
|
11
|
+
if (values === undefined)
|
|
12
|
+
index.set(key, [value]);
|
|
13
|
+
else
|
|
14
|
+
values.push(value);
|
|
11
15
|
}
|
|
12
|
-
function
|
|
13
|
-
return
|
|
16
|
+
function freezeRefs(board, index) {
|
|
17
|
+
return new Map([...index].map(([id, targets]) => [id, sorted(targets.map((target) => taskRef(board, target)))]));
|
|
14
18
|
}
|
|
15
|
-
function
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
export function createTaskRelations(board) {
|
|
20
|
+
const children = new Map();
|
|
21
|
+
const blocks = new Map();
|
|
22
|
+
const supersededBy = new Map();
|
|
23
|
+
const incoming = new Map();
|
|
24
|
+
for (const task of board.tasks.values()) {
|
|
25
|
+
if (task.parent !== null)
|
|
26
|
+
append(children, task.parent, task.id);
|
|
27
|
+
for (const need of task.needs)
|
|
28
|
+
append(blocks, need, task.id);
|
|
29
|
+
for (const target of task.supersedes)
|
|
30
|
+
append(supersededBy, target, task.id);
|
|
31
|
+
for (const target of task.relates)
|
|
32
|
+
append(incoming, target, task.id);
|
|
33
|
+
}
|
|
34
|
+
const childRefs = freezeRefs(board, children);
|
|
35
|
+
const blockRefs = freezeRefs(board, blocks);
|
|
36
|
+
const successorRefs = freezeRefs(board, supersededBy);
|
|
37
|
+
const incomingRefs = freezeRefs(board, incoming);
|
|
38
|
+
return {
|
|
39
|
+
children: (id) => childRefs.get(id) ?? none,
|
|
40
|
+
blocks: (id) => blockRefs.get(id) ?? none,
|
|
41
|
+
supersededBy: (id) => successorRefs.get(id) ?? none,
|
|
42
|
+
related: (id) => {
|
|
43
|
+
const task = board.tasks.get(id);
|
|
44
|
+
const ids = new Set(task?.relates ?? []);
|
|
45
|
+
for (const ref of incomingRefs.get(id) ?? none)
|
|
46
|
+
ids.add(ref.id);
|
|
47
|
+
return ids.size === 0 ? none : sorted([...ids].map((related) => taskRef(board, related)));
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function needReleased(board, id) {
|
|
52
|
+
const target = board.tasks.get(id);
|
|
53
|
+
return target !== undefined && terminal(target.state);
|
|
54
|
+
}
|
|
55
|
+
function hasUnresolvedNeeds(board, task) {
|
|
56
|
+
return task.needs.some((id) => !needReleased(board, id));
|
|
21
57
|
}
|
|
22
58
|
export function taskDisposition(board, task) {
|
|
23
59
|
if (task.state !== "open")
|
|
24
60
|
return task.state;
|
|
25
|
-
return
|
|
61
|
+
return hasUnresolvedNeeds(board, task) ? "blocked" : "ready";
|
|
26
62
|
}
|
|
27
|
-
export function
|
|
63
|
+
export function taskBlocked(board, task) {
|
|
64
|
+
return (task.state === "open" || task.state === "in_progress") && hasUnresolvedNeeds(board, task);
|
|
65
|
+
}
|
|
66
|
+
export function projectDetailFacts(board, id, relations) {
|
|
28
67
|
const task = board.tasks.get(id);
|
|
29
68
|
if (task === undefined)
|
|
30
69
|
return null;
|
|
31
|
-
const needs = task.needs.map((need) => ({ ...taskRef(board, need), released: board
|
|
70
|
+
const needs = task.needs.map((need) => ({ ...taskRef(board, need), released: needReleased(board, need) }));
|
|
32
71
|
return {
|
|
33
72
|
task,
|
|
34
73
|
needs,
|
|
35
74
|
blockers: needs.filter((need) => !need.released).map(({ released: _released, ...ref }) => ref),
|
|
36
|
-
blocks:
|
|
37
|
-
parent: task.parent === null ? null : taskRef(board, task.parent), children: children(
|
|
38
|
-
supersedes: task.supersedes.map((target) => taskRef(board, target)), supersededBy:
|
|
39
|
-
related: related(
|
|
75
|
+
blocks: relations.blocks(id),
|
|
76
|
+
parent: task.parent === null ? null : taskRef(board, task.parent), children: relations.children(id),
|
|
77
|
+
supersedes: task.supersedes.map((target) => taskRef(board, target)), supersededBy: relations.supersededBy(id),
|
|
78
|
+
related: relations.related(id),
|
|
40
79
|
};
|
|
41
80
|
}
|
|
42
81
|
function inScope(task, scope) { return scope === null || sameNamespace(parseTaskId(task.id).namespace, scope); }
|
|
@@ -47,24 +86,29 @@ export function projectRows(board, scope, selection) {
|
|
|
47
86
|
disposition: taskDisposition(board, task),
|
|
48
87
|
})).sort((a, b) => a.priority - b.priority || Buffer.compare(Buffer.from(a.id), Buffer.from(b.id)));
|
|
49
88
|
}
|
|
50
|
-
export function
|
|
51
|
-
return projectRows(board, scope, "active").filter((row) => row.disposition === "ready");
|
|
52
|
-
}
|
|
53
|
-
export function projectBlocked(board, scope) {
|
|
89
|
+
export function projectBlocked(board, scope, relations) {
|
|
54
90
|
return projectRows(board, scope, "active").flatMap((row) => {
|
|
55
|
-
if (
|
|
91
|
+
if (!taskBlocked(board, board.tasks.get(row.id)))
|
|
56
92
|
return [];
|
|
57
|
-
|
|
58
|
-
return blockers.length === 0 ? [] : [{ ...row, blockers }];
|
|
93
|
+
return [{ ...row, blockers: projectDetailFacts(board, row.id, relations).blockers }];
|
|
59
94
|
});
|
|
60
95
|
}
|
|
61
96
|
export function projectStatusRows(board, scope) {
|
|
62
|
-
const
|
|
97
|
+
const relations = createTaskRelations(board);
|
|
98
|
+
const blockers = new Map(projectBlocked(board, scope, relations).map((row) => [row.id, row.blockers]));
|
|
63
99
|
return projectRows(board, scope, "all").map((row) => {
|
|
64
100
|
const unresolved = blockers.get(row.id);
|
|
65
101
|
return unresolved === undefined ? row : { ...row, blockers: unresolved };
|
|
66
102
|
});
|
|
67
103
|
}
|
|
104
|
+
export function projectTaskBoardObservation(board) {
|
|
105
|
+
const rows = projectRows(board, null, "all");
|
|
106
|
+
return {
|
|
107
|
+
statusRows: projectStatusRows(board, null),
|
|
108
|
+
selectNamespace: (namespace) => projectRows(board, namespace, "all"),
|
|
109
|
+
selectCreatedBy: (createdBy) => rows.filter((row) => board.tasks.get(row.id)?.createdBy === createdBy),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
68
112
|
function relationIds(task, relation) {
|
|
69
113
|
return relation === "parent" ? task.parent === null ? [] : [task.parent] : task[relation];
|
|
70
114
|
}
|
|
@@ -147,7 +191,7 @@ export function diagnoseBoard(board) {
|
|
|
147
191
|
}
|
|
148
192
|
return issues;
|
|
149
193
|
}
|
|
150
|
-
export function buildTree(board, root) {
|
|
194
|
+
export function buildTree(board, root, relations) {
|
|
151
195
|
if (!board.tasks.has(root))
|
|
152
196
|
return null;
|
|
153
197
|
const walk = (id, path) => {
|
|
@@ -156,7 +200,7 @@ export function buildTree(board, root) {
|
|
|
156
200
|
const node = { task: { ...ref, priority: task?.priority ?? null } };
|
|
157
201
|
if (path.includes(id))
|
|
158
202
|
return { ...node, cycle: true, children: [] };
|
|
159
|
-
return { ...node, children: children(
|
|
203
|
+
return { ...node, children: relations.children(id).map((child) => walk(child.id, [...path, id])) };
|
|
160
204
|
};
|
|
161
205
|
return walk(root, []);
|
|
162
206
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type TaskId } from "./identity.js";
|
|
2
2
|
import type { WorldRoot } from "../world.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type TaskRefusal, type TaskRetry } from "./operations.js";
|
|
4
4
|
type TaskDocumentChange = Readonly<{
|
|
5
5
|
taskId: TaskId;
|
|
6
6
|
kind: "created" | "updated";
|
|
@@ -5,6 +5,7 @@ import { relationProblem } from "./board.js";
|
|
|
5
5
|
import { serializeTaskDocument } from "./document.js";
|
|
6
6
|
import { allocateLocalId, deriveLocalStem, formatTaskId, parseTaskId, sameNamespace } from "./identity.js";
|
|
7
7
|
import { authorityPath, readBoard, replaceAuthority, withTaskLocks } from "./store.js";
|
|
8
|
+
import { advanceTaskTimestamp } from "./operations.js";
|
|
8
9
|
function failure(diagnostic) { return { kind: "invalid-composition", diagnostic }; }
|
|
9
10
|
function parseNamespace(value) {
|
|
10
11
|
if (value === "")
|
|
@@ -118,8 +119,9 @@ function applyAssignments(document, assignments, body) {
|
|
|
118
119
|
}
|
|
119
120
|
return body === undefined ? next : { ...next, body };
|
|
120
121
|
}
|
|
121
|
-
function currentTimestamp() {
|
|
122
|
-
|
|
122
|
+
function currentTimestamp() {
|
|
123
|
+
return new Date().toISOString();
|
|
124
|
+
}
|
|
123
125
|
function createdTask(id, title, at, actor) {
|
|
124
126
|
return {
|
|
125
127
|
id, title, state: "open", priority: 2, needs: [], parent: null, supersedes: [], relates: [], note: "",
|
|
@@ -164,8 +166,13 @@ function plan(sketch, board, defaultNamespace, at, actor) {
|
|
|
164
166
|
current = { ...current, parent };
|
|
165
167
|
}
|
|
166
168
|
current = applyAssignments(current, node.assignments, node.body);
|
|
167
|
-
if (before !== null
|
|
168
|
-
|
|
169
|
+
if (before !== null
|
|
170
|
+
&& !Buffer.from(serializeTaskDocument(current)).equals(Buffer.from(serializeTaskDocument(before)))) {
|
|
171
|
+
current = {
|
|
172
|
+
...current,
|
|
173
|
+
updatedAt: advanceTaskTimestamp(before.updatedAt, at),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
169
176
|
byDepth[node.depth] = current.id;
|
|
170
177
|
byDepth.length = node.depth + 1;
|
|
171
178
|
all.set(current.id, current);
|
|
@@ -3,9 +3,9 @@ export type { WorldRoot } from "../world.js";
|
|
|
3
3
|
import { type BlockedTaskRow, type TaskDetailFacts, type TaskDoctorIssue, type TaskRef, type TaskRow, type TaskTreeNode } from "./board.js";
|
|
4
4
|
import { type TaskCompositionResult } from "./compose.js";
|
|
5
5
|
import { TaskAuthorityCorruptionError, type TaskPriority, type TaskState } from "./document.js";
|
|
6
|
-
import {
|
|
6
|
+
import type { TaskId } from "./identity.js";
|
|
7
7
|
import { type AddTaskDocumentInput, type AddTaskInput, type TaskBatchResult, type TaskMutationResult, type TaskOutcome, type TaskRefusal, type TaskRetry, type TaskUpdateResult, type TaskView, type UpdateTaskInput } from "./operations.js";
|
|
8
|
-
import { type TaskPage, type TaskQueryExpression, type TaskQueryPredicate, type TaskQueryRow, type TaskQuerySort } from "./query.js";
|
|
8
|
+
import { TASK_RELATION_PREDICATE_FIELDS, type TaskPage, type TaskQueryExpression, type TaskQueryPredicate, type TaskQueryRow, type TaskQuerySort, type TaskRelationPredicateField } from "./query.js";
|
|
9
9
|
export type TaskDetail = Omit<TaskDetailFacts, "task"> & Readonly<{
|
|
10
10
|
task: TaskView;
|
|
11
11
|
}>;
|
|
@@ -17,8 +17,8 @@ export type TaskDoctorReport = Readonly<{
|
|
|
17
17
|
issues: readonly TaskDoctorIssue[];
|
|
18
18
|
}>;
|
|
19
19
|
export type TaskDecompositionTree = TaskOutcome<TaskTreeNode>;
|
|
20
|
-
export type { AddTaskDocumentInput, AddTaskInput, BlockedTaskRow, TaskBatchResult, TaskCompositionResult, TaskDoctorIssue, TaskId, TaskMutationResult, TaskOutcome, TaskPriority, TaskRef, TaskRefusal, TaskRetry, TaskRow, TaskState, TaskTreeNode, TaskUpdateResult, TaskView, UpdateTaskInput, TaskPage, TaskQueryExpression, TaskQueryPredicate, TaskQueryRow, TaskQuerySort };
|
|
21
|
-
export { TaskAuthorityCorruptionError };
|
|
20
|
+
export type { AddTaskDocumentInput, AddTaskInput, BlockedTaskRow, TaskBatchResult, TaskCompositionResult, TaskDoctorIssue, TaskId, TaskMutationResult, TaskOutcome, TaskPriority, TaskRef, TaskRefusal, TaskRetry, TaskRow, TaskState, TaskTreeNode, TaskUpdateResult, TaskView, UpdateTaskInput, TaskPage, TaskQueryExpression, TaskQueryPredicate, TaskQueryRow, TaskQuerySort, TaskRelationPredicateField, };
|
|
21
|
+
export { TaskAuthorityCorruptionError, TASK_RELATION_PREDICATE_FIELDS };
|
|
22
22
|
declare class TaskHandle {
|
|
23
23
|
readonly id: TaskId;
|
|
24
24
|
private readonly world;
|
package/build/src/task/index.js
CHANGED
|
@@ -1,136 +1,11 @@
|
|
|
1
|
-
import { buildTree, diagnoseBoard, projectDetailFacts } from "./board.js";
|
|
1
|
+
import { buildTree, createTaskRelations, diagnoseBoard, projectDetailFacts, } from "./board.js";
|
|
2
2
|
import { composeTasks } from "./compose.js";
|
|
3
3
|
import { TaskAuthorityCorruptionError } from "./document.js";
|
|
4
|
-
import { isTaskSegment, parseTaskId } from "./identity.js";
|
|
5
4
|
import { addTask, addTaskDocument, batchTasks, blockedTasks, currentNamespace, lifecycleTask, listTasks, queryTasks, readyTasks, setCurrentNamespace, taskView, updateTask, } from "./operations.js";
|
|
6
5
|
import { readBoard } from "./store.js";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
11
|
-
throw new TypeError(`${label} must be an object`);
|
|
12
|
-
return value;
|
|
13
|
-
}
|
|
14
|
-
function closed(value, allowed, label) {
|
|
15
|
-
for (const key of Object.keys(value))
|
|
16
|
-
if (!allowed.includes(key))
|
|
17
|
-
throw new TypeError(`${label} has unknown field: ${key}`);
|
|
18
|
-
}
|
|
19
|
-
function id(value) { if (typeof value !== "string")
|
|
20
|
-
throw new TypeError("task ID must be a string"); parseTaskId(value); return value; }
|
|
21
|
-
function signal(value) {
|
|
22
|
-
if (value === undefined)
|
|
23
|
-
return undefined;
|
|
24
|
-
if (!(value instanceof AbortSignal))
|
|
25
|
-
throw new TypeError("signal must be an AbortSignal");
|
|
26
|
-
return value;
|
|
27
|
-
}
|
|
28
|
-
function strings(value, label) {
|
|
29
|
-
if (value === undefined)
|
|
30
|
-
return undefined;
|
|
31
|
-
if (!Array.isArray(value) || !value.every((item) => typeof item === "string"))
|
|
32
|
-
throw new TypeError(`${label} must be an array of strings`);
|
|
33
|
-
return value;
|
|
34
|
-
}
|
|
35
|
-
function taskIds(value, label) {
|
|
36
|
-
const values = strings(value, label)?.map(id);
|
|
37
|
-
if (values !== undefined && new Set(values).size !== values.length)
|
|
38
|
-
throw new TypeError(`${label} must not contain duplicates`);
|
|
39
|
-
return values;
|
|
40
|
-
}
|
|
41
|
-
function namespace(value) {
|
|
42
|
-
const values = strings(value, "namespace");
|
|
43
|
-
if (values === undefined)
|
|
44
|
-
return undefined;
|
|
45
|
-
if (!values.every(isTaskSegment))
|
|
46
|
-
throw new TypeError("namespace must contain canonical segments");
|
|
47
|
-
return values;
|
|
48
|
-
}
|
|
49
|
-
function text(value, label) { if (value === undefined)
|
|
50
|
-
return undefined; if (typeof value !== "string")
|
|
51
|
-
throw new TypeError(`${label} must be a string`); return value; }
|
|
52
|
-
function actor(value) {
|
|
53
|
-
if (value === undefined)
|
|
54
|
-
return undefined;
|
|
55
|
-
if (typeof value !== "string" || value.trim().length === 0)
|
|
56
|
-
throw new TypeError("actor must be a nonblank string");
|
|
57
|
-
return value;
|
|
58
|
-
}
|
|
59
|
-
function priority(value) { if (value === undefined)
|
|
60
|
-
return undefined; if (!Number.isInteger(value) || typeof value !== "number" || value < 0 || value > 3)
|
|
61
|
-
throw new TypeError("priority must be 0..3"); return value; }
|
|
62
|
-
function limit(value) { if (value === undefined)
|
|
63
|
-
return undefined; if (typeof value !== "number" || !isValidTaskLimit(value))
|
|
64
|
-
throw new TypeError(`limit must be an integer from 1 to ${MAX_TASK_LIMIT}`); return value; }
|
|
65
|
-
function sort(value) {
|
|
66
|
-
if (value === undefined)
|
|
67
|
-
return undefined;
|
|
68
|
-
if (value !== "priority" && value !== "created" && value !== "updated" && value !== "id")
|
|
69
|
-
throw new TypeError("sort is invalid");
|
|
70
|
-
return value;
|
|
71
|
-
}
|
|
72
|
-
function state(value) {
|
|
73
|
-
if (value === undefined)
|
|
74
|
-
return undefined;
|
|
75
|
-
if (value !== "open" && value !== "in_progress" && value !== "on_hold" && value !== "done" && value !== "drop")
|
|
76
|
-
throw new TypeError("state is invalid");
|
|
77
|
-
return value;
|
|
78
|
-
}
|
|
79
|
-
function nullableId(value) { return value === undefined || value === null ? value : id(value); }
|
|
80
|
-
function addInput(input) {
|
|
81
|
-
const v = record(input, "add input");
|
|
82
|
-
closed(v, ["title", "namespace", "body", "note", "state", "priority", "needs", "parent", "supersedes", "relates", "actor", "signal"], "add input");
|
|
83
|
-
const title = text(v.title, "title");
|
|
84
|
-
if (title === undefined || title.trim() === "")
|
|
85
|
-
throw new TypeError("title is required");
|
|
86
|
-
const ns = namespace(v.namespace), body = text(v.body, "body"), note = text(v.note, "note"), initialState = state(v.state), pri = priority(v.priority);
|
|
87
|
-
const needs = taskIds(v.needs, "needs"), parent = nullableId(v.parent), supersedes = taskIds(v.supersedes, "supersedes"), relates = taskIds(v.relates, "relates");
|
|
88
|
-
const createdBy = actor(v.actor), abort = signal(v.signal);
|
|
89
|
-
return { title, ...(ns === undefined ? {} : { namespace: ns }), ...(body === undefined ? {} : { body }), ...(note === undefined ? {} : { note }),
|
|
90
|
-
...(initialState === undefined ? {} : { state: initialState }), ...(pri === undefined ? {} : { priority: pri }),
|
|
91
|
-
...(needs === undefined ? {} : { needs }), ...(parent === undefined ? {} : { parent }),
|
|
92
|
-
...(supersedes === undefined ? {} : { supersedes }), ...(relates === undefined ? {} : { relates }),
|
|
93
|
-
...(createdBy === undefined ? {} : { actor: createdBy }),
|
|
94
|
-
...(abort === undefined ? {} : { signal: abort }) };
|
|
95
|
-
}
|
|
96
|
-
function updateInput(input) {
|
|
97
|
-
const v = record(input, "update input");
|
|
98
|
-
closed(v, ["title", "body", "appendBody", "note", "priority", "needs", "addNeeds", "dropNeeds", "parent", "supersedes", "addSupersedes", "dropSupersedes", "relates", "addRelates", "dropRelates", "signal"], "update input");
|
|
99
|
-
if (v.body !== undefined && v.appendBody !== undefined)
|
|
100
|
-
throw new TypeError("body and appendBody are mutually exclusive");
|
|
101
|
-
const result = {};
|
|
102
|
-
const title = text(v.title, "title");
|
|
103
|
-
if (title !== undefined) {
|
|
104
|
-
if (title.trim().length === 0)
|
|
105
|
-
throw new TypeError("title must be nonblank");
|
|
106
|
-
result.title = title;
|
|
107
|
-
}
|
|
108
|
-
const body = text(v.body, "body");
|
|
109
|
-
if (body !== undefined)
|
|
110
|
-
result.body = body;
|
|
111
|
-
const appendBody = text(v.appendBody, "appendBody");
|
|
112
|
-
if (appendBody !== undefined)
|
|
113
|
-
result.appendBody = appendBody;
|
|
114
|
-
const note = text(v.note, "note");
|
|
115
|
-
if (note !== undefined)
|
|
116
|
-
result.note = note;
|
|
117
|
-
const pri = priority(v.priority);
|
|
118
|
-
if (pri !== undefined)
|
|
119
|
-
result.priority = pri;
|
|
120
|
-
for (const [key, value] of [["needs", taskIds(v.needs, "needs")], ["addNeeds", taskIds(v.addNeeds, "addNeeds")], ["dropNeeds", taskIds(v.dropNeeds, "dropNeeds")], ["supersedes", taskIds(v.supersedes, "supersedes")], ["addSupersedes", taskIds(v.addSupersedes, "addSupersedes")], ["dropSupersedes", taskIds(v.dropSupersedes, "dropSupersedes")], ["relates", taskIds(v.relates, "relates")], ["addRelates", taskIds(v.addRelates, "addRelates")], ["dropRelates", taskIds(v.dropRelates, "dropRelates")]])
|
|
121
|
-
if (value !== undefined)
|
|
122
|
-
result[key] = value;
|
|
123
|
-
const parent = nullableId(v.parent);
|
|
124
|
-
if (parent !== undefined)
|
|
125
|
-
result.parent = parent;
|
|
126
|
-
const abort = signal(v.signal);
|
|
127
|
-
if (abort !== undefined)
|
|
128
|
-
result.signal = abort;
|
|
129
|
-
const update = result;
|
|
130
|
-
if (Object.keys(result).filter((key) => key !== "signal").length === 0)
|
|
131
|
-
throw new TypeError("update requires at least one field change");
|
|
132
|
-
return update;
|
|
133
|
-
}
|
|
6
|
+
import { actor, addInput, closed, limit, namespace, record, signal, sort, taskId as id, taskIds, text, updateInput, } from "./input.js";
|
|
7
|
+
import { normalizeTaskQuery, TASK_RELATION_PREDICATE_FIELDS, DEFAULT_TASK_LIMIT, } from "./query.js";
|
|
8
|
+
export { TaskAuthorityCorruptionError, TASK_RELATION_PREDICATE_FIELDS };
|
|
134
9
|
class TaskHandle {
|
|
135
10
|
id;
|
|
136
11
|
world;
|
|
@@ -139,13 +14,15 @@ class TaskHandle {
|
|
|
139
14
|
this.world = world;
|
|
140
15
|
}
|
|
141
16
|
async read() {
|
|
142
|
-
const
|
|
17
|
+
const board = (await readBoard(this.world)).board;
|
|
18
|
+
const facts = projectDetailFacts(board, this.id, createTaskRelations(board));
|
|
143
19
|
return facts === null ? null : { ...facts, task: taskView(facts.task) };
|
|
144
20
|
}
|
|
145
21
|
async tree() {
|
|
146
22
|
if (arguments.length > 0)
|
|
147
23
|
throw new TypeError("tree accepts no input");
|
|
148
|
-
const
|
|
24
|
+
const board = (await readBoard(this.world)).board;
|
|
25
|
+
const node = buildTree(board, this.id, createTaskRelations(board));
|
|
149
26
|
return node === null ? { kind: "refused", refusal: { kind: "task-missing", taskId: this.id } } : { kind: "accepted", value: node };
|
|
150
27
|
}
|
|
151
28
|
update(input) { return updateTask(this.world, this.id, updateInput(input)); }
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type TaskPriority, type TaskState } from "./document.js";
|
|
2
|
+
import { type TaskId } from "./identity.js";
|
|
3
|
+
import type { AddTaskInput, UpdateTaskInput } from "./operations.js";
|
|
4
|
+
import { type TaskQuerySort } from "./query.js";
|
|
5
|
+
export declare function record(value: unknown, label: string): Record<string, unknown>;
|
|
6
|
+
export declare function closed(value: Record<string, unknown>, allowed: readonly string[], label: string): void;
|
|
7
|
+
export declare function taskId(value: unknown): TaskId;
|
|
8
|
+
export declare function signal(value: unknown): AbortSignal | undefined;
|
|
9
|
+
export declare function taskIds(value: unknown, label: string): readonly TaskId[] | undefined;
|
|
10
|
+
export declare function namespace(value: unknown): readonly string[] | undefined;
|
|
11
|
+
export declare function text(value: unknown, label: string): string | undefined;
|
|
12
|
+
export declare function actor(value: unknown): string | undefined;
|
|
13
|
+
export declare function priority(value: unknown): TaskPriority | undefined;
|
|
14
|
+
export declare function limit(value: unknown): number | undefined;
|
|
15
|
+
export declare function sort(value: unknown): TaskQuerySort | undefined;
|
|
16
|
+
export declare function state(value: unknown): TaskState | undefined;
|
|
17
|
+
export declare function addInput(input: unknown): AddTaskInput;
|
|
18
|
+
export declare function updateInput(input: unknown): UpdateTaskInput;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { isTaskSegment, parseTaskId } from "./identity.js";
|
|
2
|
+
import { isValidTaskLimit, MAX_TASK_LIMIT } from "./query.js";
|
|
3
|
+
export function record(value, label) {
|
|
4
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
5
|
+
throw new TypeError(`${label} must be an object`);
|
|
6
|
+
}
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
export function closed(value, allowed, label) {
|
|
10
|
+
for (const key of Object.keys(value)) {
|
|
11
|
+
if (!allowed.includes(key))
|
|
12
|
+
throw new TypeError(`${label} has unknown field: ${key}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function taskId(value) {
|
|
16
|
+
if (typeof value !== "string")
|
|
17
|
+
throw new TypeError("task ID must be a string");
|
|
18
|
+
parseTaskId(value);
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
export function signal(value) {
|
|
22
|
+
if (value === undefined)
|
|
23
|
+
return undefined;
|
|
24
|
+
if (!(value instanceof AbortSignal))
|
|
25
|
+
throw new TypeError("signal must be an AbortSignal");
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
function strings(value, label) {
|
|
29
|
+
if (value === undefined)
|
|
30
|
+
return undefined;
|
|
31
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
|
|
32
|
+
throw new TypeError(`${label} must be an array of strings`);
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
export function taskIds(value, label) {
|
|
37
|
+
const values = strings(value, label)?.map(taskId);
|
|
38
|
+
if (values !== undefined && new Set(values).size !== values.length) {
|
|
39
|
+
throw new TypeError(`${label} must not contain duplicates`);
|
|
40
|
+
}
|
|
41
|
+
return values;
|
|
42
|
+
}
|
|
43
|
+
export function namespace(value) {
|
|
44
|
+
const values = strings(value, "namespace");
|
|
45
|
+
if (values === undefined)
|
|
46
|
+
return undefined;
|
|
47
|
+
if (!values.every(isTaskSegment))
|
|
48
|
+
throw new TypeError("namespace must contain canonical segments");
|
|
49
|
+
return values;
|
|
50
|
+
}
|
|
51
|
+
export function text(value, label) {
|
|
52
|
+
if (value === undefined)
|
|
53
|
+
return undefined;
|
|
54
|
+
if (typeof value !== "string")
|
|
55
|
+
throw new TypeError(`${label} must be a string`);
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
export function actor(value) {
|
|
59
|
+
if (value === undefined)
|
|
60
|
+
return undefined;
|
|
61
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
62
|
+
throw new TypeError("actor must be a nonblank string");
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
export function priority(value) {
|
|
67
|
+
if (value === undefined)
|
|
68
|
+
return undefined;
|
|
69
|
+
if (!Number.isInteger(value) || typeof value !== "number" || value < 0 || value > 3) {
|
|
70
|
+
throw new TypeError("priority must be 0..3");
|
|
71
|
+
}
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
export function limit(value) {
|
|
75
|
+
if (value === undefined)
|
|
76
|
+
return undefined;
|
|
77
|
+
if (typeof value !== "number" || !isValidTaskLimit(value)) {
|
|
78
|
+
throw new TypeError(`limit must be an integer from 1 to ${MAX_TASK_LIMIT}`);
|
|
79
|
+
}
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
export function sort(value) {
|
|
83
|
+
if (value === undefined)
|
|
84
|
+
return undefined;
|
|
85
|
+
if (value !== "priority" && value !== "created" && value !== "updated" && value !== "id") {
|
|
86
|
+
throw new TypeError("sort is invalid");
|
|
87
|
+
}
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
export function state(value) {
|
|
91
|
+
if (value === undefined)
|
|
92
|
+
return undefined;
|
|
93
|
+
if (value !== "open" && value !== "in_progress" && value !== "on_hold"
|
|
94
|
+
&& value !== "done" && value !== "drop") {
|
|
95
|
+
throw new TypeError("state is invalid");
|
|
96
|
+
}
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
function nullableId(value) {
|
|
100
|
+
return value === undefined || value === null ? value : taskId(value);
|
|
101
|
+
}
|
|
102
|
+
export function addInput(input) {
|
|
103
|
+
const value = record(input, "add input");
|
|
104
|
+
closed(value, [
|
|
105
|
+
"title", "namespace", "body", "note", "state", "priority", "needs", "parent",
|
|
106
|
+
"supersedes", "relates", "actor", "signal",
|
|
107
|
+
], "add input");
|
|
108
|
+
const title = text(value.title, "title");
|
|
109
|
+
if (title === undefined || title.trim() === "")
|
|
110
|
+
throw new TypeError("title is required");
|
|
111
|
+
const selectedNamespace = namespace(value.namespace);
|
|
112
|
+
const body = text(value.body, "body");
|
|
113
|
+
const note = text(value.note, "note");
|
|
114
|
+
const initialState = state(value.state);
|
|
115
|
+
const selectedPriority = priority(value.priority);
|
|
116
|
+
const needs = taskIds(value.needs, "needs");
|
|
117
|
+
const parent = nullableId(value.parent);
|
|
118
|
+
const supersedes = taskIds(value.supersedes, "supersedes");
|
|
119
|
+
const relates = taskIds(value.relates, "relates");
|
|
120
|
+
const createdBy = actor(value.actor);
|
|
121
|
+
const abort = signal(value.signal);
|
|
122
|
+
return {
|
|
123
|
+
title,
|
|
124
|
+
...(selectedNamespace === undefined ? {} : { namespace: selectedNamespace }),
|
|
125
|
+
...(body === undefined ? {} : { body }),
|
|
126
|
+
...(note === undefined ? {} : { note }),
|
|
127
|
+
...(initialState === undefined ? {} : { state: initialState }),
|
|
128
|
+
...(selectedPriority === undefined ? {} : { priority: selectedPriority }),
|
|
129
|
+
...(needs === undefined ? {} : { needs }),
|
|
130
|
+
...(parent === undefined ? {} : { parent }),
|
|
131
|
+
...(supersedes === undefined ? {} : { supersedes }),
|
|
132
|
+
...(relates === undefined ? {} : { relates }),
|
|
133
|
+
...(createdBy === undefined ? {} : { actor: createdBy }),
|
|
134
|
+
...(abort === undefined ? {} : { signal: abort }),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export function updateInput(input) {
|
|
138
|
+
const value = record(input, "update input");
|
|
139
|
+
closed(value, [
|
|
140
|
+
"title", "body", "appendBody", "note", "priority", "needs", "addNeeds", "dropNeeds",
|
|
141
|
+
"parent", "supersedes", "addSupersedes", "dropSupersedes", "relates", "addRelates",
|
|
142
|
+
"dropRelates", "signal",
|
|
143
|
+
], "update input");
|
|
144
|
+
if (value.body !== undefined && value.appendBody !== undefined) {
|
|
145
|
+
throw new TypeError("body and appendBody are mutually exclusive");
|
|
146
|
+
}
|
|
147
|
+
const result = {};
|
|
148
|
+
const title = text(value.title, "title");
|
|
149
|
+
if (title !== undefined) {
|
|
150
|
+
if (title.trim().length === 0)
|
|
151
|
+
throw new TypeError("title must be nonblank");
|
|
152
|
+
result.title = title;
|
|
153
|
+
}
|
|
154
|
+
const body = text(value.body, "body");
|
|
155
|
+
if (body !== undefined)
|
|
156
|
+
result.body = body;
|
|
157
|
+
const appendBody = text(value.appendBody, "appendBody");
|
|
158
|
+
if (appendBody !== undefined)
|
|
159
|
+
result.appendBody = appendBody;
|
|
160
|
+
const note = text(value.note, "note");
|
|
161
|
+
if (note !== undefined)
|
|
162
|
+
result.note = note;
|
|
163
|
+
const selectedPriority = priority(value.priority);
|
|
164
|
+
if (selectedPriority !== undefined)
|
|
165
|
+
result.priority = selectedPriority;
|
|
166
|
+
for (const [key, ids] of [
|
|
167
|
+
["needs", taskIds(value.needs, "needs")],
|
|
168
|
+
["addNeeds", taskIds(value.addNeeds, "addNeeds")],
|
|
169
|
+
["dropNeeds", taskIds(value.dropNeeds, "dropNeeds")],
|
|
170
|
+
["supersedes", taskIds(value.supersedes, "supersedes")],
|
|
171
|
+
["addSupersedes", taskIds(value.addSupersedes, "addSupersedes")],
|
|
172
|
+
["dropSupersedes", taskIds(value.dropSupersedes, "dropSupersedes")],
|
|
173
|
+
["relates", taskIds(value.relates, "relates")],
|
|
174
|
+
["addRelates", taskIds(value.addRelates, "addRelates")],
|
|
175
|
+
["dropRelates", taskIds(value.dropRelates, "dropRelates")],
|
|
176
|
+
])
|
|
177
|
+
if (ids !== undefined)
|
|
178
|
+
result[key] = ids;
|
|
179
|
+
const parent = nullableId(value.parent);
|
|
180
|
+
if (parent !== undefined)
|
|
181
|
+
result.parent = parent;
|
|
182
|
+
const abort = signal(value.signal);
|
|
183
|
+
if (abort !== undefined)
|
|
184
|
+
result.signal = abort;
|
|
185
|
+
if (Object.keys(result).filter((key) => key !== "signal").length === 0) {
|
|
186
|
+
throw new TypeError("update requires at least one field change");
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
}
|