@astrosheep/keiyaku 4.0.0 → 4.0.2
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/README.md +133 -8
- package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +2 -2
- package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +4 -4
- package/build/integrations/marketplace/plugins/keiyaku/opencode.js +2 -2
- package/build/integrations/marketplace/plugins/keiyaku/package.json +2 -2
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +13 -5
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +91 -32
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +134 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/agents/openai.yaml +4 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +41 -6
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +262 -46
- package/build/src/akuma/abort.d.ts +2 -0
- package/build/src/akuma/abort.js +50 -0
- package/build/src/akuma/akuma.d.ts +50 -33
- package/build/src/akuma/akuma.js +175 -127
- package/build/src/akuma/archetype.d.ts +14 -5
- package/build/src/akuma/archetype.js +85 -39
- package/build/src/akuma/body.d.ts +3 -5
- package/build/src/akuma/body.js +490 -183
- package/build/src/akuma/coordinate.d.ts +9 -0
- package/build/src/akuma/coordinate.js +22 -0
- package/build/src/akuma/heart/facts.d.ts +88 -45
- package/build/src/akuma/heart/facts.js +19 -8
- package/build/src/akuma/heart/index.d.ts +64 -55
- package/build/src/akuma/heart/index.js +172 -252
- package/build/src/akuma/heart/rows.d.ts +38 -83
- package/build/src/akuma/heart/rows.js +117 -155
- package/build/src/akuma/heart/schema.d.ts +1 -1
- package/build/src/akuma/heart/schema.js +61 -22
- package/build/src/akuma/heart/soul.d.ts +8 -0
- package/build/src/akuma/heart/soul.js +136 -0
- package/build/src/akuma/heart/storage.d.ts +47 -0
- package/build/src/akuma/heart/storage.js +224 -0
- package/build/src/akuma/heart/tells.d.ts +12 -0
- package/build/src/akuma/heart/tells.js +79 -0
- package/build/src/akuma/heart/timeline.d.ts +15 -0
- package/build/src/akuma/heart/timeline.js +86 -0
- package/build/src/akuma/identity.d.ts +2 -2
- package/build/src/akuma/identity.js +9 -9
- package/build/src/akuma/index.d.ts +10 -3
- package/build/src/akuma/index.js +7 -1
- package/build/src/akuma/projection.d.ts +176 -0
- package/build/src/akuma/projection.js +256 -0
- package/build/src/akuma/provider-recipe.d.ts +24 -0
- package/build/src/akuma/provider-recipe.js +100 -0
- package/build/src/akuma/provider.d.ts +67 -13
- package/build/src/akuma/provider.js +172 -105
- package/build/src/akuma/providers/acp/core.d.ts +19 -0
- package/build/src/akuma/providers/acp/core.js +159 -0
- package/build/src/akuma/providers/acp/events.d.ts +20 -0
- package/build/src/akuma/providers/acp/events.js +73 -0
- package/build/src/akuma/providers/acp/index.d.ts +12 -0
- package/build/src/akuma/providers/acp/index.js +94 -0
- package/build/src/akuma/providers/{claude.d.ts → claude/events.d.ts} +9 -41
- package/build/src/akuma/providers/claude/events.js +238 -0
- package/build/src/akuma/providers/claude/index.d.ts +47 -0
- package/build/src/akuma/providers/claude/index.js +279 -0
- package/build/src/akuma/providers/claude/input.d.ts +13 -0
- package/build/src/akuma/providers/claude/input.js +109 -0
- package/build/src/akuma/providers/codex-app-server/events.d.ts +1 -1
- package/build/src/akuma/providers/codex-app-server/events.js +15 -12
- package/build/src/akuma/providers/codex-app-server/index.d.ts +15 -13
- package/build/src/akuma/providers/codex-app-server/index.js +77 -33
- package/build/src/akuma/providers/grok-build/index.d.ts +4 -0
- package/build/src/akuma/providers/grok-build/index.js +70 -0
- package/build/src/akuma/providers/index.d.ts +5 -3
- package/build/src/akuma/providers/index.js +35 -56
- package/build/src/akuma/providers/opencode-sdk/events.d.ts +54 -0
- package/build/src/akuma/providers/opencode-sdk/events.js +235 -0
- package/build/src/akuma/providers/opencode-sdk/index.d.ts +33 -0
- package/build/src/akuma/providers/opencode-sdk/index.js +308 -0
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +26 -0
- package/build/src/akuma/providers/opencode-sdk/session.js +63 -0
- package/build/src/akuma/providers/pi/events.d.ts +33 -0
- package/build/src/akuma/providers/pi/events.js +166 -0
- package/build/src/akuma/providers/pi/index.d.ts +11 -0
- package/build/src/akuma/providers/pi/index.js +191 -0
- package/build/src/akuma/publication.d.ts +3 -3
- package/build/src/akuma/publication.js +28 -40
- package/build/src/akuma/requests.d.ts +9 -6
- package/build/src/akuma/requests.js +95 -65
- package/build/src/alias/index.d.ts +4 -3
- package/build/src/alias/index.js +12 -13
- package/build/src/body/decode.js +7 -1
- package/build/src/body/region.d.ts +2 -0
- package/build/src/body/region.js +77 -7
- package/build/src/body/render.js +2 -1
- package/build/src/body/shape.js +7 -1
- package/build/src/body/verification.js +19 -4
- package/build/src/cli/accepted.d.ts +18 -12
- package/build/src/cli/accepted.js +73 -34
- package/build/src/cli/actor.js +3 -3
- package/build/src/cli/commands/akuma-invoke.d.ts +44 -19
- package/build/src/cli/commands/akuma-invoke.js +101 -52
- package/build/src/cli/commands/akuma.d.ts +27 -9
- package/build/src/cli/commands/akuma.js +151 -80
- package/build/src/cli/commands/contract.d.ts +155 -0
- package/build/src/cli/commands/contract.js +20 -0
- package/build/src/cli/commands/install.js +2 -0
- package/build/src/cli/commands/task-invoke.d.ts +19 -4
- package/build/src/cli/commands/task-invoke.js +65 -17
- package/build/src/cli/commands/task-query.d.ts +5 -0
- package/build/src/cli/commands/task-query.js +247 -0
- package/build/src/cli/commands/task.d.ts +3 -1
- package/build/src/cli/commands/task.js +52 -14
- package/build/src/cli/coordinates.d.ts +18 -0
- package/build/src/cli/coordinates.js +120 -0
- package/build/src/cli/draft.d.ts +8 -0
- package/build/src/cli/draft.js +96 -0
- package/build/src/cli/invoke.d.ts +9 -3
- package/build/src/cli/invoke.js +287 -108
- package/build/src/cli/main.js +54 -7
- package/build/src/cli/parse.d.ts +21 -117
- package/build/src/cli/parse.js +79 -84
- package/build/src/cli/render/akuma-tool-command.d.ts +6 -0
- package/build/src/cli/render/akuma-tool-command.js +144 -0
- package/build/src/cli/render/akuma-tool.d.ts +4 -2
- package/build/src/cli/render/akuma-tool.js +35 -5
- package/build/src/cli/render/akuma.d.ts +3 -1
- package/build/src/cli/render/akuma.js +247 -212
- package/build/src/cli/render/audit.d.ts +3 -0
- package/build/src/cli/render/audit.js +104 -0
- package/build/src/cli/render/catalog.d.ts +2 -0
- package/build/src/cli/render/catalog.js +16 -0
- package/build/src/cli/render/contract.d.ts +3 -3
- package/build/src/cli/render/contract.js +200 -54
- package/build/src/cli/render/kanshi.js +272 -129
- package/build/src/cli/render/receipt.d.ts +19 -0
- package/build/src/cli/render/receipt.js +106 -0
- package/build/src/cli/render/refusal.d.ts +16 -2
- package/build/src/cli/render/refusal.js +88 -3
- package/build/src/cli/render/region.d.ts +2 -0
- package/build/src/cli/render/region.js +17 -0
- package/build/src/cli/render/task.d.ts +2 -1
- package/build/src/cli/render/task.js +223 -60
- package/build/src/cli/render/terminal.d.ts +20 -0
- package/build/src/cli/render/terminal.js +121 -0
- package/build/src/cli/render/text.js +11 -3
- package/build/src/cli/result.d.ts +126 -19
- package/build/src/cli/usage.d.ts +1 -0
- package/build/src/cli/usage.js +3 -0
- package/build/src/contract-worktree.d.ts +44 -0
- package/build/src/contract-worktree.js +251 -0
- package/build/src/coordination/durable-file.d.ts +5 -1
- package/build/src/coordination/durable-file.js +57 -10
- package/build/src/coordination/sqlite-transaction-lock.d.ts +4 -0
- package/build/src/coordination/sqlite-transaction-lock.js +22 -5
- package/build/src/core/facts/codec.js +23 -4
- package/build/src/core/facts/eligibility.d.ts +1 -10
- package/build/src/core/facts/eligibility.js +0 -49
- package/build/src/core/facts/fold.js +1 -6
- package/build/src/core/facts/gate.d.ts +1 -0
- package/build/src/core/facts/gate.js +1 -0
- package/build/src/core/facts/observation.d.ts +3 -0
- package/build/src/core/facts/observation.js +12 -0
- package/build/src/core/facts/types.d.ts +10 -3
- package/build/src/core/subject.js +2 -2
- package/build/src/core/verbs/amend.d.ts +1 -1
- package/build/src/core/verbs/amend.js +3 -18
- package/build/src/core/verbs/bind.js +2 -16
- package/build/src/core/verbs/deliver.d.ts +1 -1
- package/build/src/core/verbs/deliver.js +14 -4
- package/build/src/core/verbs/placement.d.ts +1 -1
- package/build/src/core/verbs/placement.js +7 -9
- package/build/src/dispatch/index.d.ts +5 -3
- package/build/src/dispatch/index.js +30 -21
- package/build/src/duration.d.ts +10 -0
- package/build/src/duration.js +22 -0
- package/build/src/git/admission.d.ts +2 -2
- package/build/src/git/admission.js +33 -23
- package/build/src/git/hooks.d.ts +15 -0
- package/build/src/git/hooks.js +70 -18
- package/build/src/git/identity.d.ts +5 -1
- package/build/src/git/identity.js +23 -4
- package/build/src/git/integration.d.ts +48 -0
- package/build/src/git/integration.js +250 -0
- package/build/src/git/observe.d.ts +29 -15
- package/build/src/git/observe.js +185 -76
- package/build/src/git/read-observation.d.ts +41 -0
- package/build/src/git/read-observation.js +361 -0
- package/build/src/git/reconcile.d.ts +17 -5
- package/build/src/git/reconcile.js +305 -109
- package/build/src/git/repository.d.ts +41 -18
- package/build/src/git/repository.js +220 -114
- package/build/src/git/scratch.d.ts +17 -0
- package/build/src/git/scratch.js +73 -0
- package/build/src/git/target-placement.d.ts +92 -0
- package/build/src/git/target-placement.js +375 -0
- package/build/src/git/tender.d.ts +49 -0
- package/build/src/git/tender.js +63 -0
- package/build/src/git/terminal-seal.d.ts +24 -0
- package/build/src/git/terminal-seal.js +89 -0
- package/build/src/git/tree.d.ts +2 -2
- package/build/src/git/tree.js +4 -0
- package/build/src/git/workspace.d.ts +53 -0
- package/build/src/git/workspace.js +139 -0
- package/build/src/identity/selector.d.ts +6 -0
- package/build/src/identity/selector.js +19 -0
- package/build/src/index.d.ts +4 -2
- package/build/src/index.js +2 -1
- package/build/src/kanshi/index.d.ts +2 -2
- package/build/src/kanshi/index.js +1 -1
- package/build/src/kanshi/read.d.ts +7 -3
- package/build/src/kanshi/read.js +245 -45
- package/build/src/kanshi/report.d.ts +81 -6
- package/build/src/kanshi/select.d.ts +5 -0
- package/build/src/kanshi/select.js +33 -1
- package/build/src/library/address.d.ts +57 -0
- package/build/src/library/address.js +165 -0
- package/build/src/library/akuma-creation.d.ts +16 -7
- package/build/src/library/akuma-creation.js +96 -22
- package/build/src/library/audit.d.ts +18 -0
- package/build/src/library/audit.js +44 -0
- package/build/src/library/bind.d.ts +21 -0
- package/build/src/library/bind.js +56 -0
- package/build/src/library/catalog.d.ts +58 -0
- package/build/src/library/catalog.js +69 -0
- package/build/src/library/configuration.d.ts +6 -15
- package/build/src/library/configuration.js +14 -17
- package/build/src/library/contract.d.ts +23 -49
- package/build/src/library/contract.js +171 -185
- package/build/src/library/delivery.d.ts +24 -0
- package/build/src/library/delivery.js +21 -0
- package/build/src/library/fleet.d.ts +65 -0
- package/build/src/library/fleet.js +153 -0
- package/build/src/library/input.d.ts +3 -0
- package/build/src/library/input.js +19 -0
- package/build/src/library/keiyaku.d.ts +18 -3
- package/build/src/library/keiyaku.js +12 -1
- package/build/src/library/mutation.d.ts +39 -0
- package/build/src/library/mutation.js +37 -0
- package/build/src/library/reconcile.d.ts +29 -0
- package/build/src/library/reconcile.js +179 -0
- package/build/src/library/refusal.d.ts +21 -0
- package/build/src/library/refusal.js +29 -0
- package/build/src/library/region.d.ts +14 -1
- package/build/src/library/region.js +18 -4
- package/build/src/library/repo.d.ts +4 -13
- package/build/src/library/repo.js +14 -24
- package/build/src/protocol/attempt.d.ts +16 -3
- package/build/src/protocol/attempt.js +41 -10
- package/build/src/protocol/bind.d.ts +9 -2
- package/build/src/protocol/bind.js +48 -40
- package/build/src/protocol/intent.d.ts +57 -20
- package/build/src/protocol/intent.js +55 -79
- package/build/src/protocol/operations.d.ts +152 -43
- package/build/src/protocol/operations.js +482 -166
- package/build/src/protocol/outcome.d.ts +14 -3
- package/build/src/protocol/outcome.js +11 -3
- package/build/src/protocol/placement.d.ts +41 -0
- package/build/src/protocol/placement.js +147 -0
- package/build/src/protocol/read/documents.d.ts +2 -2
- package/build/src/protocol/read/documents.js +4 -4
- package/build/src/protocol/read/status.d.ts +18 -5
- package/build/src/protocol/read/status.js +73 -13
- package/build/src/protocol/run.d.ts +49 -7
- package/build/src/protocol/run.js +53 -25
- package/build/src/runtime/proc/line-rpc.d.ts +4 -8
- package/build/src/runtime/proc/line-rpc.js +22 -28
- package/build/src/runtime/proc/run.d.ts +23 -21
- package/build/src/runtime/proc/run.js +123 -89
- package/build/src/runtime/proc/stdio.d.ts +18 -0
- package/build/src/runtime/proc/stdio.js +58 -0
- package/build/src/settings.d.ts +7 -1
- package/build/src/settings.js +32 -17
- package/build/src/settlement/fence.d.ts +5 -0
- package/build/src/settlement/fence.js +14 -0
- package/build/src/settlement/holder.d.ts +35 -4
- package/build/src/settlement/holder.js +127 -28
- package/build/src/settlement/settle.d.ts +9 -1
- package/build/src/settlement/settle.js +74 -37
- package/build/src/task/board.d.ts +23 -5
- package/build/src/task/board.js +17 -10
- package/build/src/task/compose.d.ts +2 -2
- package/build/src/task/compose.js +18 -10
- package/build/src/task/context.d.ts +3 -3
- package/build/src/task/context.js +15 -20
- package/build/src/task/document.d.ts +2 -1
- package/build/src/task/document.js +15 -5
- package/build/src/task/index.d.ts +27 -15
- package/build/src/task/index.js +57 -35
- package/build/src/task/operations.d.ts +38 -13
- package/build/src/task/operations.js +80 -46
- package/build/src/task/query.d.ts +67 -0
- package/build/src/task/query.js +279 -0
- package/build/src/task/store.d.ts +5 -7
- package/build/src/task/store.js +43 -28
- package/build/src/verification/declaration.d.ts +1 -0
- package/build/src/verification/execution.d.ts +51 -0
- package/build/src/verification/execution.js +108 -0
- package/build/src/workspace-place.d.ts +41 -0
- package/build/src/workspace-place.js +386 -0
- package/build/src/world.d.ts +24 -0
- package/build/src/world.js +136 -0
- package/package.json +8 -4
- package/build/src/akuma/activity.d.ts +0 -66
- package/build/src/akuma/activity.js +0 -139
- package/build/src/akuma/providers/claude.js +0 -305
- package/build/src/context-root.d.ts +0 -4
- package/build/src/context-root.js +0 -15
- package/build/src/git/delivery.d.ts +0 -20
- package/build/src/git/delivery.js +0 -109
- package/build/src/git/verification.d.ts +0 -14
- package/build/src/git/verification.js +0 -56
- package/build/src/protocol/read/audit.d.ts +0 -25
- package/build/src/protocol/read/audit.js +0 -39
- package/build/src/verification/producer.d.ts +0 -23
- package/build/src/verification/producer.js +0 -54
|
@@ -7,7 +7,7 @@ function values(command, name) {
|
|
|
7
7
|
const item = command.flags[name];
|
|
8
8
|
return Array.isArray(item) ? item : undefined;
|
|
9
9
|
}
|
|
10
|
-
function namespace(raw) { return raw === undefined ? undefined : raw === "" ? [] : raw.split("/"); }
|
|
10
|
+
function namespace(raw) { return raw === undefined ? undefined : raw === "/" ? [] : raw.split("/"); }
|
|
11
11
|
function priority(raw) {
|
|
12
12
|
if (raw === undefined)
|
|
13
13
|
return undefined;
|
|
@@ -16,10 +16,17 @@ function priority(raw) {
|
|
|
16
16
|
}
|
|
17
17
|
function state(raw) { return raw; }
|
|
18
18
|
function ids(items) { return items; }
|
|
19
|
-
function
|
|
19
|
+
function limit(command) { const raw = value(command, "limit"); return raw === undefined ? undefined : Number(raw); }
|
|
20
|
+
async function invokeAdd(tasks, command, input) {
|
|
20
21
|
const selectedNamespace = namespace(value(command, "namespace"));
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
const actor = input.actor;
|
|
23
|
+
if (command.stdin === "document") {
|
|
24
|
+
return tasks.addDocument({
|
|
25
|
+
markdown: await input.readStdin(),
|
|
26
|
+
...(selectedNamespace === undefined ? {} : { namespace: selectedNamespace }),
|
|
27
|
+
...(actor === undefined ? {} : { actor }),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
23
30
|
const body = value(command, "body"), note = value(command, "note"), initialState = state(value(command, "state")), selectedPriority = priority(value(command, "priority"));
|
|
24
31
|
const needs = ids(values(command, "needs")), parent = value(command, "parent");
|
|
25
32
|
const supersedes = ids(values(command, "supersedes")), relates = ids(values(command, "relates"));
|
|
@@ -28,11 +35,12 @@ function invokeAdd(tasks, command, readStdin) {
|
|
|
28
35
|
...(body === undefined ? {} : { body }), ...(note === undefined ? {} : { note }), ...(initialState === undefined ? {} : { state: initialState }), ...(selectedPriority === undefined ? {} : { priority: selectedPriority }),
|
|
29
36
|
...(needs === undefined ? {} : { needs }), ...(parent === undefined ? {} : { parent: parent }),
|
|
30
37
|
...(supersedes === undefined ? {} : { supersedes }), ...(relates === undefined ? {} : { relates }),
|
|
38
|
+
...(actor === undefined ? {} : { actor }),
|
|
31
39
|
});
|
|
32
40
|
}
|
|
33
|
-
function invokeUpdate(tasks, command, readStdin) {
|
|
34
|
-
const body = command.stdin === "body" ? readStdin() : value(command, "body"), appendBody = command.stdin === "append" ? readStdin() : value(command, "append");
|
|
35
|
-
const note = command.stdin === "note" ? readStdin() : value(command, "note");
|
|
41
|
+
async function invokeUpdate(tasks, command, readStdin) {
|
|
42
|
+
const body = command.stdin === "body" ? await readStdin() : value(command, "body"), appendBody = command.stdin === "append" ? await readStdin() : value(command, "append");
|
|
43
|
+
const note = command.stdin === "note" ? await readStdin() : value(command, "note");
|
|
36
44
|
const title = value(command, "title"), selectedPriority = priority(value(command, "priority"));
|
|
37
45
|
const addNeeds = ids(values(command, "needs")), dropNeeds = ids(values(command, "drop-needs"));
|
|
38
46
|
const parent = value(command, "parent"), addSupersedes = ids(values(command, "supersedes"));
|
|
@@ -53,29 +61,69 @@ async function invokeRead(tasks, command) {
|
|
|
53
61
|
const detail = await tasks.task({ id }).read();
|
|
54
62
|
return detail ?? { kind: "refused", refusal: { kind: "task-missing", taskId: id } };
|
|
55
63
|
}
|
|
56
|
-
case "ls": return tasks.list({ selection: command.flags.all === true ? "all" : command.flags.closed === true ? "closed" : "active", ...(command.flags.world === true ? { scope: "world" } : {}) });
|
|
57
|
-
case "ready": return tasks.ready(command.flags.world === true ? { scope: "world" } : {});
|
|
58
|
-
case "blocked": return tasks.blocked(command.flags.world === true ? { scope: "world" } : {});
|
|
59
|
-
case "
|
|
64
|
+
case "ls": return tasks.list({ selection: command.flags.all === true ? "all" : command.flags.closed === true ? "closed" : "active", ...(command.flags.world === true ? { scope: "world" } : {}), ...(limit(command) === undefined ? {} : { limit: limit(command) }) });
|
|
65
|
+
case "ready": return tasks.ready({ ...(command.flags.world === true ? { scope: "world" } : {}), ...(value(command, "parent") === undefined ? {} : { parent: value(command, "parent") }), ...(limit(command) === undefined ? {} : { limit: limit(command) }) });
|
|
66
|
+
case "blocked": return tasks.blocked({ ...(command.flags.world === true ? { scope: "world" } : {}), ...(value(command, "parent") === undefined ? {} : { parent: value(command, "parent") }), ...(limit(command) === undefined ? {} : { limit: limit(command) }) });
|
|
67
|
+
case "query": return tasks.query({ ...(command.where === undefined ? {} : { where: command.where }), ...(command.flags.world === true ? { scope: "world" } : {}), ...(value(command, "sort") === undefined ? {} : { sort: value(command, "sort") }), ...(limit(command) === undefined ? {} : { limit: limit(command) }) });
|
|
68
|
+
case "tree": return tasks.task({ id }).tree();
|
|
60
69
|
case "doctor": return tasks.doctor();
|
|
61
70
|
default: throw new Error(`task action is not a read: ${command.action}`);
|
|
62
71
|
}
|
|
63
72
|
}
|
|
73
|
+
function isWorldObservation(command) {
|
|
74
|
+
return command.action === "ls" || command.action === "ready" || command.action === "blocked"
|
|
75
|
+
|| command.action === "query" || command.action === "doctor";
|
|
76
|
+
}
|
|
77
|
+
function diagnostic(error) { return error instanceof Error ? error.message : String(error); }
|
|
78
|
+
async function observeWorldRead(tasks, command) {
|
|
79
|
+
try {
|
|
80
|
+
return { kind: "present", value: await invokeRead(tasks, command) };
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
return { kind: "failed", failure: { message: diagnostic(error) } };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function missingWorld(command) {
|
|
87
|
+
const missing = (id) => ({
|
|
88
|
+
kind: "refused",
|
|
89
|
+
refusal: { kind: "task-missing", taskId: id },
|
|
90
|
+
});
|
|
91
|
+
if (isWorldObservation(command))
|
|
92
|
+
return { kind: "absent" };
|
|
93
|
+
if (command.action === "namespace")
|
|
94
|
+
return { kind: "accepted", value: [] };
|
|
95
|
+
if (command.action === "hold" || command.action === "done" || command.action === "drop") {
|
|
96
|
+
return { items: command.positionals.map((id) => ({ id: id, outcome: missing(id) })) };
|
|
97
|
+
}
|
|
98
|
+
return missing(command.positionals[0]);
|
|
99
|
+
}
|
|
64
100
|
export async function invokeTask(command, input) {
|
|
65
|
-
|
|
66
|
-
if (
|
|
101
|
+
let world = input.world;
|
|
102
|
+
if (world === null && (command.action === "add" || command.action === "compose"
|
|
103
|
+
|| (command.action === "namespace" && command.positionals.length > 0))) {
|
|
104
|
+
world = await input.establish();
|
|
105
|
+
}
|
|
106
|
+
if (world === null)
|
|
107
|
+
return missingWorld(command);
|
|
108
|
+
const tasks = Tasks.of(world);
|
|
109
|
+
if (isWorldObservation(command))
|
|
110
|
+
return observeWorldRead(tasks, command);
|
|
111
|
+
if (command.action === "show" || command.action === "tree")
|
|
67
112
|
return invokeRead(tasks, command);
|
|
68
113
|
if (command.action === "add")
|
|
69
|
-
return invokeAdd(tasks, command, input
|
|
114
|
+
return await invokeAdd(tasks, command, input);
|
|
70
115
|
if (command.action === "update")
|
|
71
|
-
return invokeUpdate(tasks, command, input.readStdin);
|
|
116
|
+
return await invokeUpdate(tasks, command, input.readStdin);
|
|
72
117
|
const id = command.positionals[0];
|
|
73
118
|
switch (command.action) {
|
|
74
119
|
case "start": return tasks.task({ id }).start();
|
|
75
120
|
case "stop": return tasks.task({ id }).stop();
|
|
76
121
|
case "resume": return tasks.task({ id }).resume();
|
|
77
122
|
case "hold": return tasks.batch({ verb: "hold", ids: command.positionals });
|
|
78
|
-
case "done":
|
|
123
|
+
case "done": {
|
|
124
|
+
const note = value(command, "note");
|
|
125
|
+
return tasks.batch({ verb: "done", ids: command.positionals, ...(note === undefined ? {} : { note }) });
|
|
126
|
+
}
|
|
79
127
|
case "drop": {
|
|
80
128
|
const note = value(command, "note");
|
|
81
129
|
return tasks.batch({ verb: "drop", ids: command.positionals, ...(note === undefined ? {} : { note }) });
|
|
@@ -86,7 +134,7 @@ export async function invokeTask(command, input) {
|
|
|
86
134
|
await tasks.setNamespace({ namespace: selected });
|
|
87
135
|
return tasks.namespace();
|
|
88
136
|
}
|
|
89
|
-
case "compose": return tasks.compose({ markdown: input.readStdin() });
|
|
137
|
+
case "compose": return tasks.compose({ markdown: await input.readStdin(), ...(input.actor === undefined ? {} : { actor: input.actor }) });
|
|
90
138
|
default: throw new Error(`task action has no invocation: ${command.action}`);
|
|
91
139
|
}
|
|
92
140
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { TaskQueryExpression } from "../../task/index.js";
|
|
2
|
+
export type { TaskQueryExpression } from "../../task/index.js";
|
|
3
|
+
export declare function parseTaskQueryExpression(source: string): TaskQueryExpression;
|
|
4
|
+
export declare function validateTaskLimit(source: string): void;
|
|
5
|
+
export declare function validateTaskParent(source: string): void;
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { MAX_TASK_LIMIT, normalizeTaskQuery } from "../../task/query.js";
|
|
2
|
+
function syntax(message, offset) { throw new Error(`${message} at column ${offset + 1}`); }
|
|
3
|
+
function scanString(source, offset) {
|
|
4
|
+
let index = offset + 1;
|
|
5
|
+
let value = "";
|
|
6
|
+
while (index < source.length && source[index] !== "\"") {
|
|
7
|
+
const character = source[index];
|
|
8
|
+
if (character !== "\\") {
|
|
9
|
+
value += character;
|
|
10
|
+
index += 1;
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
const escaped = source[index + 1];
|
|
14
|
+
if (escaped === undefined)
|
|
15
|
+
syntax("unterminated string", offset);
|
|
16
|
+
if (escaped !== "\\" && escaped !== "\"")
|
|
17
|
+
syntax("only \\\\ and \\\" escapes are supported", index);
|
|
18
|
+
value += escaped;
|
|
19
|
+
index += 2;
|
|
20
|
+
}
|
|
21
|
+
if (source[index] !== "\"")
|
|
22
|
+
syntax("unterminated string", offset);
|
|
23
|
+
return { token: { kind: "string", value, offset }, next: index + 1 };
|
|
24
|
+
}
|
|
25
|
+
function scanOperator(source, offset) {
|
|
26
|
+
const pair = source.slice(offset, offset + 2);
|
|
27
|
+
if (pair === "!=" || pair === "<=" || pair === ">=") {
|
|
28
|
+
return { token: { kind: "operator", value: pair, offset }, next: offset + 2 };
|
|
29
|
+
}
|
|
30
|
+
const character = source[offset];
|
|
31
|
+
if (character !== "=" && character !== "<" && character !== ">" && character !== "~")
|
|
32
|
+
return null;
|
|
33
|
+
return { token: { kind: "operator", value: character, offset }, next: offset + 1 };
|
|
34
|
+
}
|
|
35
|
+
function scanWord(source, offset) {
|
|
36
|
+
let index = offset;
|
|
37
|
+
while (index < source.length && !/[\s()=!<>~"]/u.test(source[index]))
|
|
38
|
+
index += 1;
|
|
39
|
+
if (index === offset)
|
|
40
|
+
syntax(`unexpected ${JSON.stringify(source[offset])}`, offset);
|
|
41
|
+
return { token: { kind: "word", value: source.slice(offset, index), offset }, next: index };
|
|
42
|
+
}
|
|
43
|
+
function lex(source) {
|
|
44
|
+
const tokens = [];
|
|
45
|
+
let index = 0;
|
|
46
|
+
while (index < source.length) {
|
|
47
|
+
if (/\s/u.test(source[index])) {
|
|
48
|
+
index += 1;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const offset = index, character = source[index];
|
|
52
|
+
if (character === "(") {
|
|
53
|
+
tokens.push({ kind: "left", value: character, offset });
|
|
54
|
+
index += 1;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (character === ")") {
|
|
58
|
+
tokens.push({ kind: "right", value: character, offset });
|
|
59
|
+
index += 1;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (character === "\"") {
|
|
63
|
+
const scanned = scanString(source, offset);
|
|
64
|
+
tokens.push(scanned.token);
|
|
65
|
+
index = scanned.next;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const operator = scanOperator(source, offset);
|
|
69
|
+
if (operator !== null) {
|
|
70
|
+
tokens.push(operator.token);
|
|
71
|
+
index = operator.next;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const word = scanWord(source, offset);
|
|
75
|
+
tokens.push(word.token);
|
|
76
|
+
index = word.next;
|
|
77
|
+
}
|
|
78
|
+
tokens.push({ kind: "end", value: "", offset: source.length });
|
|
79
|
+
return tokens;
|
|
80
|
+
}
|
|
81
|
+
function canonicalTaskId(token, field) {
|
|
82
|
+
if (!token.value.startsWith("task/"))
|
|
83
|
+
return syntax(`${field} requires a canonical TaskId`, token.offset);
|
|
84
|
+
return token.value;
|
|
85
|
+
}
|
|
86
|
+
function state(token) {
|
|
87
|
+
if (token.value !== "open" && token.value !== "in_progress" && token.value !== "on_hold" && token.value !== "done" && token.value !== "drop") {
|
|
88
|
+
return syntax(`invalid state ${JSON.stringify(token.value)}`, token.offset);
|
|
89
|
+
}
|
|
90
|
+
return token.value;
|
|
91
|
+
}
|
|
92
|
+
function priority(token) {
|
|
93
|
+
const value = Number(token.value);
|
|
94
|
+
if (!Number.isInteger(value) || value < 0 || value > 3)
|
|
95
|
+
return syntax("priority requires 0..3", token.offset);
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
function boolean(token) {
|
|
99
|
+
if (token.value === "true")
|
|
100
|
+
return true;
|
|
101
|
+
if (token.value === "false")
|
|
102
|
+
return false;
|
|
103
|
+
return syntax("boolean predicate requires true or false", token.offset);
|
|
104
|
+
}
|
|
105
|
+
function timestamp(token, field) {
|
|
106
|
+
const milliseconds = Date.parse(token.value);
|
|
107
|
+
if (!Number.isFinite(milliseconds) || new Date(milliseconds).toISOString() !== token.value)
|
|
108
|
+
return syntax(`${field} requires a canonical UTC ISO timestamp`, token.offset);
|
|
109
|
+
return token.value;
|
|
110
|
+
}
|
|
111
|
+
function equalityOperator(token, field) {
|
|
112
|
+
if (token.value !== "=" && token.value !== "!=")
|
|
113
|
+
syntax(`${field} supports only = and !=`, token.offset);
|
|
114
|
+
return token.value;
|
|
115
|
+
}
|
|
116
|
+
function orderedOperator(token, field) {
|
|
117
|
+
if (token.value === "~")
|
|
118
|
+
syntax(`${field} operator is invalid`, token.offset);
|
|
119
|
+
return token.value;
|
|
120
|
+
}
|
|
121
|
+
function textOperator(token, field) {
|
|
122
|
+
if (token.value !== "=" && token.value !== "!=" && token.value !== "~") {
|
|
123
|
+
syntax(`${field} supports only =, !=, and ~`, token.offset);
|
|
124
|
+
}
|
|
125
|
+
return token.value;
|
|
126
|
+
}
|
|
127
|
+
function parseTextPredicate(field, operator, value) {
|
|
128
|
+
const selected = textOperator(operator, field);
|
|
129
|
+
if (field === "title" && value.kind !== "string")
|
|
130
|
+
syntax("title values must use double quotes", value.offset);
|
|
131
|
+
if (field === "id" && selected !== "~")
|
|
132
|
+
canonicalTaskId(value, "id");
|
|
133
|
+
return { field, operator: selected, value: value.value };
|
|
134
|
+
}
|
|
135
|
+
function parseRelationPredicate(field, operator, value) {
|
|
136
|
+
return { field, operator: equalityOperator(operator, field), value: canonicalTaskId(value, field) };
|
|
137
|
+
}
|
|
138
|
+
function parseBooleanPredicate(field, operator, value) {
|
|
139
|
+
return { field, operator: equalityOperator(operator, field), value: boolean(value) };
|
|
140
|
+
}
|
|
141
|
+
function parseTimestampPredicate(field, operator, value) {
|
|
142
|
+
return { field, operator: orderedOperator(operator, field), value: timestamp(value, field) };
|
|
143
|
+
}
|
|
144
|
+
class Parser {
|
|
145
|
+
tokens;
|
|
146
|
+
index = 0;
|
|
147
|
+
constructor(tokens) {
|
|
148
|
+
this.tokens = tokens;
|
|
149
|
+
}
|
|
150
|
+
current() { return this.tokens[this.index]; }
|
|
151
|
+
take() { const token = this.current(); this.index += 1; return token; }
|
|
152
|
+
word(value) { return this.current().kind === "word" && this.current().value === value; }
|
|
153
|
+
parse() {
|
|
154
|
+
const value = this.or();
|
|
155
|
+
if (this.current().kind !== "end")
|
|
156
|
+
syntax(`unexpected ${JSON.stringify(this.current().value)}`, this.current().offset);
|
|
157
|
+
return value;
|
|
158
|
+
}
|
|
159
|
+
or() {
|
|
160
|
+
const terms = [this.and()];
|
|
161
|
+
while (this.word("or")) {
|
|
162
|
+
this.take();
|
|
163
|
+
terms.push(this.and());
|
|
164
|
+
}
|
|
165
|
+
return terms.length === 1 ? terms[0] : { kind: "or", terms };
|
|
166
|
+
}
|
|
167
|
+
and() {
|
|
168
|
+
const terms = [this.not()];
|
|
169
|
+
while (this.word("and")) {
|
|
170
|
+
this.take();
|
|
171
|
+
terms.push(this.not());
|
|
172
|
+
}
|
|
173
|
+
return terms.length === 1 ? terms[0] : { kind: "and", terms };
|
|
174
|
+
}
|
|
175
|
+
not() {
|
|
176
|
+
if (this.word("not")) {
|
|
177
|
+
this.take();
|
|
178
|
+
return { kind: "not", term: this.not() };
|
|
179
|
+
}
|
|
180
|
+
return this.primary();
|
|
181
|
+
}
|
|
182
|
+
primary() {
|
|
183
|
+
if (this.current().kind === "left") {
|
|
184
|
+
const opening = this.take();
|
|
185
|
+
const value = this.or();
|
|
186
|
+
if (this.current().kind !== "right")
|
|
187
|
+
syntax("missing closing parenthesis", opening.offset);
|
|
188
|
+
this.take();
|
|
189
|
+
return value;
|
|
190
|
+
}
|
|
191
|
+
return { kind: "predicate", predicate: this.predicate() };
|
|
192
|
+
}
|
|
193
|
+
predicate() {
|
|
194
|
+
const field = this.take();
|
|
195
|
+
if (field.kind !== "word")
|
|
196
|
+
syntax("expected query field", field.offset);
|
|
197
|
+
if ((field.value === "ready" || field.value === "blocked") && this.current().kind !== "operator") {
|
|
198
|
+
return { field: field.value, operator: "=", value: true };
|
|
199
|
+
}
|
|
200
|
+
const operator = this.take();
|
|
201
|
+
if (operator.kind !== "operator")
|
|
202
|
+
syntax(`expected operator after ${field.value}`, operator.offset);
|
|
203
|
+
const value = this.take();
|
|
204
|
+
if (value.kind !== "word" && value.kind !== "string")
|
|
205
|
+
syntax(`expected value for ${field.value}`, value.offset);
|
|
206
|
+
return this.typedPredicate(field, operator, value);
|
|
207
|
+
}
|
|
208
|
+
typedPredicate(field, operator, value) {
|
|
209
|
+
switch (field.value) {
|
|
210
|
+
case "state": return { field: "state", operator: equalityOperator(operator, "state"), value: state(value) };
|
|
211
|
+
case "priority": return { field: "priority", operator: orderedOperator(operator, "priority"), value: priority(value) };
|
|
212
|
+
case "title": return parseTextPredicate("title", operator, value);
|
|
213
|
+
case "id": return parseTextPredicate("id", operator, value);
|
|
214
|
+
case "parent": return {
|
|
215
|
+
field: "parent",
|
|
216
|
+
operator: equalityOperator(operator, "parent"),
|
|
217
|
+
value: value.value === "none" ? null : canonicalTaskId(value, "parent"),
|
|
218
|
+
};
|
|
219
|
+
case "under": return parseRelationPredicate("under", operator, value);
|
|
220
|
+
case "needs": return parseRelationPredicate("needs", operator, value);
|
|
221
|
+
case "blocks": return parseRelationPredicate("blocks", operator, value);
|
|
222
|
+
case "ready": return parseBooleanPredicate("ready", operator, value);
|
|
223
|
+
case "blocked": return parseBooleanPredicate("blocked", operator, value);
|
|
224
|
+
case "created": return parseTimestampPredicate("created", operator, value);
|
|
225
|
+
case "updated": return parseTimestampPredicate("updated", operator, value);
|
|
226
|
+
default: return syntax(`unknown query field ${JSON.stringify(field.value)}`, field.offset);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
export function parseTaskQueryExpression(source) {
|
|
231
|
+
if (source.trim().length === 0)
|
|
232
|
+
return syntax("query expression must be nonblank", 0);
|
|
233
|
+
return normalizeTaskQuery(new Parser(lex(source)).parse());
|
|
234
|
+
}
|
|
235
|
+
export function validateTaskLimit(source) {
|
|
236
|
+
if (!/^[1-9][0-9]*$/u.test(source) || Number(source) > MAX_TASK_LIMIT) {
|
|
237
|
+
throw new Error(`--limit must be an integer from 1 to ${MAX_TASK_LIMIT}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
export function validateTaskParent(source) {
|
|
241
|
+
try {
|
|
242
|
+
normalizeTaskQuery({ kind: "predicate", predicate: { field: "under", operator: "=", value: source } });
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
throw new Error("--parent requires a canonical TaskId");
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { type TaskQueryExpression } from "./task-query.js";
|
|
2
|
+
export type TaskAction = "add" | "show" | "ls" | "ready" | "blocked" | "tree" | "doctor" | "update" | "query" | "start" | "stop" | "hold" | "resume" | "done" | "drop" | "namespace" | "compose";
|
|
2
3
|
type TaskFlagValue = string | true | readonly string[];
|
|
3
4
|
type TaskStdin = "document" | "body" | "append" | "note" | "compose";
|
|
4
5
|
export type ParsedTaskCommand = Readonly<{
|
|
@@ -8,6 +9,7 @@ export type ParsedTaskCommand = Readonly<{
|
|
|
8
9
|
positionals: readonly string[];
|
|
9
10
|
flags: Readonly<Record<string, TaskFlagValue>>;
|
|
10
11
|
stdin?: TaskStdin;
|
|
12
|
+
where?: TaskQueryExpression;
|
|
11
13
|
}>;
|
|
12
14
|
export declare function isTaskAction(value: string | undefined): value is TaskAction;
|
|
13
15
|
export declare function renderTaskHelp(action?: TaskAction): string;
|
|
@@ -1,22 +1,25 @@
|
|
|
1
|
-
import { CliUsageError, usageLine } from "../usage.js";
|
|
1
|
+
import { CliUsageError, isBlankInput, usageLine } from "../usage.js";
|
|
2
|
+
import { parseTaskQueryExpression, validateTaskLimit, validateTaskParent, } from "./task-query.js";
|
|
2
3
|
const COMMON = { json: "boolean" };
|
|
3
4
|
const TASK_COMMAND_SPECS = {
|
|
4
5
|
add: {
|
|
5
|
-
arity: [0, 1], stdin: "document", flags: { ...COMMON, namespace: "value", state: "value", priority: "value", needs: "repeat", parent: "value", supersedes: "repeat", relates: "repeat", body: "value", note: "value" },
|
|
6
|
+
arity: [0, 1], stdin: "document", flags: { ...COMMON, namespace: "value", state: "value", priority: "value", needs: "repeat", parent: "value", supersedes: "repeat", relates: "repeat", body: "value", note: "value", actor: "value" },
|
|
6
7
|
usage: `task add <TITLE> [--namespace <ns>] [--priority 0..3]
|
|
7
8
|
[--state open|in_progress|on_hold|done|drop]
|
|
8
|
-
[--note <text>]
|
|
9
|
+
[--note <text>] [--actor <actor>]
|
|
9
10
|
[--needs <TaskId>]... [--parent <TaskId>]
|
|
10
11
|
[--supersedes <TaskId>]... [--relates <TaskId>]...
|
|
11
12
|
[--body <text>] [--json]
|
|
12
|
-
task add [--namespace <ns>] [--json] -`,
|
|
13
|
+
task add [--namespace <ns>] [--actor <actor>] [--json] -`,
|
|
13
14
|
purpose: "Create one Task from flags or a canonical stdin document.",
|
|
14
15
|
},
|
|
15
16
|
show: { arity: [1, 1], flags: COMMON, usage: "task show <TaskId> [--json]", purpose: "Read one Task and its relationships." },
|
|
16
|
-
ls: { arity: [0, 0], flags: { ...COMMON, closed: "boolean", all: "boolean", world: "boolean" }, usage: "task ls [--closed | --all] [--world] [--json]", purpose: "List Tasks in the selected scope." },
|
|
17
|
-
ready: { arity: [0, 0], flags: { ...COMMON, world: "boolean" }, usage: "task ready [--world] [--json]", purpose: "List Tasks whose
|
|
18
|
-
blocked: { arity: [0, 0], flags: { ...COMMON, world: "boolean" }, usage: "task blocked [--world] [--json]", purpose: "List Tasks blocked by dependencies." },
|
|
19
|
-
|
|
17
|
+
ls: { arity: [0, 0], flags: { ...COMMON, closed: "boolean", all: "boolean", world: "boolean", limit: "value" }, usage: "task ls [--closed | --all] [--world] [--limit <n>] [--json]", purpose: "List Tasks in the selected scope." },
|
|
18
|
+
ready: { arity: [0, 0], flags: { ...COMMON, world: "boolean", parent: "value", limit: "value" }, usage: "task ready [--world] [--parent <TaskId>] [--limit <n>] [--json]", purpose: "List open Tasks whose every need is terminal." },
|
|
19
|
+
blocked: { arity: [0, 0], flags: { ...COMMON, world: "boolean", parent: "value", limit: "value" }, usage: "task blocked [--world] [--parent <TaskId>] [--limit <n>] [--json]", purpose: "List Tasks blocked by dependencies." },
|
|
20
|
+
query: { arity: [0, 0], flags: { ...COMMON, where: "value", world: "boolean", sort: "value", limit: "value" }, usage: `task query [--where <expression>] [--world]
|
|
21
|
+
[--sort priority|created|updated|id] [--limit <n>] [--json]`, purpose: "Query Task facts with a typed boolean expression." },
|
|
22
|
+
tree: { arity: [1, 1], flags: COMMON, usage: "task tree <TaskId> [--json]", purpose: "Read one Task parent decomposition tree." },
|
|
20
23
|
doctor: { arity: [0, 0], flags: COMMON, usage: "task doctor [--json]", purpose: "Inspect Task authority without repairing it." },
|
|
21
24
|
update: {
|
|
22
25
|
arity: [1, 1], flags: { ...COMMON, title: "value", body: "value", append: "value", note: "value", priority: "value", needs: "repeat", "drop-needs": "repeat", parent: "value", "no-parent": "boolean", supersedes: "repeat", "drop-supersedes": "repeat", relates: "repeat", "drop-relates": "repeat" },
|
|
@@ -32,10 +35,10 @@ task add [--namespace <ns>] [--json] -`,
|
|
|
32
35
|
stop: { arity: [1, 1], flags: COMMON, usage: "task stop <TaskId> [--json]", purpose: "Return one in-progress Task to open." },
|
|
33
36
|
hold: { arity: [1, Number.POSITIVE_INFINITY], flags: COMMON, usage: "task hold <TaskId>... [--json]", purpose: "Put one or more Tasks on hold." },
|
|
34
37
|
resume: { arity: [1, 1], flags: COMMON, usage: "task resume <TaskId> [--json]", purpose: "Return one held Task to open." },
|
|
35
|
-
done: { arity: [1, Number.POSITIVE_INFINITY], flags: COMMON, usage: "task done <TaskId>... [--json]", purpose: "Mark one or more Tasks done." },
|
|
38
|
+
done: { arity: [1, Number.POSITIVE_INFINITY], flags: { ...COMMON, note: "value" }, usage: "task done <TaskId>... [--note <text>] [--json]", purpose: "Mark one or more Tasks done." },
|
|
36
39
|
drop: { arity: [1, Number.POSITIVE_INFINITY], flags: { ...COMMON, note: "value" }, usage: "task drop <TaskId>... [--note <text>] [--json]", purpose: "Drop one or more Tasks." },
|
|
37
40
|
namespace: { arity: [0, 1], flags: COMMON, usage: "task namespace [<namespace>] [--json]", purpose: "Read or replace the current Task namespace." },
|
|
38
|
-
compose: { arity: [0, 0], stdin: "compose", flags: COMMON, usage: "task compose [--json] -", purpose: "
|
|
41
|
+
compose: { arity: [0, 0], stdin: "compose", flags: { ...COMMON, actor: "value" }, usage: "task compose [--actor <actor>] [--json] -", purpose: "Admit Task documents independently; partial admission has no cross-file atomicity or rollback." },
|
|
39
42
|
};
|
|
40
43
|
export function isTaskAction(value) {
|
|
41
44
|
return value !== undefined && Object.hasOwn(TASK_COMMAND_SPECS, value);
|
|
@@ -64,6 +67,8 @@ function setFlag(flags, name, kind, value, fail) {
|
|
|
64
67
|
}
|
|
65
68
|
if (value === undefined)
|
|
66
69
|
fail(`--${name} requires a value`);
|
|
70
|
+
if (isBlankInput(value))
|
|
71
|
+
fail(`--${name} requires a nonblank value`);
|
|
67
72
|
if (kind === "repeat") {
|
|
68
73
|
const current = flags[name];
|
|
69
74
|
flags[name] = [...(Array.isArray(current) ? current : []), value];
|
|
@@ -99,6 +104,8 @@ function scanTaskArgv(action, argv, fail) {
|
|
|
99
104
|
continue;
|
|
100
105
|
}
|
|
101
106
|
if (!token.startsWith("--")) {
|
|
107
|
+
if (isBlankInput(token))
|
|
108
|
+
fail(`task ${action} requires a nonblank value`);
|
|
102
109
|
positionals.push(token);
|
|
103
110
|
continue;
|
|
104
111
|
}
|
|
@@ -117,18 +124,40 @@ function validateUpdate(scanned, fail) {
|
|
|
117
124
|
if (Object.keys(flags).every((name) => name === "json"))
|
|
118
125
|
fail("task update requires at least one patch option");
|
|
119
126
|
}
|
|
127
|
+
function validateTaskReadFlags(action, flags, fail) {
|
|
128
|
+
if (action === "ls" && flags.closed === true && flags.all === true)
|
|
129
|
+
fail("--closed and --all are mutually exclusive");
|
|
130
|
+
if (typeof flags.limit === "string") {
|
|
131
|
+
try {
|
|
132
|
+
validateTaskLimit(flags.limit);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (typeof flags.sort === "string" && flags.sort !== "priority" && flags.sort !== "created" && flags.sort !== "updated" && flags.sort !== "id") {
|
|
139
|
+
fail("--sort must be priority, created, updated, or id");
|
|
140
|
+
}
|
|
141
|
+
if ((action === "ready" || action === "blocked") && typeof flags.parent === "string") {
|
|
142
|
+
try {
|
|
143
|
+
validateTaskParent(flags.parent);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
120
150
|
function validateTaskScan(action, scanned, fail) {
|
|
121
151
|
const spec = TASK_COMMAND_SPECS[action], { positionals, flags, stdin } = scanned;
|
|
122
152
|
if (positionals.length < spec.arity[0] || positionals.length > spec.arity[1])
|
|
123
153
|
fail(`task ${action} has invalid positional arguments`);
|
|
124
154
|
if (action === "add" && (stdin === "document") === (positionals.length === 1))
|
|
125
155
|
fail("task add requires either TITLE or final '-' input");
|
|
126
|
-
if (action === "add" && stdin === "document" && Object.keys(flags).some((name) => name !== "json" && name !== "namespace"))
|
|
156
|
+
if (action === "add" && stdin === "document" && Object.keys(flags).some((name) => name !== "json" && name !== "namespace" && name !== "actor"))
|
|
127
157
|
fail("task add document input owns its creation fields");
|
|
128
158
|
if (action === "compose" && stdin !== "compose")
|
|
129
159
|
fail("task compose requires final '-' input");
|
|
130
|
-
|
|
131
|
-
fail("--closed and --all are mutually exclusive");
|
|
160
|
+
validateTaskReadFlags(action, flags, fail);
|
|
132
161
|
if (action === "update")
|
|
133
162
|
validateUpdate(scanned, fail);
|
|
134
163
|
}
|
|
@@ -140,5 +169,14 @@ export function parseTaskCommand(argv) {
|
|
|
140
169
|
const fail = (message) => { throw new CliUsageError(message, renderTaskUsage(action)); };
|
|
141
170
|
const scanned = scanTaskArgv(action, argv, fail);
|
|
142
171
|
validateTaskScan(action, scanned, fail);
|
|
143
|
-
|
|
172
|
+
let where;
|
|
173
|
+
if (action === "query" && typeof scanned.flags.where === "string") {
|
|
174
|
+
try {
|
|
175
|
+
where = parseTaskQueryExpression(scanned.flags.where);
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return { command: "task", action, output: scanned.flags.json === true ? "json" : "text", ...scanned, ...(where === undefined ? {} : { where }) };
|
|
144
182
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Repo } from "../index.js";
|
|
2
|
+
import { type WorldRoot } from "../world.js";
|
|
3
|
+
import { type ParsedCommand } from "./parse.js";
|
|
4
|
+
export type CliCoordinates = Readonly<{
|
|
5
|
+
cwd: string;
|
|
6
|
+
repo?: Repo;
|
|
7
|
+
world: WorldRoot | null;
|
|
8
|
+
candidateWorld: WorldRoot | null;
|
|
9
|
+
establishWorld: () => Promise<WorldRoot>;
|
|
10
|
+
}>;
|
|
11
|
+
export type CliCoordinateInput = Readonly<{
|
|
12
|
+
processCwd?: string;
|
|
13
|
+
cwd?: string;
|
|
14
|
+
repo?: string;
|
|
15
|
+
command: ParsedCommand;
|
|
16
|
+
}>;
|
|
17
|
+
export declare function assertExplicitRepoUse(command: ParsedCommand, repo: string | undefined): void;
|
|
18
|
+
export declare function resolveCliCoordinates(input: CliCoordinateInput): Promise<CliCoordinates>;
|