@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
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { TreeUpdate } from "../core/facts/offer.js";
|
|
2
2
|
import { type ContractId } from "../core/facts/types.js";
|
|
3
|
-
import
|
|
3
|
+
import type { GitDecisionObservation } from "../git/observe.js";
|
|
4
|
+
import { type GitRepository } from "../git/repository.js";
|
|
5
|
+
import { type GitDecodeChannel, type GitReadObservation, type GitTreeSelection } from "../git/read-observation.js";
|
|
4
6
|
import { type TaskId } from "../task/identity.js";
|
|
7
|
+
export declare function taskHolderObservationSelection(): GitTreeSelection;
|
|
5
8
|
export type TaskHolder = Readonly<{
|
|
6
9
|
version: 1;
|
|
7
10
|
taskId: TaskId;
|
|
@@ -10,6 +13,34 @@ export type TaskHolder = Readonly<{
|
|
|
10
13
|
}>;
|
|
11
14
|
export type TaskHolderProjection = ReadonlyMap<ContractId, TaskHolder>;
|
|
12
15
|
export declare function claimTaskHolder(taskId: TaskId, owner: ContractId): TreeUpdate;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
type AdmissionOutcome = Readonly<{
|
|
17
|
+
kind: string;
|
|
18
|
+
}>;
|
|
19
|
+
export type TaskHolderAdmission<T extends AdmissionOutcome> = Readonly<{
|
|
20
|
+
kind: "completed";
|
|
21
|
+
result: T;
|
|
22
|
+
}> | Readonly<{
|
|
23
|
+
kind: "accepted-release-failed";
|
|
24
|
+
result: T;
|
|
25
|
+
taskId: TaskId;
|
|
26
|
+
diagnostic: string;
|
|
27
|
+
}>;
|
|
28
|
+
export declare function finishTaskHolderAdmission<T extends AdmissionOutcome>(taskId: TaskId, result: T, close: () => void): TaskHolderAdmission<T>;
|
|
29
|
+
export declare function claimTaskHolderWithFence<T extends AdmissionOutcome>(repository: GitRepository, taskId: TaskId, action: () => T | Promise<T>): Promise<TaskHolderAdmission<T>>;
|
|
30
|
+
export declare function releaseTaskHolder(channel: GitDecodeChannel, observation: GitDecisionObservation, owner: ContractId): Promise<TreeUpdate | null>;
|
|
31
|
+
export type TaskHolderReleasePublication = Readonly<{
|
|
32
|
+
kind: "released";
|
|
33
|
+
}> | Readonly<{
|
|
34
|
+
kind: "not-held";
|
|
35
|
+
}> | Readonly<{
|
|
36
|
+
kind: "non-published";
|
|
37
|
+
diagnostic: string;
|
|
38
|
+
}>;
|
|
39
|
+
/** Publish the released holder against the same frozen observation that decided the release. */
|
|
40
|
+
export declare function publishTaskHolderRelease(repository: GitRepository, channel: GitDecodeChannel, observation: GitDecisionObservation, owner: ContractId): Promise<TaskHolderReleasePublication>;
|
|
41
|
+
export declare function readTaskHolderProjectionFromDecision(channel: GitDecodeChannel, observation: GitDecisionObservation): Promise<TaskHolderProjection>;
|
|
42
|
+
export declare function observeTaskHolderProjection(repository: GitRepository, channel: GitDecodeChannel): Promise<TaskHolderProjection>;
|
|
43
|
+
export declare function releaseTaskHolderWithFence<T extends AdmissionOutcome>(repository: GitRepository, channel: GitDecodeChannel, owner: ContractId, action: () => T | Promise<T>): Promise<TaskHolderAdmission<T>>;
|
|
44
|
+
export declare function readTaskHoldersAt(observation: Pick<GitReadObservation, "snapshot" | "readBlobs">): Promise<readonly TaskHolder[]>;
|
|
45
|
+
export declare function readTaskHolderProjectionAt(observation: GitReadObservation): Promise<TaskHolderProjection>;
|
|
46
|
+
export {};
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { AuthorityCorruptionError } from "../core/facts/errors.js";
|
|
3
3
|
import { contractId } from "../core/facts/types.js";
|
|
4
|
-
import {
|
|
4
|
+
import { GIT_REF, updateGitTree, updateRefsAtomically, writeBlob, writeCommit, } from "../git/repository.js";
|
|
5
|
+
import { withGitTargetedReadObservation, } from "../git/read-observation.js";
|
|
5
6
|
import { parseTaskId } from "../task/identity.js";
|
|
7
|
+
import { acquireTaskSettlementFence } from "./fence.js";
|
|
6
8
|
const HOLDER_ROOT = "settlement/task-holders";
|
|
7
9
|
const HOLDER_PREFIX = `${HOLDER_ROOT}/`;
|
|
8
10
|
const HOLDER_SUFFIX = ".json";
|
|
11
|
+
export function taskHolderObservationSelection() {
|
|
12
|
+
return { subtrees: [HOLDER_ROOT] };
|
|
13
|
+
}
|
|
9
14
|
function holderPath(taskId) {
|
|
10
15
|
const digest = createHash("sha256").update(taskId).digest("hex");
|
|
11
16
|
return `${HOLDER_PREFIX}${digest}${HOLDER_SUFFIX}`;
|
|
@@ -62,27 +67,6 @@ function decodeHolder(path, bytes) {
|
|
|
62
67
|
corruption(`TaskHolder bytes are not canonical: ${path}`);
|
|
63
68
|
return holder;
|
|
64
69
|
}
|
|
65
|
-
function holderEntries(repository, snapshot, scope) {
|
|
66
|
-
const expanded = scope === "complete" ? snapshot : expandGitSnapshot(repository, snapshot);
|
|
67
|
-
const holders = [];
|
|
68
|
-
const seen = new Set();
|
|
69
|
-
for (const [path, entry] of expanded.paths) {
|
|
70
|
-
if (path === HOLDER_ROOT)
|
|
71
|
-
corruption(`TaskHolder authority root is not a tree: ${path}`);
|
|
72
|
-
if (!path.startsWith(HOLDER_PREFIX))
|
|
73
|
-
continue;
|
|
74
|
-
if (!path.endsWith(HOLDER_SUFFIX))
|
|
75
|
-
corruption(`unexpected TaskHolder authority path: ${path}`);
|
|
76
|
-
if (entry.type !== "blob")
|
|
77
|
-
corruption(`TaskHolder path is not a blob: ${path}`);
|
|
78
|
-
const holder = decodeHolder(path, readBlob(repository, entry.oid));
|
|
79
|
-
if (seen.has(holder.taskId))
|
|
80
|
-
corruption(`duplicate TaskHolder identity: ${holder.taskId}`);
|
|
81
|
-
seen.add(holder.taskId);
|
|
82
|
-
holders.push(holder);
|
|
83
|
-
}
|
|
84
|
-
return holders.sort((left, right) => Buffer.compare(Buffer.from(left.taskId), Buffer.from(right.taskId)));
|
|
85
|
-
}
|
|
86
70
|
function projectTaskHolders(holders) {
|
|
87
71
|
const projection = new Map();
|
|
88
72
|
for (const holder of holders) {
|
|
@@ -99,15 +83,130 @@ function update(holder) {
|
|
|
99
83
|
export function claimTaskHolder(taskId, owner) {
|
|
100
84
|
return update({ version: 1, taskId, contractId: owner, disposition: "held" });
|
|
101
85
|
}
|
|
102
|
-
|
|
103
|
-
|
|
86
|
+
function detail(error) {
|
|
87
|
+
return error instanceof Error ? error.message : String(error);
|
|
88
|
+
}
|
|
89
|
+
export function finishTaskHolderAdmission(taskId, result, close) {
|
|
90
|
+
try {
|
|
91
|
+
close();
|
|
92
|
+
return { kind: "completed", result };
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (result.kind !== "accepted")
|
|
96
|
+
throw error;
|
|
97
|
+
return { kind: "accepted-release-failed", result, taskId, diagnostic: detail(error) };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function admitWithTaskHolderFence(repository, taskId, action) {
|
|
101
|
+
const held = await acquireTaskSettlementFence(repository, taskId);
|
|
102
|
+
let result;
|
|
103
|
+
try {
|
|
104
|
+
result = await action();
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
try {
|
|
108
|
+
held.close();
|
|
109
|
+
}
|
|
110
|
+
catch { /* The operation failure remains decisive before admission. */ }
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
return finishTaskHolderAdmission(taskId, result, () => held.close());
|
|
114
|
+
}
|
|
115
|
+
export function claimTaskHolderWithFence(repository, taskId, action) {
|
|
116
|
+
return admitWithTaskHolderFence(repository, taskId, action);
|
|
117
|
+
}
|
|
118
|
+
export async function releaseTaskHolder(channel, observation, owner) {
|
|
119
|
+
const current = (await readTaskHolderProjectionFromDecision(channel, observation)).get(owner) ?? null;
|
|
104
120
|
return current === null || current.disposition !== "held"
|
|
105
121
|
? null
|
|
106
122
|
: update({ ...current, disposition: "released" });
|
|
107
123
|
}
|
|
108
|
-
|
|
109
|
-
|
|
124
|
+
/** Publish the released holder against the same frozen observation that decided the release. */
|
|
125
|
+
export async function publishTaskHolderRelease(repository, channel, observation, owner) {
|
|
126
|
+
const release = await releaseTaskHolder(channel, observation, owner);
|
|
127
|
+
if (release === null)
|
|
128
|
+
return { kind: "not-held" };
|
|
129
|
+
const tree = await updateGitTree(repository, observation.admission.snapshot.tree, new Map([[release.path, { oid: await writeBlob(repository, release.bytes) }]]));
|
|
130
|
+
const commit = await writeCommit({
|
|
131
|
+
repository,
|
|
132
|
+
tree,
|
|
133
|
+
parent: observation.admission.snapshot.commit,
|
|
134
|
+
message: `release held Task completion: ${owner}`,
|
|
135
|
+
});
|
|
136
|
+
const publication = await updateRefsAtomically(repository, [{
|
|
137
|
+
ref: GIT_REF,
|
|
138
|
+
newOid: commit,
|
|
139
|
+
expectedOid: observation.admission.snapshot.commit,
|
|
140
|
+
}]);
|
|
141
|
+
if (publication.kind === "published")
|
|
142
|
+
return { kind: "released" };
|
|
143
|
+
return {
|
|
144
|
+
kind: "non-published",
|
|
145
|
+
diagnostic: publication.kind === "non-published" ? detail(publication.error) : "Task holder release publication state is unknown",
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
export async function readTaskHolderProjectionFromDecision(channel, observation) {
|
|
149
|
+
const read = {
|
|
150
|
+
snapshot: observation.admission.snapshot,
|
|
151
|
+
readBlobs: async (oids) => {
|
|
152
|
+
const objects = await channel.readObjects(oids);
|
|
153
|
+
const blobs = new Map();
|
|
154
|
+
for (const [oid, object] of objects) {
|
|
155
|
+
if (object.kind === "missing")
|
|
156
|
+
blobs.set(oid, object);
|
|
157
|
+
else {
|
|
158
|
+
if (object.type !== "blob")
|
|
159
|
+
corruption(`TaskHolder Git object is not a blob: ${oid}`);
|
|
160
|
+
blobs.set(oid, { kind: "present", bytes: object.bytes });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return blobs;
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
return projectTaskHolders(await readTaskHoldersAt(read));
|
|
167
|
+
}
|
|
168
|
+
export function observeTaskHolderProjection(repository, channel) {
|
|
169
|
+
return withGitTargetedReadObservation(repository, channel, taskHolderObservationSelection(), readTaskHolderProjectionAt);
|
|
170
|
+
}
|
|
171
|
+
async function heldTaskForContract(repository, channel, owner) {
|
|
172
|
+
const holder = (await observeTaskHolderProjection(repository, channel)).get(owner) ?? null;
|
|
173
|
+
return holder?.disposition === "held" ? holder.taskId : null;
|
|
174
|
+
}
|
|
175
|
+
export async function releaseTaskHolderWithFence(repository, channel, owner, action) {
|
|
176
|
+
const taskId = await heldTaskForContract(repository, channel, owner);
|
|
177
|
+
return taskId === null
|
|
178
|
+
? { kind: "completed", result: await action() }
|
|
179
|
+
: admitWithTaskHolderFence(repository, taskId, action);
|
|
180
|
+
}
|
|
181
|
+
export async function readTaskHoldersAt(observation) {
|
|
182
|
+
const entries = [...observation.snapshot.paths]
|
|
183
|
+
.filter(([path, entry]) => path.startsWith(HOLDER_PREFIX) && entry.type === "blob");
|
|
184
|
+
const blobs = await observation.readBlobs(entries.map(([, entry]) => entry.oid));
|
|
185
|
+
const holders = [];
|
|
186
|
+
const seen = new Set();
|
|
187
|
+
for (const [path, entry] of observation.snapshot.paths) {
|
|
188
|
+
if (path === HOLDER_ROOT) {
|
|
189
|
+
if (entry.type !== "tree")
|
|
190
|
+
corruption(`TaskHolder authority root is not a tree: ${path}`);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (!path.startsWith(HOLDER_PREFIX))
|
|
194
|
+
continue;
|
|
195
|
+
if (!path.endsWith(HOLDER_SUFFIX))
|
|
196
|
+
corruption(`unexpected TaskHolder authority path: ${path}`);
|
|
197
|
+
if (entry.type !== "blob")
|
|
198
|
+
corruption(`TaskHolder path is not a blob: ${path}`);
|
|
199
|
+
const result = blobs.get(entry.oid);
|
|
200
|
+
if (result?.kind !== "present")
|
|
201
|
+
corruption(`missing TaskHolder Git object: ${path}`);
|
|
202
|
+
const holder = decodeHolder(path, result.bytes);
|
|
203
|
+
if (seen.has(holder.taskId))
|
|
204
|
+
corruption(`duplicate TaskHolder identity: ${holder.taskId}`);
|
|
205
|
+
seen.add(holder.taskId);
|
|
206
|
+
holders.push(holder);
|
|
207
|
+
}
|
|
208
|
+
return holders.sort((left, right) => Buffer.compare(Buffer.from(left.taskId), Buffer.from(right.taskId)));
|
|
110
209
|
}
|
|
111
|
-
export function
|
|
112
|
-
return projectTaskHolders(
|
|
210
|
+
export async function readTaskHolderProjectionAt(observation) {
|
|
211
|
+
return projectTaskHolders(await readTaskHoldersAt(observation));
|
|
113
212
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { type ContractId, type ContractState } from "../core/facts/types.js";
|
|
2
|
+
import type { GitDecodeChannel } from "../git/read-observation.js";
|
|
2
3
|
import type { Effect } from "../git/reconcile.js";
|
|
3
4
|
import type { GitRepository } from "../git/repository.js";
|
|
4
5
|
import type { TaskId } from "../task/identity.js";
|
|
5
6
|
export type SettlementAction = Readonly<{
|
|
6
7
|
kind: "task";
|
|
7
8
|
taskId: TaskId;
|
|
8
|
-
action: "done"
|
|
9
|
+
action: "done";
|
|
9
10
|
}> | Readonly<{
|
|
10
11
|
kind: "namespace-context";
|
|
11
12
|
path: string;
|
|
@@ -23,13 +24,20 @@ export type SettlementReport = Readonly<{
|
|
|
23
24
|
actions: readonly SettlementAction[];
|
|
24
25
|
lags: readonly SettlementLag[];
|
|
25
26
|
}>;
|
|
27
|
+
export declare function deferredTaskHolderSettlement(input: Readonly<{
|
|
28
|
+
contractId: ContractId;
|
|
29
|
+
taskId: TaskId;
|
|
30
|
+
diagnostic: string;
|
|
31
|
+
}>): SettlementReport;
|
|
26
32
|
export type SettlementInput = Readonly<{
|
|
27
33
|
repository: GitRepository;
|
|
34
|
+
channel: GitDecodeChannel;
|
|
28
35
|
state: ContractState | null;
|
|
29
36
|
effects: readonly Effect[];
|
|
30
37
|
}>;
|
|
31
38
|
export type SettlementBatchInput = Readonly<{
|
|
32
39
|
repository: GitRepository;
|
|
40
|
+
channel: GitDecodeChannel;
|
|
33
41
|
contracts: readonly Readonly<Pick<SettlementInput, "state" | "effects">>[];
|
|
34
42
|
}>;
|
|
35
43
|
export declare function settle(input: SettlementInput): Promise<SettlementReport>;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { contractSegment } from "../core/facts/types.js";
|
|
2
|
+
import { observeContractsForAdmissionAt } from "../git/observe.js";
|
|
2
3
|
import { repairNamespaceContext } from "../task/context.js";
|
|
3
4
|
import { settleTask } from "../task/operations.js";
|
|
4
|
-
import {
|
|
5
|
+
import { publishTaskHolderRelease, readTaskHolderProjectionFromDecision, taskHolderObservationSelection, } from "./holder.js";
|
|
6
|
+
import { World } from "../world.js";
|
|
7
|
+
export function deferredTaskHolderSettlement(input) {
|
|
8
|
+
return { actions: [], lags: [{ kind: "settlement-failed", surface: "task-holder", ...input }] };
|
|
9
|
+
}
|
|
5
10
|
function diagnostic(error) {
|
|
6
11
|
return error instanceof Error ? error.message : String(error);
|
|
7
12
|
}
|
|
@@ -10,46 +15,71 @@ function taskFailure(result) {
|
|
|
10
15
|
? `Task settlement requires retry: ${result.reason}`
|
|
11
16
|
: `Task settlement refused: ${JSON.stringify(result.refusal)}`;
|
|
12
17
|
}
|
|
13
|
-
async function settleTasks(
|
|
14
|
-
const
|
|
15
|
-
|
|
18
|
+
async function settleTasks(input) {
|
|
19
|
+
const { repository, channel, world, candidate, actions, lags } = input;
|
|
20
|
+
let observation;
|
|
21
|
+
try {
|
|
22
|
+
observation = await observeContractsForAdmissionAt(repository, channel, [candidate.id], taskHolderObservationSelection());
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
lags.push({ kind: "settlement-failed", surface: "task-holder", contractId: candidate.id, diagnostic: diagnostic(error) });
|
|
16
26
|
return;
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
}
|
|
28
|
+
const state = observation.journals.get(candidate.id)?.state ?? null;
|
|
29
|
+
if (state === null || state.terminal?.kind !== "claimed")
|
|
19
30
|
return;
|
|
31
|
+
let holder;
|
|
32
|
+
try {
|
|
33
|
+
holder = (await readTaskHolderProjectionFromDecision(channel, observation)).get(candidate.id) ?? null;
|
|
20
34
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
35
|
+
catch (error) {
|
|
36
|
+
lags.push({ kind: "settlement-failed", surface: "task-holder", contractId: candidate.id, diagnostic: diagnostic(error) });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (holder === null || holder.disposition !== "held")
|
|
24
40
|
return;
|
|
25
41
|
const taskId = holder.taskId;
|
|
42
|
+
let result;
|
|
43
|
+
try {
|
|
44
|
+
result = await settleTask(world, taskId);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
lags.push({ kind: "settlement-failed", surface: "task", contractId: candidate.id, taskId, diagnostic: diagnostic(error) });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (result.kind === "changed")
|
|
51
|
+
actions.push({ kind: "task", taskId, action: "done" });
|
|
52
|
+
else if (result.kind !== "unchanged") {
|
|
53
|
+
lags.push({ kind: "settlement-failed", surface: "task", contractId: candidate.id, taskId, diagnostic: taskFailure(result) });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
26
56
|
try {
|
|
27
|
-
const
|
|
28
|
-
if (
|
|
29
|
-
actions.push({ kind: "task", taskId, action: result.action });
|
|
30
|
-
else if (result.kind !== "unchanged")
|
|
57
|
+
const publication = await publishTaskHolderRelease(repository, channel, observation, candidate.id);
|
|
58
|
+
if (publication.kind === "non-published") {
|
|
31
59
|
lags.push({
|
|
32
60
|
kind: "settlement-failed",
|
|
33
|
-
surface: "task",
|
|
34
|
-
contractId:
|
|
61
|
+
surface: "task-holder",
|
|
62
|
+
contractId: candidate.id,
|
|
35
63
|
taskId,
|
|
36
|
-
diagnostic:
|
|
64
|
+
diagnostic: `Task holder release requires retry: ${publication.diagnostic}`,
|
|
37
65
|
});
|
|
66
|
+
}
|
|
38
67
|
}
|
|
39
68
|
catch (error) {
|
|
40
|
-
lags.push({ kind: "settlement-failed", surface: "task", contractId:
|
|
69
|
+
lags.push({ kind: "settlement-failed", surface: "task-holder", contractId: candidate.id, taskId, diagnostic: diagnostic(error) });
|
|
41
70
|
}
|
|
42
71
|
}
|
|
43
|
-
function settleNamespace(state, effects, actions, lags) {
|
|
44
|
-
if (state.terminal !== null || state.coordinates.workspace !== "worktree"
|
|
72
|
+
async function settleNamespace(state, effects, actions, lags) {
|
|
73
|
+
if (state.terminal !== null || state.coordinates.workspace !== "worktree")
|
|
45
74
|
return;
|
|
46
75
|
const worktrees = effects.filter((effect) => effect.kind === "worktree" && effect.action !== "removed");
|
|
47
76
|
for (const effect of worktrees) {
|
|
48
77
|
try {
|
|
78
|
+
const world = await World.at(effect.path);
|
|
49
79
|
actions.push({
|
|
50
80
|
kind: "namespace-context",
|
|
51
81
|
path: effect.path,
|
|
52
|
-
action: repairNamespaceContext(
|
|
82
|
+
action: await repairNamespaceContext(world, [contractSegment(state.id)]),
|
|
53
83
|
});
|
|
54
84
|
}
|
|
55
85
|
catch (error) {
|
|
@@ -63,29 +93,36 @@ function settleNamespace(state, effects, actions, lags) {
|
|
|
63
93
|
}
|
|
64
94
|
}
|
|
65
95
|
}
|
|
66
|
-
function
|
|
67
|
-
try {
|
|
68
|
-
return { kind: "present", holders: readTaskHolderProjection(repository) };
|
|
69
|
-
}
|
|
70
|
-
catch (error) {
|
|
71
|
-
return { kind: "failed", diagnostic: diagnostic(error) };
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
async function settleObserved(input, observation) {
|
|
96
|
+
async function settleObserved(input) {
|
|
75
97
|
if (input.state === null)
|
|
76
98
|
return { actions: [], lags: [] };
|
|
77
99
|
const actions = [], lags = [];
|
|
78
|
-
|
|
79
|
-
|
|
100
|
+
if (input.state.terminal?.kind === "claimed") {
|
|
101
|
+
try {
|
|
102
|
+
await settleTasks({
|
|
103
|
+
repository: input.repository,
|
|
104
|
+
channel: input.channel,
|
|
105
|
+
world: await World.at(input.repository.primaryWorktree),
|
|
106
|
+
candidate: input.state,
|
|
107
|
+
actions,
|
|
108
|
+
lags,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
lags.push({ kind: "settlement-failed", surface: "task", contractId: input.state.id, diagnostic: diagnostic(error) });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
await settleNamespace(input.state, input.effects, actions, lags);
|
|
80
116
|
return { actions, lags };
|
|
81
117
|
}
|
|
118
|
+
function onPrimaryWorktree(repository) {
|
|
119
|
+
return { ...repository, effectiveCwd: repository.primaryWorktree };
|
|
120
|
+
}
|
|
82
121
|
export async function settle(input) {
|
|
83
|
-
|
|
122
|
+
const repository = onPrimaryWorktree(input.repository);
|
|
123
|
+
return await settleObserved({ ...input, repository });
|
|
84
124
|
}
|
|
85
125
|
export async function settleAll(input) {
|
|
86
|
-
const
|
|
87
|
-
return Promise.all(input.contracts.map((contract) => settleObserved({
|
|
88
|
-
repository: input.repository,
|
|
89
|
-
...contract,
|
|
90
|
-
}, observation)));
|
|
126
|
+
const repository = onPrimaryWorktree(input.repository);
|
|
127
|
+
return await Promise.all(input.contracts.map((contract) => settleObserved({ repository, channel: input.channel, ...contract })));
|
|
91
128
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { TaskDocument, TaskPriority, TaskState } from "./document.js";
|
|
2
2
|
import { type TaskId } from "./identity.js";
|
|
3
|
-
type TaskRef = Readonly<{
|
|
3
|
+
export type TaskRef = Readonly<{
|
|
4
4
|
id: TaskId;
|
|
5
5
|
title: string | null;
|
|
6
6
|
state: TaskState | "missing";
|
|
7
7
|
}>;
|
|
8
|
-
type TaskDisposition = "ready" | "blocked" | "in_progress" | "on_hold" | "done" | "drop";
|
|
8
|
+
export type TaskDisposition = "ready" | "blocked" | "in_progress" | "on_hold" | "done" | "drop";
|
|
9
9
|
export type TaskRow = Readonly<{
|
|
10
10
|
id: TaskId;
|
|
11
11
|
title: string;
|
|
@@ -34,8 +34,7 @@ export type TaskTreeNode = Readonly<{
|
|
|
34
34
|
priority: TaskPriority | null;
|
|
35
35
|
}>;
|
|
36
36
|
cycle?: true;
|
|
37
|
-
|
|
38
|
-
needs: readonly TaskTreeNode[];
|
|
37
|
+
children: readonly TaskTreeNode[];
|
|
39
38
|
}>;
|
|
40
39
|
export type TaskBoard = Readonly<{
|
|
41
40
|
tasks: ReadonlyMap<TaskId, TaskDocument>;
|
|
@@ -55,11 +54,30 @@ export type TaskDoctorIssue = Readonly<{
|
|
|
55
54
|
relation: "needs" | "parent" | "supersedes";
|
|
56
55
|
tasks: readonly TaskId[];
|
|
57
56
|
}>;
|
|
57
|
+
export declare function taskDisposition(board: TaskBoard, task: TaskDocument): TaskDisposition;
|
|
58
58
|
export declare function projectDetailFacts(board: TaskBoard, id: TaskId): TaskDetailFacts | null;
|
|
59
59
|
export declare function projectRows(board: TaskBoard, scope: readonly string[] | null, selection: "active" | "closed" | "all"): readonly TaskRow[];
|
|
60
60
|
export declare function projectReady(board: TaskBoard, scope: readonly string[] | null): readonly TaskRow[];
|
|
61
61
|
export declare function projectBlocked(board: TaskBoard, scope: readonly string[] | null): readonly BlockedTaskRow[];
|
|
62
|
+
export declare function projectStatusRows(board: TaskBoard, scope: readonly string[] | null): (Readonly<{
|
|
63
|
+
id: TaskId;
|
|
64
|
+
title: string;
|
|
65
|
+
state: TaskState;
|
|
66
|
+
priority: TaskPriority;
|
|
67
|
+
disposition: TaskDisposition;
|
|
68
|
+
}> | {
|
|
69
|
+
blockers: readonly Readonly<{
|
|
70
|
+
id: TaskId;
|
|
71
|
+
title: string | null;
|
|
72
|
+
state: TaskState | "missing";
|
|
73
|
+
}>[];
|
|
74
|
+
id: TaskId;
|
|
75
|
+
title: string;
|
|
76
|
+
state: TaskState;
|
|
77
|
+
priority: TaskPriority;
|
|
78
|
+
disposition: TaskDisposition;
|
|
79
|
+
})[];
|
|
62
80
|
export declare function relationProblem(board: TaskBoard, before: TaskDocument | null, after: TaskDocument): string | null;
|
|
63
81
|
export declare function diagnoseBoard(board: TaskBoard): readonly TaskDoctorIssue[];
|
|
64
|
-
export declare function buildTree(board: TaskBoard, root: TaskId
|
|
82
|
+
export declare function buildTree(board: TaskBoard, root: TaskId): TaskTreeNode | null;
|
|
65
83
|
export {};
|
package/build/src/task/board.js
CHANGED
|
@@ -19,7 +19,7 @@ function related(board, task) {
|
|
|
19
19
|
ids.add(candidate.id);
|
|
20
20
|
return sorted([...ids].map((id) => taskRef(board, id)));
|
|
21
21
|
}
|
|
22
|
-
function
|
|
22
|
+
export function taskDisposition(board, task) {
|
|
23
23
|
if (task.state !== "open")
|
|
24
24
|
return task.state;
|
|
25
25
|
return task.needs.every((id) => { const target = board.tasks.get(id); return target !== undefined && terminal(target.state); }) ? "ready" : "blocked";
|
|
@@ -30,7 +30,10 @@ export function projectDetailFacts(board, id) {
|
|
|
30
30
|
return null;
|
|
31
31
|
const needs = task.needs.map((need) => ({ ...taskRef(board, need), released: board.tasks.has(need) && terminal(board.tasks.get(need).state) }));
|
|
32
32
|
return {
|
|
33
|
-
task,
|
|
33
|
+
task,
|
|
34
|
+
needs,
|
|
35
|
+
blockers: needs.filter((need) => !need.released).map(({ released: _released, ...ref }) => ref),
|
|
36
|
+
blocks: reverse(board, id, "needs"),
|
|
34
37
|
parent: task.parent === null ? null : taskRef(board, task.parent), children: children(board, id),
|
|
35
38
|
supersedes: task.supersedes.map((target) => taskRef(board, target)), supersededBy: reverse(board, id, "supersedes"),
|
|
36
39
|
related: related(board, task),
|
|
@@ -41,7 +44,7 @@ export function projectRows(board, scope, selection) {
|
|
|
41
44
|
return [...board.tasks.values()].filter((task) => inScope(task, scope)).filter((task) => selection === "all"
|
|
42
45
|
|| (selection === "closed") === terminal(task.state)).map((task) => ({
|
|
43
46
|
id: task.id, title: task.title, state: task.state, priority: task.priority,
|
|
44
|
-
disposition:
|
|
47
|
+
disposition: taskDisposition(board, task),
|
|
45
48
|
})).sort((a, b) => a.priority - b.priority || Buffer.compare(Buffer.from(a.id), Buffer.from(b.id)));
|
|
46
49
|
}
|
|
47
50
|
export function projectReady(board, scope) {
|
|
@@ -55,6 +58,13 @@ export function projectBlocked(board, scope) {
|
|
|
55
58
|
return blockers.length === 0 ? [] : [{ ...row, blockers }];
|
|
56
59
|
});
|
|
57
60
|
}
|
|
61
|
+
export function projectStatusRows(board, scope) {
|
|
62
|
+
const blockers = new Map(projectBlocked(board, scope).map((row) => [row.id, row.blockers]));
|
|
63
|
+
return projectRows(board, scope, "all").map((row) => {
|
|
64
|
+
const unresolved = blockers.get(row.id);
|
|
65
|
+
return unresolved === undefined ? row : { ...row, blockers: unresolved };
|
|
66
|
+
});
|
|
67
|
+
}
|
|
58
68
|
function relationIds(task, relation) {
|
|
59
69
|
return relation === "parent" ? task.parent === null ? [] : [task.parent] : task[relation];
|
|
60
70
|
}
|
|
@@ -137,19 +147,16 @@ export function diagnoseBoard(board) {
|
|
|
137
147
|
}
|
|
138
148
|
return issues;
|
|
139
149
|
}
|
|
140
|
-
export function buildTree(board, root
|
|
150
|
+
export function buildTree(board, root) {
|
|
141
151
|
if (!board.tasks.has(root))
|
|
142
152
|
return null;
|
|
143
|
-
const expanded = new Set();
|
|
144
153
|
const walk = (id, path) => {
|
|
145
154
|
const ref = taskRef(board, id);
|
|
146
155
|
const task = board.tasks.get(id);
|
|
156
|
+
const node = { task: { ...ref, priority: task?.priority ?? null } };
|
|
147
157
|
if (path.includes(id))
|
|
148
|
-
return {
|
|
149
|
-
|
|
150
|
-
return { task: { ...ref, priority: task?.priority ?? null }, reference: true, needs: [] };
|
|
151
|
-
expanded.add(id);
|
|
152
|
-
return { task: { ...ref, priority: task?.priority ?? null }, needs: task === undefined ? [] : task.needs.map((need) => walk(need, [...path, id])) };
|
|
158
|
+
return { ...node, cycle: true, children: [] };
|
|
159
|
+
return { ...node, children: children(board, id).map((child) => walk(child.id, [...path, id])) };
|
|
153
160
|
};
|
|
154
161
|
return walk(root, []);
|
|
155
162
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type TaskId } from "./identity.js";
|
|
2
|
-
import {
|
|
2
|
+
import type { WorldRoot } from "../world.js";
|
|
3
3
|
import type { TaskRefusal, TaskRetry } from "./operations.js";
|
|
4
4
|
type TaskDocumentChange = Readonly<{
|
|
5
5
|
taskId: TaskId;
|
|
@@ -21,5 +21,5 @@ export type TaskCompositionResult = Readonly<{
|
|
|
21
21
|
}>;
|
|
22
22
|
draft: string;
|
|
23
23
|
}>;
|
|
24
|
-
export declare function composeTasks(world:
|
|
24
|
+
export declare function composeTasks(world: WorldRoot, markdown: string, signal?: AbortSignal, actor?: string): Promise<TaskCompositionResult>;
|
|
25
25
|
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
1
2
|
import { documentDiff } from "../markdown/diff.js";
|
|
2
3
|
import { readNamespaceContext } from "./context.js";
|
|
3
4
|
import { relationProblem } from "./board.js";
|
|
@@ -119,7 +120,14 @@ function applyAssignments(document, assignments, body) {
|
|
|
119
120
|
}
|
|
120
121
|
function currentTimestamp() { return new Date().toISOString(); }
|
|
121
122
|
function advancedTimestamp(previous, current) { return current > previous ? current : new Date(Date.parse(previous) + 1).toISOString(); }
|
|
122
|
-
function
|
|
123
|
+
function createdTask(id, title, at, actor) {
|
|
124
|
+
return {
|
|
125
|
+
id, title, state: "open", priority: 2, needs: [], parent: null, supersedes: [], relates: [], note: "",
|
|
126
|
+
...(actor === undefined ? {} : { createdBy: actor }),
|
|
127
|
+
createdAt: at, updatedAt: at, body: "",
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function plan(sketch, board, defaultNamespace, at, actor) {
|
|
123
131
|
try {
|
|
124
132
|
const namespace = sketch.namespace ?? defaultNamespace;
|
|
125
133
|
const occupied = new Set([...board.tasks.values()].flatMap((task) => {
|
|
@@ -132,7 +140,7 @@ function plan(sketch, board, defaultNamespace, at) {
|
|
|
132
140
|
const localId = allocateLocalId(deriveLocalStem(node.title), occupied);
|
|
133
141
|
occupied.add(localId);
|
|
134
142
|
const coordinate = { namespace, localId };
|
|
135
|
-
allocations.set(node.index,
|
|
143
|
+
allocations.set(node.index, createdTask(formatTaskId(coordinate), node.title, at, actor));
|
|
136
144
|
}
|
|
137
145
|
const all = new Map(board.tasks);
|
|
138
146
|
for (const allocated of allocations.values())
|
|
@@ -190,24 +198,24 @@ function draft(namespace, remaining) {
|
|
|
190
198
|
}
|
|
191
199
|
return `${lines.join("\n")}\n`;
|
|
192
200
|
}
|
|
193
|
-
export async function composeTasks(world, markdown, signal) {
|
|
201
|
+
export async function composeTasks(world, markdown, signal, actor) {
|
|
194
202
|
const sketch = parseSketch(markdown);
|
|
195
203
|
if ("kind" in sketch)
|
|
196
204
|
return { kind: "refused", refusal: sketch };
|
|
197
205
|
const at = currentTimestamp();
|
|
198
|
-
const context = sketch.namespace ?? readNamespaceContext(world
|
|
206
|
+
const context = sketch.namespace ?? await readNamespaceContext(world);
|
|
199
207
|
if (context === "malformed")
|
|
200
|
-
return { kind: "refused", refusal: { kind: "invalid-namespace-context", path:
|
|
208
|
+
return { kind: "refused", refusal: { kind: "invalid-namespace-context", path: resolve(world, ".keiyaku", "namespace", "current") } };
|
|
201
209
|
const namespace = context === "absent" ? [] : context;
|
|
202
|
-
const initial = readBoard(world);
|
|
203
|
-
const planned = plan(sketch, initial.board, namespace, at);
|
|
210
|
+
const initial = await readBoard(world);
|
|
211
|
+
const planned = plan(sketch, initial.board, namespace, at, actor);
|
|
204
212
|
if ("kind" in planned)
|
|
205
213
|
return { kind: "refused", refusal: planned };
|
|
206
214
|
const ordered = [...planned].sort((a, b) => Buffer.compare(Buffer.from(a.after.id), Buffer.from(b.after.id)));
|
|
207
215
|
const allocation = sketch.nodes.some((node) => node.kind === "new");
|
|
208
216
|
const result = await withTaskLocks({ world, allocation, ids: ordered.map((item) => item.after.id), ...(signal === undefined ? {} : { signal }) }, async () => {
|
|
209
|
-
const fresh = readBoard(world);
|
|
210
|
-
const replanned = plan(sketch, fresh.board, namespace, at);
|
|
217
|
+
const fresh = await readBoard(world);
|
|
218
|
+
const replanned = plan(sketch, fresh.board, namespace, at, actor);
|
|
211
219
|
if ("kind" in replanned)
|
|
212
220
|
return { kind: "refused", refusal: replanned };
|
|
213
221
|
const queue = [...replanned].sort((a, b) => Buffer.compare(Buffer.from(a.after.id), Buffer.from(b.after.id))), changes = [];
|
|
@@ -219,7 +227,7 @@ export async function composeTasks(world, markdown, signal) {
|
|
|
219
227
|
const before = beforeBytes === null ? "" : Buffer.from(beforeBytes).toString("utf8"), after = Buffer.from(afterBytes).toString("utf8");
|
|
220
228
|
if (before === after)
|
|
221
229
|
continue;
|
|
222
|
-
if (replaceAuthority({ path, expected: beforeBytes, next: afterBytes }) !== "replaced")
|
|
230
|
+
if (await replaceAuthority({ path, expected: beforeBytes, next: afterBytes }) !== "replaced")
|
|
223
231
|
return {
|
|
224
232
|
kind: "incomplete", documentChanges: changes, stopped: { kind: "retry", reason: "concurrent-modification" }, draft: draft(namespace, queue.slice(index)),
|
|
225
233
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export type NamespaceContextRead = readonly string[] | "absent" | "malformed";
|
|
2
|
-
export declare function readNamespaceContext(root: string): NamespaceContextRead
|
|
3
|
-
export declare function installNamespaceContext(root: string, segments: readonly string[]): void
|
|
4
|
-
export declare function repairNamespaceContext(root: string, segments: readonly string[]): "kept" | "installed"
|
|
2
|
+
export declare function readNamespaceContext(root: string): Promise<NamespaceContextRead>;
|
|
3
|
+
export declare function installNamespaceContext(root: string, segments: readonly string[]): Promise<void>;
|
|
4
|
+
export declare function repairNamespaceContext(root: string, segments: readonly string[]): Promise<"kept" | "installed">;
|