@astrosheep/keiyaku 4.0.1 → 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 +137 -5
- 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 +3 -3
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +91 -35
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +98 -58
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +152 -53
- package/build/src/akuma/abort.d.ts +2 -0
- package/build/src/akuma/abort.js +50 -0
- package/build/src/akuma/akuma.d.ts +23 -14
- package/build/src/akuma/akuma.js +120 -104
- package/build/src/akuma/archetype.d.ts +7 -6
- package/build/src/akuma/archetype.js +68 -41
- package/build/src/akuma/body.d.ts +3 -5
- package/build/src/akuma/body.js +408 -213
- package/build/src/akuma/heart/facts.d.ts +25 -34
- package/build/src/akuma/heart/facts.js +18 -7
- package/build/src/akuma/heart/index.d.ts +39 -42
- package/build/src/akuma/heart/index.js +134 -93
- package/build/src/akuma/heart/rows.d.ts +9 -31
- package/build/src/akuma/heart/rows.js +26 -44
- package/build/src/akuma/heart/schema.d.ts +1 -1
- package/build/src/akuma/heart/schema.js +7 -15
- 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 +28 -14
- package/build/src/akuma/heart/storage.js +86 -26
- package/build/src/akuma/identity.d.ts +2 -2
- package/build/src/akuma/identity.js +9 -9
- package/build/src/akuma/index.d.ts +6 -1
- package/build/src/akuma/index.js +6 -0
- package/build/src/akuma/projection.d.ts +117 -38
- package/build/src/akuma/projection.js +208 -97
- 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 +12 -4
- package/build/src/akuma/provider.js +117 -70
- 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/events.js +66 -13
- package/build/src/akuma/providers/claude/index.d.ts +2 -1
- package/build/src/akuma/providers/claude/index.js +35 -21
- package/build/src/akuma/providers/codex-app-server/events.js +4 -1
- package/build/src/akuma/providers/codex-app-server/index.d.ts +3 -3
- package/build/src/akuma/providers/codex-app-server/index.js +24 -31
- 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 +24 -59
- package/build/src/akuma/providers/opencode-sdk/events.d.ts +40 -34
- package/build/src/akuma/providers/opencode-sdk/events.js +220 -101
- package/build/src/akuma/providers/opencode-sdk/index.d.ts +3 -2
- package/build/src/akuma/providers/opencode-sdk/index.js +233 -128
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +11 -26
- package/build/src/akuma/providers/opencode-sdk/session.js +10 -9
- package/build/src/akuma/providers/pi/events.js +49 -11
- package/build/src/akuma/providers/pi/index.d.ts +1 -1
- package/build/src/akuma/providers/pi/index.js +50 -21
- 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 -7
- package/build/src/akuma/requests.js +92 -65
- package/build/src/alias/index.d.ts +2 -2
- package/build/src/alias/index.js +10 -10
- 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/cli/accepted.d.ts +17 -13
- package/build/src/cli/accepted.js +72 -34
- package/build/src/cli/actor.js +3 -3
- package/build/src/cli/commands/akuma-invoke.d.ts +4 -3
- package/build/src/cli/commands/akuma-invoke.js +14 -9
- package/build/src/cli/commands/akuma.d.ts +13 -3
- package/build/src/cli/commands/akuma.js +67 -34
- package/build/src/cli/commands/contract.d.ts +18 -5
- package/build/src/cli/commands/contract.js +9 -3
- package/build/src/cli/commands/task-invoke.d.ts +17 -4
- package/build/src/cli/commands/task-invoke.js +43 -20
- 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 +51 -13
- 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 +4 -3
- package/build/src/cli/invoke.js +160 -179
- package/build/src/cli/main.js +34 -6
- package/build/src/cli/parse.d.ts +9 -2
- package/build/src/cli/parse.js +29 -7
- 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 +2 -2
- package/build/src/cli/render/akuma-tool.js +30 -4
- package/build/src/cli/render/akuma.d.ts +1 -0
- package/build/src/cli/render/akuma.js +211 -116
- package/build/src/cli/render/audit.d.ts +3 -0
- package/build/src/cli/render/audit.js +104 -0
- package/build/src/cli/render/contract.d.ts +3 -3
- package/build/src/cli/render/contract.js +192 -79
- package/build/src/cli/render/kanshi.js +263 -168
- 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 -20
- 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 +7 -0
- package/build/src/cli/render/terminal.js +46 -0
- package/build/src/cli/render/text.js +6 -3
- package/build/src/cli/result.d.ts +115 -20
- package/build/src/cli/usage.d.ts +1 -0
- package/build/src/cli/usage.js +3 -0
- package/build/src/contract-worktree.d.ts +6 -5
- package/build/src/contract-worktree.js +95 -57
- package/build/src/coordination/durable-file.d.ts +4 -3
- package/build/src/coordination/durable-file.js +29 -25
- 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/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/verbs/amend.d.ts +1 -1
- package/build/src/core/verbs/amend.js +0 -11
- package/build/src/core/verbs/deliver.d.ts +1 -1
- package/build/src/core/verbs/deliver.js +1 -7
- package/build/src/core/verbs/placement.d.ts +1 -1
- package/build/src/core/verbs/placement.js +4 -1
- package/build/src/dispatch/index.d.ts +2 -2
- package/build/src/dispatch/index.js +17 -17
- package/build/src/git/admission.d.ts +1 -1
- package/build/src/git/admission.js +11 -11
- package/build/src/git/hooks.js +13 -13
- package/build/src/git/integration.d.ts +14 -15
- package/build/src/git/integration.js +80 -44
- package/build/src/git/observe.d.ts +5 -3
- package/build/src/git/observe.js +18 -12
- package/build/src/git/read-observation.js +9 -9
- package/build/src/git/reconcile.d.ts +2 -0
- package/build/src/git/reconcile.js +138 -95
- package/build/src/git/repository.d.ts +27 -19
- package/build/src/git/repository.js +155 -83
- package/build/src/git/scratch.d.ts +7 -3
- package/build/src/git/scratch.js +44 -37
- package/build/src/git/target-placement.d.ts +40 -4
- package/build/src/git/target-placement.js +221 -101
- package/build/src/git/tender.d.ts +6 -5
- package/build/src/git/tender.js +24 -43
- package/build/src/git/terminal-seal.d.ts +1 -1
- package/build/src/git/terminal-seal.js +7 -5
- package/build/src/git/workspace.d.ts +36 -4
- package/build/src/git/workspace.js +53 -18
- package/build/src/identity/selector.js +1 -1
- package/build/src/index.d.ts +3 -3
- package/build/src/index.js +1 -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 +2 -1
- package/build/src/kanshi/read.js +118 -27
- package/build/src/kanshi/report.d.ts +50 -0
- package/build/src/kanshi/select.d.ts +5 -0
- package/build/src/kanshi/select.js +32 -0
- package/build/src/library/address.d.ts +12 -3
- package/build/src/library/address.js +77 -46
- package/build/src/library/akuma-creation.d.ts +6 -1
- package/build/src/library/akuma-creation.js +86 -13
- package/build/src/library/audit.d.ts +18 -0
- package/build/src/library/audit.js +44 -0
- package/build/src/library/bind.js +4 -4
- package/build/src/library/catalog.d.ts +1 -1
- package/build/src/library/catalog.js +5 -8
- package/build/src/library/contract.d.ts +5 -6
- package/build/src/library/contract.js +11 -35
- package/build/src/library/delivery.d.ts +2 -1
- package/build/src/library/fleet.d.ts +2 -2
- package/build/src/library/fleet.js +35 -71
- package/build/src/library/keiyaku.d.ts +3 -2
- package/build/src/library/keiyaku.js +1 -0
- package/build/src/library/mutation.d.ts +7 -6
- package/build/src/library/mutation.js +23 -25
- package/build/src/library/reconcile.d.ts +29 -0
- package/build/src/library/reconcile.js +179 -0
- package/build/src/library/region.d.ts +12 -0
- package/build/src/library/region.js +15 -1
- package/build/src/library/repo.d.ts +4 -16
- package/build/src/library/repo.js +13 -38
- package/build/src/protocol/attempt.js +6 -6
- package/build/src/protocol/bind.js +4 -4
- package/build/src/protocol/intent.d.ts +15 -2
- package/build/src/protocol/intent.js +21 -6
- package/build/src/protocol/operations.d.ts +73 -22
- package/build/src/protocol/operations.js +292 -88
- package/build/src/protocol/outcome.d.ts +9 -3
- package/build/src/protocol/outcome.js +3 -1
- package/build/src/protocol/placement.d.ts +15 -4
- package/build/src/protocol/placement.js +58 -25
- package/build/src/protocol/read/status.d.ts +8 -1
- package/build/src/protocol/read/status.js +61 -14
- package/build/src/runtime/proc/line-rpc.d.ts +3 -8
- package/build/src/runtime/proc/line-rpc.js +16 -43
- package/build/src/runtime/proc/run.d.ts +15 -29
- package/build/src/runtime/proc/run.js +113 -105
- 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 +2 -2
- package/build/src/settings.js +8 -8
- package/build/src/settlement/fence.d.ts +0 -4
- package/build/src/settlement/fence.js +0 -9
- package/build/src/settlement/holder.d.ts +11 -1
- package/build/src/settlement/holder.js +25 -0
- package/build/src/settlement/settle.d.ts +1 -1
- package/build/src/settlement/settle.js +52 -89
- package/build/src/task/board.d.ts +3 -3
- package/build/src/task/board.js +6 -9
- package/build/src/task/compose.d.ts +1 -1
- package/build/src/task/compose.js +18 -10
- package/build/src/task/context.d.ts +3 -3
- package/build/src/task/context.js +14 -14
- package/build/src/task/document.d.ts +2 -1
- package/build/src/task/document.js +15 -5
- package/build/src/task/index.d.ts +19 -7
- package/build/src/task/index.js +42 -23
- package/build/src/task/operations.d.ts +15 -9
- package/build/src/task/operations.js +78 -48
- package/build/src/task/query.d.ts +67 -0
- package/build/src/task/query.js +279 -0
- package/build/src/task/store.d.ts +2 -2
- package/build/src/task/store.js +40 -25
- package/build/src/verification/execution.d.ts +4 -2
- package/build/src/verification/execution.js +9 -5
- package/build/src/workspace-place.d.ts +41 -0
- package/build/src/workspace-place.js +386 -0
- package/build/src/world.d.ts +13 -2
- package/build/src/world.js +81 -48
- package/package.json +3 -2
- package/build/src/protocol/read/audit.d.ts +0 -31
- package/build/src/protocol/read/audit.js +0 -55
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { formatTaskId, parseTaskId, sameNamespace } from "./identity.js";
|
|
2
|
+
import { taskDisposition } from "./board.js";
|
|
3
|
+
export const DEFAULT_TASK_LIMIT = 100;
|
|
4
|
+
export const MAX_TASK_LIMIT = 1_000;
|
|
5
|
+
const states = ["open", "in_progress", "on_hold", "done", "drop"];
|
|
6
|
+
const comparisons = ["=", "!=", "<", "<=", ">", ">=", "~"];
|
|
7
|
+
function fail(message) { throw new TypeError(message); }
|
|
8
|
+
function object(value, label) {
|
|
9
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
10
|
+
fail(`${label} must be an object`);
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
function closed(value, fields, label) {
|
|
14
|
+
for (const key of Object.keys(value))
|
|
15
|
+
if (!fields.includes(key))
|
|
16
|
+
fail(`${label} has unknown field: ${key}`);
|
|
17
|
+
}
|
|
18
|
+
function taskId(value, label) {
|
|
19
|
+
if (typeof value !== "string")
|
|
20
|
+
fail(`${label} must be a TaskId`);
|
|
21
|
+
try {
|
|
22
|
+
const coordinate = parseTaskId(value);
|
|
23
|
+
if (formatTaskId(coordinate) !== value)
|
|
24
|
+
fail(`${label} must be a canonical TaskId`);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
fail(`${label} must be a canonical TaskId`);
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
function comparison(value) {
|
|
32
|
+
if (typeof value !== "string" || !comparisons.includes(value))
|
|
33
|
+
fail("query operator is invalid");
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
function timestamp(value, field) {
|
|
37
|
+
if (typeof value !== "string" || !Number.isFinite(Date.parse(value)) || new Date(Date.parse(value)).toISOString() !== value) {
|
|
38
|
+
fail(`${field} must be a canonical UTC ISO timestamp`);
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
function equalityOperator(operator, field) {
|
|
43
|
+
if (operator !== "=" && operator !== "!=")
|
|
44
|
+
fail(`${field} supports only = and !=`);
|
|
45
|
+
return operator;
|
|
46
|
+
}
|
|
47
|
+
function orderedOperator(operator, field) {
|
|
48
|
+
if (operator === "~")
|
|
49
|
+
fail(`${field} does not support ~`);
|
|
50
|
+
return operator;
|
|
51
|
+
}
|
|
52
|
+
function normalizeStatePredicate(input, operator) {
|
|
53
|
+
if (typeof input.value !== "string" || !states.includes(input.value))
|
|
54
|
+
fail("state value is invalid");
|
|
55
|
+
return { field: "state", operator: equalityOperator(operator, "state"), value: input.value };
|
|
56
|
+
}
|
|
57
|
+
function normalizePriorityPredicate(input, operator) {
|
|
58
|
+
if (typeof input.value !== "number" || !Number.isInteger(input.value) || input.value < 0 || input.value > 3) {
|
|
59
|
+
fail("priority value must be 0..3");
|
|
60
|
+
}
|
|
61
|
+
return { field: "priority", operator: orderedOperator(operator, "priority"), value: input.value };
|
|
62
|
+
}
|
|
63
|
+
function normalizeTextPredicate(input, field, operator) {
|
|
64
|
+
if (typeof input.value !== "string" || input.value.length === 0)
|
|
65
|
+
fail(`${field} value must be nonblank`);
|
|
66
|
+
if (operator !== "=" && operator !== "!=" && operator !== "~")
|
|
67
|
+
fail(`${field} supports only =, !=, and ~`);
|
|
68
|
+
if (field === "id" && operator !== "~")
|
|
69
|
+
taskId(input.value, "id value");
|
|
70
|
+
return { field, operator, value: input.value };
|
|
71
|
+
}
|
|
72
|
+
function normalizeParentPredicate(input, operator) {
|
|
73
|
+
const value = input.value === null ? null : taskId(input.value, "parent value");
|
|
74
|
+
return { field: "parent", operator: equalityOperator(operator, "parent"), value };
|
|
75
|
+
}
|
|
76
|
+
function normalizeRelationPredicate(input, field, operator) {
|
|
77
|
+
return { field, operator: equalityOperator(operator, field), value: taskId(input.value, `${field} value`) };
|
|
78
|
+
}
|
|
79
|
+
function normalizeBooleanPredicate(input, field, operator) {
|
|
80
|
+
if (typeof input.value !== "boolean")
|
|
81
|
+
fail(`${field} value must be boolean`);
|
|
82
|
+
return { field, operator: equalityOperator(operator, field), value: input.value };
|
|
83
|
+
}
|
|
84
|
+
function normalizeTimestampPredicate(input, field, operator) {
|
|
85
|
+
return { field, operator: orderedOperator(operator, field), value: timestamp(input.value, `${field} value`) };
|
|
86
|
+
}
|
|
87
|
+
function predicate(value) {
|
|
88
|
+
const input = object(value, "query predicate");
|
|
89
|
+
closed(input, ["field", "operator", "value"], "query predicate");
|
|
90
|
+
const field = input.field;
|
|
91
|
+
if (typeof field !== "string")
|
|
92
|
+
fail("query predicate field is required");
|
|
93
|
+
const operator = comparison(input.operator);
|
|
94
|
+
switch (field) {
|
|
95
|
+
case "state": return normalizeStatePredicate(input, operator);
|
|
96
|
+
case "priority": return normalizePriorityPredicate(input, operator);
|
|
97
|
+
case "title": return normalizeTextPredicate(input, "title", operator);
|
|
98
|
+
case "id": return normalizeTextPredicate(input, "id", operator);
|
|
99
|
+
case "parent": return normalizeParentPredicate(input, operator);
|
|
100
|
+
case "under": return normalizeRelationPredicate(input, "under", operator);
|
|
101
|
+
case "needs": return normalizeRelationPredicate(input, "needs", operator);
|
|
102
|
+
case "blocks": return normalizeRelationPredicate(input, "blocks", operator);
|
|
103
|
+
case "ready": return normalizeBooleanPredicate(input, "ready", operator);
|
|
104
|
+
case "blocked": return normalizeBooleanPredicate(input, "blocked", operator);
|
|
105
|
+
case "created": return normalizeTimestampPredicate(input, "created", operator);
|
|
106
|
+
case "updated": return normalizeTimestampPredicate(input, "updated", operator);
|
|
107
|
+
default: return fail(`unknown query field: ${field}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export function normalizeTaskQuery(value) {
|
|
111
|
+
const input = object(value, "query expression");
|
|
112
|
+
if (input.kind === "predicate") {
|
|
113
|
+
closed(input, ["kind", "predicate"], "query expression");
|
|
114
|
+
return { kind: "predicate", predicate: predicate(input.predicate) };
|
|
115
|
+
}
|
|
116
|
+
if (input.kind === "not") {
|
|
117
|
+
closed(input, ["kind", "term"], "query expression");
|
|
118
|
+
return { kind: "not", term: normalizeTaskQuery(input.term) };
|
|
119
|
+
}
|
|
120
|
+
if (input.kind === "and" || input.kind === "or") {
|
|
121
|
+
closed(input, ["kind", "terms"], "query expression");
|
|
122
|
+
if (!Array.isArray(input.terms) || input.terms.length === 0)
|
|
123
|
+
fail(`${input.kind} query requires terms`);
|
|
124
|
+
return { kind: input.kind, terms: input.terms.map((term) => normalizeTaskQuery(term)) };
|
|
125
|
+
}
|
|
126
|
+
fail("query expression kind is invalid");
|
|
127
|
+
}
|
|
128
|
+
function compare(left, operator, right) {
|
|
129
|
+
if (operator === "=")
|
|
130
|
+
return left === right;
|
|
131
|
+
if (operator === "!=")
|
|
132
|
+
return left !== right;
|
|
133
|
+
if (operator === "<")
|
|
134
|
+
return left < right;
|
|
135
|
+
if (operator === "<=")
|
|
136
|
+
return left <= right;
|
|
137
|
+
if (operator === ">")
|
|
138
|
+
return left > right;
|
|
139
|
+
return left >= right;
|
|
140
|
+
}
|
|
141
|
+
function membership(left, operator, right) { return operator === "=" ? left === right : left !== right; }
|
|
142
|
+
function indexChildren(board) {
|
|
143
|
+
const children = new Map();
|
|
144
|
+
for (const task of board.tasks.values()) {
|
|
145
|
+
if (task.parent === null)
|
|
146
|
+
continue;
|
|
147
|
+
const siblings = children.get(task.parent) ?? [];
|
|
148
|
+
siblings.push(task.id);
|
|
149
|
+
children.set(task.parent, siblings);
|
|
150
|
+
}
|
|
151
|
+
return children;
|
|
152
|
+
}
|
|
153
|
+
function indexReverseNeeds(board) {
|
|
154
|
+
const reverse = new Map();
|
|
155
|
+
for (const task of board.tasks.values()) {
|
|
156
|
+
for (const need of task.needs) {
|
|
157
|
+
const blockers = reverse.get(need) ?? new Set();
|
|
158
|
+
blockers.add(task.id);
|
|
159
|
+
reverse.set(need, blockers);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return reverse;
|
|
163
|
+
}
|
|
164
|
+
function descendants(children, parent) {
|
|
165
|
+
const result = new Set(), pending = [parent];
|
|
166
|
+
while (pending.length > 0) {
|
|
167
|
+
const current = pending.shift();
|
|
168
|
+
for (const child of children.get(current) ?? []) {
|
|
169
|
+
if (result.has(child))
|
|
170
|
+
continue;
|
|
171
|
+
result.add(child);
|
|
172
|
+
pending.push(child);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
177
|
+
function relation(board, reverseNeeds, id, field, target) {
|
|
178
|
+
const task = board.tasks.get(id);
|
|
179
|
+
if (task === undefined)
|
|
180
|
+
return false;
|
|
181
|
+
return field === "needs" ? task.needs.includes(target) : reverseNeeds.get(target)?.has(id) === true;
|
|
182
|
+
}
|
|
183
|
+
function matchText(left, operator, right) {
|
|
184
|
+
return operator === "~" ? left.includes(right) : compare(left, operator, right);
|
|
185
|
+
}
|
|
186
|
+
function matchRelation(selected, operator) {
|
|
187
|
+
return operator === "=" ? selected : !selected;
|
|
188
|
+
}
|
|
189
|
+
function isBlocked(board, task) {
|
|
190
|
+
const active = task.state === "open" || task.state === "in_progress";
|
|
191
|
+
return active && task.needs.some((need) => {
|
|
192
|
+
const target = board.tasks.get(need);
|
|
193
|
+
return target === undefined || (target.state !== "done" && target.state !== "drop");
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
function taskRef(board, id) {
|
|
197
|
+
const task = board.tasks.get(id);
|
|
198
|
+
return task === undefined ? { id, title: null, state: "missing" } : { id, title: task.title, state: task.state };
|
|
199
|
+
}
|
|
200
|
+
function matchesPredicate(board, reverseNeeds, task, value, under) {
|
|
201
|
+
switch (value.field) {
|
|
202
|
+
case "state": return compare(task.state, value.operator, value.value);
|
|
203
|
+
case "priority": return compare(task.priority, value.operator, value.value);
|
|
204
|
+
case "title": return matchText(task.title, value.operator, value.value);
|
|
205
|
+
case "id": return matchText(task.id, value.operator, value.value);
|
|
206
|
+
case "parent": return compare(task.parent ?? "", value.operator, value.value ?? "");
|
|
207
|
+
case "under": return matchRelation(under.get(value.value)?.has(task.id) === true, value.operator);
|
|
208
|
+
case "needs": return matchRelation(relation(board, reverseNeeds, task.id, "needs", value.value), value.operator);
|
|
209
|
+
case "blocks": return matchRelation(relation(board, reverseNeeds, task.id, "blocks", value.value), value.operator);
|
|
210
|
+
case "ready": return membership(taskDisposition(board, task) === "ready", value.operator, value.value);
|
|
211
|
+
case "blocked": return membership(isBlocked(board, task), value.operator, value.value);
|
|
212
|
+
case "created": return compare(task.createdAt, value.operator, value.value);
|
|
213
|
+
case "updated": return compare(task.updatedAt, value.operator, value.value);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function matches(board, reverseNeeds, task, expression, under) {
|
|
217
|
+
if (expression.kind === "not")
|
|
218
|
+
return !matches(board, reverseNeeds, task, expression.term, under);
|
|
219
|
+
if (expression.kind !== "predicate") {
|
|
220
|
+
return expression.kind === "and"
|
|
221
|
+
? expression.terms.every((term) => matches(board, reverseNeeds, task, term, under))
|
|
222
|
+
: expression.terms.some((term) => matches(board, reverseNeeds, task, term, under));
|
|
223
|
+
}
|
|
224
|
+
return matchesPredicate(board, reverseNeeds, task, expression.predicate, under);
|
|
225
|
+
}
|
|
226
|
+
function row(board, reverseNeeds, task) {
|
|
227
|
+
return {
|
|
228
|
+
id: task.id, title: task.title, state: task.state, priority: task.priority, disposition: taskDisposition(board, task),
|
|
229
|
+
parent: task.parent,
|
|
230
|
+
needs: task.needs.map((need) => taskRef(board, need)),
|
|
231
|
+
blocks: [...(reverseNeeds.get(task.id) ?? [])]
|
|
232
|
+
.sort((left, right) => Buffer.compare(Buffer.from(left), Buffer.from(right)))
|
|
233
|
+
.map((id) => taskRef(board, id)),
|
|
234
|
+
createdAt: task.createdAt, updatedAt: task.updatedAt,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function sortTasks(tasks, sort) {
|
|
238
|
+
return [...tasks].sort((left, right) => {
|
|
239
|
+
const tie = Buffer.compare(Buffer.from(left.id), Buffer.from(right.id));
|
|
240
|
+
if (sort === "id")
|
|
241
|
+
return tie;
|
|
242
|
+
if (sort === "priority")
|
|
243
|
+
return left.priority - right.priority || tie;
|
|
244
|
+
const compared = left[sort === "created" ? "createdAt" : "updatedAt"].localeCompare(right[sort === "created" ? "createdAt" : "updatedAt"]);
|
|
245
|
+
return (sort === "updated" ? -compared : compared) || tie;
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
export function projectQuery(board, scope, expression, sort = "priority", limit = DEFAULT_TASK_LIMIT) {
|
|
249
|
+
const children = indexChildren(board);
|
|
250
|
+
const reverseNeeds = indexReverseNeeds(board);
|
|
251
|
+
const under = new Map(queryUnderTargets(expression).map((target) => [target, descendants(children, target)]));
|
|
252
|
+
const selected = sortTasks([...board.tasks.values()].filter((task) => scope === null || sameNamespace(parseTaskId(task.id).namespace, scope))
|
|
253
|
+
.filter((task) => matches(board, reverseNeeds, task, expression, under)), sort);
|
|
254
|
+
const rows = selected.slice(0, limit).map((task) => row(board, reverseNeeds, task));
|
|
255
|
+
return { rows, total: selected.length, returned: rows.length, truncated: rows.length < selected.length };
|
|
256
|
+
}
|
|
257
|
+
export function projectPage(rows, limit = DEFAULT_TASK_LIMIT) {
|
|
258
|
+
const visible = rows.slice(0, limit);
|
|
259
|
+
return { rows: visible, total: rows.length, returned: visible.length, truncated: visible.length < rows.length };
|
|
260
|
+
}
|
|
261
|
+
export function underExpression(parent) {
|
|
262
|
+
return { kind: "predicate", predicate: { field: "under", operator: "=", value: parent } };
|
|
263
|
+
}
|
|
264
|
+
export function queryUnderTargets(expression) {
|
|
265
|
+
const targets = new Set();
|
|
266
|
+
function collect(value) {
|
|
267
|
+
if (value.kind === "predicate" && value.predicate.field === "under")
|
|
268
|
+
targets.add(value.predicate.value);
|
|
269
|
+
else if (value.kind === "not")
|
|
270
|
+
collect(value.term);
|
|
271
|
+
else if (value.kind !== "predicate")
|
|
272
|
+
value.terms.forEach(collect);
|
|
273
|
+
}
|
|
274
|
+
collect(expression);
|
|
275
|
+
return [...targets];
|
|
276
|
+
}
|
|
277
|
+
export function isValidTaskLimit(value) {
|
|
278
|
+
return Number.isInteger(value) && value > 0 && value <= MAX_TASK_LIMIT;
|
|
279
|
+
}
|
|
@@ -5,12 +5,12 @@ export type BoardSnapshot = Readonly<{
|
|
|
5
5
|
board: TaskBoard;
|
|
6
6
|
bytes: ReadonlyMap<TaskId, Uint8Array>;
|
|
7
7
|
}>;
|
|
8
|
-
export declare function readBoard(world: WorldRoot): BoardSnapshot
|
|
8
|
+
export declare function readBoard(world: WorldRoot): Promise<BoardSnapshot>;
|
|
9
9
|
export declare function replaceAuthority(input: Readonly<{
|
|
10
10
|
path: string;
|
|
11
11
|
expected: Uint8Array | null;
|
|
12
12
|
next: Uint8Array;
|
|
13
|
-
}>): "replaced" | "concurrent-modification"
|
|
13
|
+
}>): Promise<"replaced" | "concurrent-modification">;
|
|
14
14
|
export declare function withTaskLocks<T>(input: Readonly<{
|
|
15
15
|
world: WorldRoot;
|
|
16
16
|
allocation: boolean;
|
package/build/src/task/store.js
CHANGED
|
@@ -1,18 +1,37 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { closeSync,
|
|
2
|
+
import { closeSync, fsyncSync, openSync, renameSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { lstat, mkdir, readFile, readdir, unlink } from "node:fs/promises";
|
|
3
4
|
import { dirname, relative, resolve, sep } from "node:path";
|
|
4
5
|
import { acquireSqliteTransactionLock, SqliteTransactionLockError } from "../coordination/sqlite-transaction-lock.js";
|
|
5
6
|
import { parseTaskDocument } from "./document.js";
|
|
6
7
|
import { formatTaskId, parseTaskId, taskAuthorityPath } from "./identity.js";
|
|
8
|
+
function syncTaskDirectory(path) {
|
|
9
|
+
if (process.platform === "win32")
|
|
10
|
+
return;
|
|
11
|
+
const directory = openSync(path, "r");
|
|
12
|
+
try {
|
|
13
|
+
fsyncSync(directory);
|
|
14
|
+
}
|
|
15
|
+
finally {
|
|
16
|
+
closeSync(directory);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
7
19
|
function tasksDirectory(world) { return resolve(world, ".keiyaku", "tasks"); }
|
|
8
|
-
function authorityFiles(directory) {
|
|
9
|
-
if (!existsSync(directory))
|
|
10
|
-
return [];
|
|
20
|
+
async function authorityFiles(directory) {
|
|
11
21
|
const files = [];
|
|
12
|
-
|
|
22
|
+
let entries;
|
|
23
|
+
try {
|
|
24
|
+
entries = await readdir(directory, { withFileTypes: true });
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (error.code === "ENOENT")
|
|
28
|
+
return [];
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
for (const entry of entries) {
|
|
13
32
|
const path = resolve(directory, entry.name);
|
|
14
33
|
if (entry.isDirectory())
|
|
15
|
-
files.push(...authorityFiles(path));
|
|
34
|
+
files.push(...await authorityFiles(path));
|
|
16
35
|
else if (entry.isFile() && entry.name.endsWith(".md"))
|
|
17
36
|
files.push(path);
|
|
18
37
|
else
|
|
@@ -26,16 +45,18 @@ function coordinateFromPath(tasksDirectory, path) {
|
|
|
26
45
|
throw new Error(`invalid Task authority path: ${path}`);
|
|
27
46
|
return parseTaskId(`task/${local.slice(0, -3).split(sep).join("/")}`);
|
|
28
47
|
}
|
|
29
|
-
export function readBoard(world) {
|
|
48
|
+
export async function readBoard(world) {
|
|
30
49
|
const directory = tasksDirectory(world);
|
|
31
|
-
const tasks = new Map()
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
50
|
+
const tasks = new Map();
|
|
51
|
+
const bytes = new Map();
|
|
52
|
+
for (const path of await authorityFiles(directory)) {
|
|
53
|
+
const stat = await lstat(path);
|
|
54
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
35
55
|
throw new Error(`Task authority is not a regular file: ${path}`);
|
|
56
|
+
}
|
|
36
57
|
const coordinate = coordinateFromPath(directory, path);
|
|
37
58
|
const id = formatTaskId(coordinate);
|
|
38
|
-
const source =
|
|
59
|
+
const source = await readFile(path);
|
|
39
60
|
tasks.set(id, parseTaskDocument(source, coordinate));
|
|
40
61
|
bytes.set(id, source);
|
|
41
62
|
}
|
|
@@ -44,9 +65,9 @@ export function readBoard(world) {
|
|
|
44
65
|
function equal(left, right) {
|
|
45
66
|
return left === null || right === null ? left === right : Buffer.from(left).equals(Buffer.from(right));
|
|
46
67
|
}
|
|
47
|
-
function currentBytes(path) {
|
|
68
|
+
async function currentBytes(path) {
|
|
48
69
|
try {
|
|
49
|
-
return
|
|
70
|
+
return await readFile(path);
|
|
50
71
|
}
|
|
51
72
|
catch (error) {
|
|
52
73
|
if (error.code === "ENOENT")
|
|
@@ -54,9 +75,9 @@ function currentBytes(path) {
|
|
|
54
75
|
throw error;
|
|
55
76
|
}
|
|
56
77
|
}
|
|
57
|
-
export function replaceAuthority(input) {
|
|
78
|
+
export async function replaceAuthority(input) {
|
|
58
79
|
const parent = dirname(input.path);
|
|
59
|
-
|
|
80
|
+
await mkdir(parent, { recursive: true });
|
|
60
81
|
const temporary = resolve(parent, `.tmp-${randomBytes(8).toString("hex")}`);
|
|
61
82
|
let descriptor;
|
|
62
83
|
try {
|
|
@@ -65,23 +86,17 @@ export function replaceAuthority(input) {
|
|
|
65
86
|
fsyncSync(descriptor);
|
|
66
87
|
closeSync(descriptor);
|
|
67
88
|
descriptor = undefined;
|
|
68
|
-
if (!equal(currentBytes(input.path), input.expected))
|
|
89
|
+
if (!equal(await currentBytes(input.path), input.expected))
|
|
69
90
|
return "concurrent-modification";
|
|
70
91
|
renameSync(temporary, input.path);
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
fsyncSync(directoryDescriptor);
|
|
74
|
-
}
|
|
75
|
-
finally {
|
|
76
|
-
closeSync(directoryDescriptor);
|
|
77
|
-
}
|
|
92
|
+
syncTaskDirectory(parent);
|
|
78
93
|
return "replaced";
|
|
79
94
|
}
|
|
80
95
|
finally {
|
|
81
96
|
if (descriptor !== undefined)
|
|
82
97
|
closeSync(descriptor);
|
|
83
98
|
try {
|
|
84
|
-
|
|
99
|
+
await unlink(temporary);
|
|
85
100
|
}
|
|
86
101
|
catch { /* renamed or best effort */ }
|
|
87
102
|
}
|
|
@@ -8,6 +8,8 @@ type VerificationVerdict = "satisfied" | "unsatisfied";
|
|
|
8
8
|
export type VerificationTerminalOutcome = Readonly<{
|
|
9
9
|
kind: "terminal";
|
|
10
10
|
verdict: VerificationVerdict;
|
|
11
|
+
passed: number;
|
|
12
|
+
total: number;
|
|
11
13
|
summary?: string;
|
|
12
14
|
}>;
|
|
13
15
|
export type VerificationNonterminalOutcome = Readonly<{
|
|
@@ -40,8 +42,8 @@ export type ExecuteVerificationInput = Readonly<{
|
|
|
40
42
|
candidate: SnapshotId;
|
|
41
43
|
declarations: readonly VerificationDeclaration[];
|
|
42
44
|
environment: NodeJS.ProcessEnv;
|
|
43
|
-
materializeScratchCandidate: (repository: GitRepository, candidate: SnapshotId) => MaterializedScratchCandidate
|
|
44
|
-
projectSettings: (root: string) => Settings
|
|
45
|
+
materializeScratchCandidate: (repository: GitRepository, candidate: SnapshotId) => Promise<MaterializedScratchCandidate>;
|
|
46
|
+
projectSettings: (root: string) => Promise<Settings>;
|
|
45
47
|
signal?: AbortSignal;
|
|
46
48
|
}>;
|
|
47
49
|
/** Execute one disposable Verification attempt over the exact integration snapshot. */
|
|
@@ -29,6 +29,8 @@ function appendSummary(current, diagnostic) {
|
|
|
29
29
|
}
|
|
30
30
|
async function executeDeclarations(input) {
|
|
31
31
|
let verdict = "satisfied";
|
|
32
|
+
let passed = 0;
|
|
33
|
+
const total = input.declarations.length;
|
|
32
34
|
let summary;
|
|
33
35
|
for (const [index, declaration] of input.declarations.entries()) {
|
|
34
36
|
const outcome = await runProcess({
|
|
@@ -45,17 +47,19 @@ async function executeDeclarations(input) {
|
|
|
45
47
|
}
|
|
46
48
|
if (outcome.kind === "spawn-error" || outcome.kind === "unknown-exit" || outcome.kind === "cancelled")
|
|
47
49
|
return outcome;
|
|
48
|
-
if (outcome.code
|
|
50
|
+
if (outcome.code === 0)
|
|
51
|
+
passed += 1;
|
|
52
|
+
else
|
|
49
53
|
verdict = "unsatisfied";
|
|
50
54
|
summary = appendSummary(summary, processDiagnostic(declaration, index, outcome));
|
|
51
55
|
}
|
|
52
|
-
return { kind: "terminal", verdict, ...(summary === undefined ? {} : { summary }) };
|
|
56
|
+
return { kind: "terminal", verdict, passed, total, ...(summary === undefined ? {} : { summary }) };
|
|
53
57
|
}
|
|
54
58
|
/** Execute one disposable Verification attempt over the exact integration snapshot. */
|
|
55
59
|
export async function executeVerification(input) {
|
|
56
60
|
let scratch;
|
|
57
61
|
try {
|
|
58
|
-
scratch = input.materializeScratchCandidate(input.repository, input.candidate);
|
|
62
|
+
scratch = await input.materializeScratchCandidate(input.repository, input.candidate);
|
|
59
63
|
}
|
|
60
64
|
catch (error) {
|
|
61
65
|
return { outcome: { kind: "candidate-unavailable", diagnostic: error instanceof Error ? error.message : String(error) } };
|
|
@@ -66,7 +70,7 @@ export async function executeVerification(input) {
|
|
|
66
70
|
let destroy;
|
|
67
71
|
try {
|
|
68
72
|
try {
|
|
69
|
-
const hooks = worktreeHooksFrom({ settings: input.projectSettings(scratch.cwd) });
|
|
73
|
+
const hooks = worktreeHooksFrom({ settings: await input.projectSettings(scratch.cwd) });
|
|
70
74
|
destroy = hooks.destroy;
|
|
71
75
|
const readiness = await runHookCommands(scratch.cwd, hooks.create, input.signal);
|
|
72
76
|
outcome = readiness.kind === "cancelled"
|
|
@@ -92,7 +96,7 @@ export async function executeVerification(input) {
|
|
|
92
96
|
if (result.kind === "failed")
|
|
93
97
|
cleanup = { phase: "destroy", command: result.command, detail: result.failure };
|
|
94
98
|
}
|
|
95
|
-
leak = scratch.dispose();
|
|
99
|
+
leak = await scratch.dispose();
|
|
96
100
|
}
|
|
97
101
|
if (outcome === undefined)
|
|
98
102
|
throw new Error("Verification ended without an outcome");
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type ContractId } from "./core/facts/types.js";
|
|
2
|
+
import type { GitRepository } from "./git/repository.js";
|
|
3
|
+
export declare const CONTRACT_PLACES: readonly string[];
|
|
4
|
+
export type Place = string & {
|
|
5
|
+
readonly [placeBrand]: "Place";
|
|
6
|
+
};
|
|
7
|
+
declare const placeBrand: unique symbol;
|
|
8
|
+
export type PlaceAppointment = Readonly<{
|
|
9
|
+
place: Place;
|
|
10
|
+
contract: ContractId;
|
|
11
|
+
}>;
|
|
12
|
+
export type PlaceRegister = Readonly<{
|
|
13
|
+
appointments: readonly PlaceAppointment[];
|
|
14
|
+
byPlace: ReadonlyMap<Place, PlaceAppointment>;
|
|
15
|
+
byContract: ReadonlyMap<ContractId, PlaceAppointment>;
|
|
16
|
+
}>;
|
|
17
|
+
export type ManagedWorktreeAppointment = Readonly<{
|
|
18
|
+
kind: "appointed";
|
|
19
|
+
place: Place;
|
|
20
|
+
path: string;
|
|
21
|
+
}> | Readonly<{
|
|
22
|
+
kind: "unappointed";
|
|
23
|
+
}> | Readonly<{
|
|
24
|
+
kind: "failed";
|
|
25
|
+
diagnostic: string;
|
|
26
|
+
}>;
|
|
27
|
+
export declare function placeRegisterPath(repository: GitRepository): string;
|
|
28
|
+
export declare function place(value: string): Place;
|
|
29
|
+
export declare function nextPlace(current?: Place): Place;
|
|
30
|
+
export declare function firstFreePlace(register: PlaceRegister): Place;
|
|
31
|
+
export declare function canonicalPlaceRegister(register: PlaceRegister): string;
|
|
32
|
+
export declare function decodePlaceRegister(path: string, bytes: string): PlaceRegister;
|
|
33
|
+
export declare function emptyPlaceRegister(): PlaceRegister;
|
|
34
|
+
export declare function readPlaceRegister(repository: GitRepository): Promise<PlaceRegister>;
|
|
35
|
+
export declare function appointmentFor(register: PlaceRegister, contract: ContractId): PlaceAppointment | undefined;
|
|
36
|
+
export declare function appointManagedWorktrees(repository: GitRepository, contracts: readonly ContractId[]): Promise<PlaceRegister>;
|
|
37
|
+
export declare function appointManagedWorktree(repository: GitRepository, contract: ContractId): Promise<PlaceAppointment>;
|
|
38
|
+
export declare function releaseManagedWorktrees(repository: GitRepository, contracts: readonly ContractId[]): Promise<void>;
|
|
39
|
+
export declare function releaseManagedWorktree(repository: GitRepository, contract: ContractId): Promise<void>;
|
|
40
|
+
export declare function readManagedWorktreeAppointment(repository: GitRepository, contract: ContractId, register?: PlaceRegister): Promise<ManagedWorktreeAppointment>;
|
|
41
|
+
export {};
|