@astrosheep/keiyaku 4.0.2 → 4.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +1 -1
  2. package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
  3. package/build/integrations/marketplace/plugins/keiyaku/package.json +1 -1
  4. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +1 -1
  5. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +1 -1
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +5 -0
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +3 -1
  8. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +9 -8
  9. package/build/src/akuma/akuma.d.ts +34 -11
  10. package/build/src/akuma/akuma.js +121 -61
  11. package/build/src/akuma/allowed.d.ts +7 -0
  12. package/build/src/akuma/allowed.js +40 -0
  13. package/build/src/akuma/archetype.d.ts +7 -4
  14. package/build/src/akuma/archetype.js +15 -16
  15. package/build/src/akuma/body.d.ts +3 -1
  16. package/build/src/akuma/body.js +19 -8
  17. package/build/src/akuma/heart/facts.d.ts +49 -7
  18. package/build/src/akuma/heart/index.d.ts +4 -8
  19. package/build/src/akuma/heart/index.js +76 -15
  20. package/build/src/akuma/heart/rows.d.ts +7 -5
  21. package/build/src/akuma/heart/rows.js +98 -11
  22. package/build/src/akuma/heart/schema.d.ts +1 -1
  23. package/build/src/akuma/heart/schema.js +15 -10
  24. package/build/src/akuma/heart/soul.js +3 -1
  25. package/build/src/akuma/heart/storage.d.ts +2 -0
  26. package/build/src/akuma/heart/storage.js +20 -0
  27. package/build/src/akuma/heart/timeline.d.ts +1 -5
  28. package/build/src/akuma/heart/timeline.js +8 -17
  29. package/build/src/akuma/index.d.ts +4 -1
  30. package/build/src/akuma/index.js +1 -0
  31. package/build/src/akuma/projection.d.ts +17 -3
  32. package/build/src/akuma/projection.js +40 -3
  33. package/build/src/akuma/provider.d.ts +1 -1
  34. package/build/src/akuma/provider.js +2 -1
  35. package/build/src/akuma/providers/acp/core.d.ts +3 -1
  36. package/build/src/akuma/providers/acp/core.js +4 -4
  37. package/build/src/akuma/providers/acp/events.d.ts +11 -3
  38. package/build/src/akuma/providers/acp/events.js +78 -4
  39. package/build/src/akuma/providers/claude/events.js +62 -3
  40. package/build/src/akuma/providers/codex-app-server/events.js +1 -13
  41. package/build/src/akuma/providers/grok-build/index.d.ts +2 -1
  42. package/build/src/akuma/providers/grok-build/index.js +72 -2
  43. package/build/src/akuma/providers/opencode-sdk/events.js +71 -6
  44. package/build/src/akuma/providers/pi/events.js +17 -3
  45. package/build/src/akuma/providers/unified-patch.d.ts +4 -0
  46. package/build/src/akuma/providers/unified-patch.js +14 -0
  47. package/build/src/akuma/publication.d.ts +1 -1
  48. package/build/src/akuma/publication.js +36 -7
  49. package/build/src/akuma/requests.d.ts +70 -4
  50. package/build/src/akuma/requests.js +288 -77
  51. package/build/src/akuma-body.d.ts +1 -0
  52. package/build/src/akuma-body.js +31 -0
  53. package/build/src/cli/accepted.d.ts +4 -1
  54. package/build/src/cli/accepted.js +26 -4
  55. package/build/src/cli/commands/akuma-invoke.d.ts +8 -7
  56. package/build/src/cli/commands/akuma-invoke.js +18 -10
  57. package/build/src/cli/commands/akuma.d.ts +11 -2
  58. package/build/src/cli/commands/akuma.js +67 -28
  59. package/build/src/cli/commands/amend.d.ts +1 -1
  60. package/build/src/cli/commands/amend.js +1 -1
  61. package/build/src/cli/commands/contract.d.ts +3 -3
  62. package/build/src/cli/commands/contract.js +1 -1
  63. package/build/src/cli/commands/install.js +4 -4
  64. package/build/src/cli/commands/task-query.d.ts +1 -1
  65. package/build/src/cli/commands/task-query.js +11 -5
  66. package/build/src/cli/commands/task.js +4 -4
  67. package/build/src/cli/coordinates.d.ts +1 -0
  68. package/build/src/cli/coordinates.js +5 -1
  69. package/build/src/cli/invoke.d.ts +1 -0
  70. package/build/src/cli/invoke.js +90 -73
  71. package/build/src/cli/main.d.ts +2 -0
  72. package/build/src/cli/main.js +29 -4
  73. package/build/src/cli/parse.d.ts +1 -0
  74. package/build/src/cli/parse.js +8 -4
  75. package/build/src/cli/render/akuma.d.ts +2 -2
  76. package/build/src/cli/render/akuma.js +84 -13
  77. package/build/src/cli/render/audit.js +16 -2
  78. package/build/src/cli/render/contract.d.ts +2 -0
  79. package/build/src/cli/render/contract.js +203 -36
  80. package/build/src/cli/render/kanshi.js +64 -29
  81. package/build/src/cli/render/receipt.d.ts +2 -1
  82. package/build/src/cli/render/receipt.js +9 -1
  83. package/build/src/cli/render/text.js +3 -1
  84. package/build/src/cli/result.d.ts +38 -3
  85. package/build/src/cli/selectors.d.ts +1 -0
  86. package/build/src/cli/selectors.js +9 -6
  87. package/build/src/cli/usage.d.ts +2 -0
  88. package/build/src/cli/usage.js +4 -0
  89. package/build/src/contract-worktree.js +3 -0
  90. package/build/src/dispatch/index.js +50 -39
  91. package/build/src/git/hooks.d.ts +0 -4
  92. package/build/src/git/hooks.js +0 -36
  93. package/build/src/git/integration.d.ts +2 -6
  94. package/build/src/git/integration.js +8 -8
  95. package/build/src/git/read-observation.js +1 -0
  96. package/build/src/git/reconcile.d.ts +6 -1
  97. package/build/src/git/reconcile.js +55 -9
  98. package/build/src/git/tender.d.ts +19 -4
  99. package/build/src/git/tender.js +44 -13
  100. package/build/src/git/terminal-seal.d.ts +14 -0
  101. package/build/src/git/terminal-seal.js +15 -2
  102. package/build/src/git/workspace.d.ts +1 -0
  103. package/build/src/git/workspace.js +12 -4
  104. package/build/src/index.d.ts +1 -1
  105. package/build/src/kanshi/index.d.ts +1 -1
  106. package/build/src/kanshi/index.js +1 -1
  107. package/build/src/kanshi/read.d.ts +9 -1
  108. package/build/src/kanshi/read.js +91 -37
  109. package/build/src/kanshi/report.d.ts +1 -0
  110. package/build/src/library/address.d.ts +5 -14
  111. package/build/src/library/address.js +20 -22
  112. package/build/src/library/akuma-creation.d.ts +4 -2
  113. package/build/src/library/akuma-creation.js +43 -26
  114. package/build/src/library/bind.js +3 -3
  115. package/build/src/library/catalog.d.ts +1 -2
  116. package/build/src/library/catalog.js +11 -6
  117. package/build/src/library/configuration.d.ts +4 -0
  118. package/build/src/library/configuration.js +19 -0
  119. package/build/src/library/contract.d.ts +15 -1
  120. package/build/src/library/contract.js +51 -4
  121. package/build/src/library/fleet.d.ts +47 -9
  122. package/build/src/library/fleet.js +184 -44
  123. package/build/src/library/input.js +1 -0
  124. package/build/src/library/keiyaku.d.ts +4 -4
  125. package/build/src/library/keiyaku.js +1 -2
  126. package/build/src/protocol/bind.d.ts +1 -2
  127. package/build/src/protocol/bind.js +2 -16
  128. package/build/src/protocol/operations.d.ts +4 -1
  129. package/build/src/protocol/operations.js +33 -27
  130. package/build/src/protocol/read/status.d.ts +3 -1
  131. package/build/src/protocol/read/status.js +24 -14
  132. package/build/src/runtime/proc/run.d.ts +1 -0
  133. package/build/src/runtime/proc/run.js +9 -2
  134. package/build/src/settlement/settle.d.ts +1 -0
  135. package/build/src/settlement/settle.js +4 -1
  136. package/build/src/task/board.d.ts +18 -3
  137. package/build/src/task/board.js +73 -26
  138. package/build/src/task/index.d.ts +3 -3
  139. package/build/src/task/index.js +7 -5
  140. package/build/src/task/operations.d.ts +2 -19
  141. package/build/src/task/operations.js +29 -7
  142. package/build/src/task/query.d.ts +9 -2
  143. package/build/src/task/query.js +32 -64
  144. package/build/src/verification/execution.js +2 -1
  145. package/package.json +3 -1
@@ -1,15 +1,8 @@
1
1
  import { normalizeTargetBranch, observeBindCoordinates } from "../git/observe.js";
2
2
  import { gitObjectIdForSnapshot } from "../git/identity.js";
3
- import { contractIdFromSegment } from "../core/facts/types.js";
4
3
  import { decideBind } from "../core/verbs/bind.js";
5
4
  import { admitIntent } from "./intent.js";
6
5
  import { complete } from "./outcome.js";
7
- import { fitIdentityStem, normalizeIdentityStem } from "../identity/normalize.js";
8
- const COLLISION_ATTEMPTS = 3;
9
- function contractIdFor(title, suffix) {
10
- const stem = fitIdentityStem({ stem: normalizeIdentityStem({ source: title }) || "contract", maxBytes: 48 });
11
- return contractIdFromSegment(suffix === undefined ? stem : `${stem}-${suffix}`);
12
- }
13
6
  async function bindPreparation(input, target, seed) {
14
7
  if (input.verification.kind === "refused") {
15
8
  return { kind: "prepared", input: { ...seed, preparation: input.verification } };
@@ -50,7 +43,8 @@ export async function bindOperation(input) {
50
43
  target = normalized;
51
44
  }
52
45
  const at = new Date().toISOString();
53
- const attempt = async (id) => complete(await admitIntent(input.channel, input.scope, {
46
+ const id = input.contractId;
47
+ return complete(await admitIntent(input.channel, input.scope, {
54
48
  contractId: id,
55
49
  ...(input.actor === undefined ? {} : { actor: input.actor }),
56
50
  at,
@@ -59,12 +53,4 @@ export async function bindOperation(input) {
59
53
  prepareInput: async (_observation, original) => bindPreparation(input, target, original),
60
54
  ...(input.decorateOffer === undefined ? {} : { decorateOffer: input.decorateOffer }),
61
55
  }), { contractId: id });
62
- if (input.contractId !== undefined)
63
- return attempt(input.contractId);
64
- let result = await attempt(contractIdFor(input.title));
65
- for (let collision = 0; collision < COLLISION_ATTEMPTS && result.kind === "refused" && result.refusal.kind === "contract-exists"; collision += 1) {
66
- result = await attempt(contractIdFor(input.title, randomBytes(8).toString("hex")));
67
- }
68
- return result;
69
56
  }
70
- import { randomBytes } from "node:crypto";
@@ -90,6 +90,7 @@ export type AuditReport = Readonly<{
90
90
  export type VerificationReuse = CurrentVerifiedAttestation;
91
91
  export type DocumentDerivation = Readonly<{
92
92
  document: DocumentKey;
93
+ bytes: string;
93
94
  title: string;
94
95
  verification: VerificationDeclarationPreparation;
95
96
  }>;
@@ -155,7 +156,7 @@ export type DeliverValue = DeliveryIdentity & Readonly<{
155
156
  leak?: WorktreeLeak;
156
157
  }>;
157
158
  type DeliverOperationInput = MutationOperationInput & Readonly<{
158
- deriveDocument?: (state: ContractState) => DocumentDerivation;
159
+ deriveDocument: (state: ContractState) => DocumentDerivation;
159
160
  message?: string;
160
161
  requireBranchesToBeUpToDate: boolean;
161
162
  includeDirty: boolean;
@@ -169,6 +170,8 @@ export declare function prepareDelivery(repository: GitRepository, stage: Readon
169
170
  }>;
170
171
  }>, input: Readonly<{
171
172
  title: string;
173
+ document: string;
174
+ actor?: ActorId;
172
175
  message?: string;
173
176
  requireBranchesToBeUpToDate?: boolean;
174
177
  includeDirty?: boolean;
@@ -1,5 +1,5 @@
1
1
  import { materializeIntegrationSnapshot, planIntegration, readDeliveryDiff, readDeliveryScope, worktreeChangeId, } from "../git/integration.js";
2
- import { captureTender, dirtyTenderDelta, dirtyTenderRefusal, materializeTenderSnapshot, } from "../git/tender.js";
2
+ import { captureTender, dirtyTenderDelta, dirtyTenderRefusal, materializeTenderSnapshot, prepareDeliveryCommitMetadata, } from "../git/tender.js";
3
3
  import { currentBranch, extendContractsForAdmissionAt, observeContractAt, observeContractWorld, observeContractsForAdmissionAt, } from "../git/observe.js";
4
4
  import { reconcile, reconcileBatch, reconcileObservationFailure, } from "../git/reconcile.js";
5
5
  import { NoGitWorldError, repositoryAt } from "../git/repository.js";
@@ -188,22 +188,22 @@ export async function prepareDelivery(repository, stage, input) {
188
188
  if ((tender.data.dirty && input.includeDirty !== true) || tender.data.changes.submodules.length > 0) {
189
189
  return { kind: "refused", refusal: await dirtyTenderRefusal(repository, contractId, tender.data) };
190
190
  }
191
- const tenderSnapshot = await materializeTenderSnapshot(repository, tender.data, {
191
+ const commit = await prepareDeliveryCommitMetadata(repository, {
192
192
  contractId,
193
193
  title: input.title,
194
+ document: input.document,
195
+ at: tender.data.at,
196
+ ...(input.actor === undefined ? {} : { actor: input.actor }),
194
197
  ...(input.message === undefined ? {} : { message: input.message }),
195
198
  });
199
+ const tenderSnapshot = await materializeTenderSnapshot(repository, tender.data, commit);
196
200
  const requireBranchesToBeUpToDate = input.requireBranchesToBeUpToDate ?? false;
197
201
  const integration = await planIntegration(repository, { contractId, coordinates }, { ...tender.data, head: tenderSnapshot }, requireBranchesToBeUpToDate);
198
202
  if (integration.kind === "refused")
199
203
  return integration;
200
204
  const integrationSnapshot = coordinates.target === undefined
201
205
  ? tenderSnapshot
202
- : await materializeIntegrationSnapshot(repository, integration.data.tree, integration.data.predecessor, {
203
- contractId,
204
- title: input.title,
205
- ...(input.message === undefined ? {} : { message: input.message }),
206
- });
206
+ : await materializeIntegrationSnapshot(repository, integration.data.tree, integration.data.predecessor, commit);
207
207
  return {
208
208
  kind: "prepared",
209
209
  data: {
@@ -276,6 +276,8 @@ async function deliverAttempt(input, attempt) {
276
276
  coordinates: state.coordinates,
277
277
  }, {
278
278
  title: derivation.title,
279
+ document: derivation.bytes,
280
+ ...(input.actor === undefined ? {} : { actor: input.actor }),
279
281
  ...(input.message === undefined ? {} : { message: input.message }),
280
282
  requireBranchesToBeUpToDate: input.requireBranchesToBeUpToDate,
281
283
  includeDirty: input.includeDirty,
@@ -589,6 +591,25 @@ async function auditTargetAnswer(repository, state, candidate) {
589
591
  candidate: candidate.integration.snapshot,
590
592
  });
591
593
  }
594
+ function blockedAudit(refusal) {
595
+ return {
596
+ candidate: { kind: "blocked", refusal },
597
+ verification: { kind: "not-run" },
598
+ target: { kind: "not-observed" },
599
+ };
600
+ }
601
+ function completedAudit(state, verified, value) {
602
+ const obligations = {
603
+ ...(verified?.cleanup === undefined ? {} : { cleanup: verified.cleanup }),
604
+ ...(verified?.leak === undefined ? {} : { leak: verified.leak }),
605
+ };
606
+ return verified?.admission === undefined
607
+ ? accepted(state, [], value, undefined, obligations)
608
+ : {
609
+ ...admitted(verified.admission, value),
610
+ ...obligations,
611
+ };
612
+ }
592
613
  export async function auditOperation(input) {
593
614
  const observed = await observeContractsForAdmissionAt(input.scope, input.channel, [input.contractId]);
594
615
  const state = activeContract(observed.decision, input.contractId);
@@ -603,11 +624,7 @@ export async function auditOperation(input) {
603
624
  }
604
625
  const workspace = await auditWorkspace(input.scope, state);
605
626
  if (workspace.kind === "unappointed") {
606
- return accepted(state, [], {
607
- candidate: { kind: "blocked", refusal: { kind: "worktree-missing", contractId: state.id } },
608
- verification: { kind: "not-run" },
609
- target: { kind: "not-observed" },
610
- });
627
+ return accepted(state, [], blockedAudit({ kind: "worktree-missing", contractId: state.id }));
611
628
  }
612
629
  const prepared = await prepareDelivery(input.scope, {
613
630
  contractId: state.id,
@@ -615,15 +632,13 @@ export async function auditOperation(input) {
615
632
  ...(workspace.appointment === undefined ? {} : { appointment: workspace.appointment }),
616
633
  }, {
617
634
  title: derivation.title,
635
+ document: derivation.bytes,
636
+ ...(input.actor === undefined ? {} : { actor: input.actor }),
618
637
  requireBranchesToBeUpToDate: input.requireBranchesToBeUpToDate ?? false,
619
638
  includeDirty: input.includeDirty ?? false,
620
639
  });
621
640
  if (prepared.kind === "refused") {
622
- return accepted(state, [], {
623
- candidate: { kind: "blocked", refusal: prepared.refusal },
624
- verification: { kind: "not-run" },
625
- target: { kind: "not-observed" },
626
- });
641
+ return accepted(state, [], blockedAudit(prepared.refusal));
627
642
  }
628
643
  const verified = derivation.verification.data === null
629
644
  ? undefined
@@ -638,16 +653,7 @@ export async function auditOperation(input) {
638
653
  : await auditTargetAnswer(input.scope, state, prepared.data),
639
654
  ...(delivery === undefined ? {} : { delivery }),
640
655
  };
641
- const obligations = {
642
- ...(verified?.cleanup === undefined ? {} : { cleanup: verified.cleanup }),
643
- ...(verified?.leak === undefined ? {} : { leak: verified.leak }),
644
- };
645
- return verified?.admission === undefined
646
- ? accepted(state, [], value, undefined, obligations)
647
- : {
648
- ...admitted(verified.admission, value),
649
- ...obligations,
650
- };
656
+ return completedAudit(state, verified, value);
651
657
  }
652
658
  export async function reconcileOperation(input) {
653
659
  try {
@@ -46,6 +46,8 @@ export type ContractObservation = Readonly<{
46
46
  }>;
47
47
  export declare function phaseAtFor(state: Pick<ContractState, "terminal" | "delivery" | "bound">, bindAt: string): string;
48
48
  /** Build the Contract board from one immutable git observation. */
49
- export declare function readContractBoard(observation: GitReadObservation): Promise<ContractBoard>;
49
+ export declare function readContractBoard(observation: GitReadObservation, selected?: ContractId): Promise<ContractBoard>;
50
+ /** Observe one Contract and its target from one fresh ref epoch. */
51
+ export declare function readContractObservation(observation: GitReadObservation, id: ContractId): Promise<ContractObservation>;
50
52
  /** Observe one Contract and its target from one fresh ref epoch. */
51
53
  export declare function readContractObservationAt(repository: GitRepository, channel: GitDecodeChannel, id: ContractId): Promise<ContractObservation>;
@@ -76,7 +76,15 @@ async function rowFor(repository, state, bindAt, targetObservation, register) {
76
76
  };
77
77
  }
78
78
  /** Build the Contract board from one immutable git observation. */
79
- export async function readContractBoard(observation) {
79
+ export async function readContractBoard(observation, selected) {
80
+ if (selected !== undefined) {
81
+ const contract = await readContractObservation(observation, selected);
82
+ return {
83
+ root: observation.repository.primaryWorktree,
84
+ state: observation.snapshot.commit,
85
+ rows: contract.kind === "missing" ? [] : [contract.row],
86
+ };
87
+ }
80
88
  const observed = await observeActiveContractWorld(observation);
81
89
  const register = await readPlaceRegister(observation.repository);
82
90
  const rows = [];
@@ -90,18 +98,20 @@ export async function readContractBoard(observation) {
90
98
  return { root: observation.repository.primaryWorktree, state: observed.snapshot, rows: await Promise.all(rows) };
91
99
  }
92
100
  /** Observe one Contract and its target from one fresh ref epoch. */
101
+ export async function readContractObservation(observation, id) {
102
+ const observed = await observeContractsForAdmissionInObservationAt(observation, [id]);
103
+ const record = observed.journals.get(id);
104
+ if (record === undefined)
105
+ throw new Error(`missing requested Contract observation: ${id}`);
106
+ const state = record.state;
107
+ return state === null
108
+ ? { kind: "missing", id }
109
+ : {
110
+ kind: "present",
111
+ row: await rowFor(observation.repository, state, record.entries[0].at, await observeDeliveryTargetAt(observation, state), await readPlaceRegister(observation.repository)),
112
+ };
113
+ }
114
+ /** Observe one Contract and its target from one fresh ref epoch. */
93
115
  export async function readContractObservationAt(repository, channel, id) {
94
- return withContractReadObservationAt(repository, channel, id, async (observation) => {
95
- const observed = await observeContractsForAdmissionInObservationAt(observation, [id]);
96
- const record = observed.journals.get(id);
97
- if (record === undefined)
98
- throw new Error(`missing requested Contract observation: ${id}`);
99
- const state = record.state;
100
- return state === null
101
- ? { kind: "missing", id }
102
- : {
103
- kind: "present",
104
- row: await rowFor(observation.repository, state, record.entries[0].at, await observeDeliveryTargetAt(observation, state), await readPlaceRegister(observation.repository)),
105
- };
106
- });
116
+ return withContractReadObservationAt(repository, channel, id, async (observation) => await readContractObservation(observation, id));
107
117
  }
@@ -51,6 +51,7 @@ export type OwnedProcess = Readonly<{
51
51
  export declare function terminateOwnedProcess(child: ChildProcess, force?: boolean): Promise<void>;
52
52
  export declare function spawnDetachedProcess(input: DetachedProcessInput): Promise<OwnedProcess>;
53
53
  export declare function runProcess(input: ProcessInput): Promise<ProcessOutcome>;
54
+ export declare function runCrossPlatformProcess(input: ProcessInput): Promise<ProcessOutcome>;
54
55
  export declare function runProcessToExit(input: ProcessLaunch): Promise<ProcessOutcome>;
55
56
  export declare function consumeProcessStdout(input: ProcessInput, consume: (chunk: Buffer) => void): Promise<ProcessConsumption>;
56
57
  export {};
@@ -2,6 +2,7 @@ import { execFile, spawn } from "node:child_process";
2
2
  import { open } from "node:fs/promises";
3
3
  import { setTimeout as delay } from "node:timers/promises";
4
4
  import { promisify } from "node:util";
5
+ import crossSpawn from "cross-spawn";
5
6
  const TERMINATION_GRACE_MS = 250;
6
7
  const STREAM_TAIL_BYTES = 16 * 1024;
7
8
  function tailCapture(limit) {
@@ -138,10 +139,10 @@ function terminalOutcome(code, stdout, stderr, consuming) {
138
139
  truncated: (consuming ? false : stdout.truncated) || stderr.truncated,
139
140
  };
140
141
  }
141
- async function executeProcess(input, timeoutMs, consumeStdout) {
142
+ async function executeProcess(input, timeoutMs, consumeStdout, spawnProcess = spawn) {
142
143
  if (input.signal?.aborted === true)
143
144
  return { outcome: { kind: "cancelled" }, pid: null };
144
- const child = spawn(input.argv[0], input.argv.slice(1), {
145
+ const child = spawnProcess(input.argv[0], input.argv.slice(1), {
145
146
  cwd: input.cwd,
146
147
  env: input.env,
147
148
  detached: true,
@@ -223,6 +224,12 @@ export async function runProcess(input) {
223
224
  throw new Error("buffered process produced a stream consumer error");
224
225
  return result.outcome;
225
226
  }
227
+ export async function runCrossPlatformProcess(input) {
228
+ const result = await executeProcess(input, input.timeoutMs, undefined, crossSpawn);
229
+ if (result.outcome.kind === "stream-error")
230
+ throw new Error("buffered process produced a stream consumer error");
231
+ return result.outcome;
232
+ }
226
233
  export async function runProcessToExit(input) {
227
234
  const result = await executeProcess(input, undefined);
228
235
  if (result.outcome.kind === "stream-error")
@@ -29,6 +29,7 @@ export declare function deferredTaskHolderSettlement(input: Readonly<{
29
29
  taskId: TaskId;
30
30
  diagnostic: string;
31
31
  }>): SettlementReport;
32
+ export declare function contractNamespace(id: ContractId): readonly [string];
32
33
  export type SettlementInput = Readonly<{
33
34
  repository: GitRepository;
34
35
  channel: GitDecodeChannel;
@@ -7,6 +7,9 @@ import { World } from "../world.js";
7
7
  export function deferredTaskHolderSettlement(input) {
8
8
  return { actions: [], lags: [{ kind: "settlement-failed", surface: "task-holder", ...input }] };
9
9
  }
10
+ export function contractNamespace(id) {
11
+ return [contractSegment(id)];
12
+ }
10
13
  function diagnostic(error) {
11
14
  return error instanceof Error ? error.message : String(error);
12
15
  }
@@ -79,7 +82,7 @@ async function settleNamespace(state, effects, actions, lags) {
79
82
  actions.push({
80
83
  kind: "namespace-context",
81
84
  path: effect.path,
82
- action: await repairNamespaceContext(world, [contractSegment(state.id)]),
85
+ action: await repairNamespaceContext(world, contractNamespace(state.id)),
83
86
  });
84
87
  }
85
88
  catch (error) {
@@ -54,11 +54,20 @@ export type TaskDoctorIssue = Readonly<{
54
54
  relation: "needs" | "parent" | "supersedes";
55
55
  tasks: readonly TaskId[];
56
56
  }>;
57
+ export declare function taskRef(board: TaskBoard, id: TaskId): TaskRef;
58
+ export type TaskRelationProjection = Readonly<{
59
+ children(id: TaskId): readonly TaskRef[];
60
+ blocks(id: TaskId): readonly TaskRef[];
61
+ supersededBy(id: TaskId): readonly TaskRef[];
62
+ related(id: TaskId): readonly TaskRef[];
63
+ }>;
64
+ export declare function createTaskRelations(board: TaskBoard): TaskRelationProjection;
57
65
  export declare function taskDisposition(board: TaskBoard, task: TaskDocument): TaskDisposition;
58
- export declare function projectDetailFacts(board: TaskBoard, id: TaskId): TaskDetailFacts | null;
66
+ export declare function taskBlocked(board: TaskBoard, task: TaskDocument): boolean;
67
+ export declare function projectDetailFacts(board: TaskBoard, id: TaskId, relations: TaskRelationProjection): TaskDetailFacts | null;
59
68
  export declare function projectRows(board: TaskBoard, scope: readonly string[] | null, selection: "active" | "closed" | "all"): readonly TaskRow[];
60
69
  export declare function projectReady(board: TaskBoard, scope: readonly string[] | null): readonly TaskRow[];
61
- export declare function projectBlocked(board: TaskBoard, scope: readonly string[] | null): readonly BlockedTaskRow[];
70
+ export declare function projectBlocked(board: TaskBoard, scope: readonly string[] | null, relations: TaskRelationProjection): readonly BlockedTaskRow[];
62
71
  export declare function projectStatusRows(board: TaskBoard, scope: readonly string[] | null): (Readonly<{
63
72
  id: TaskId;
64
73
  title: string;
@@ -77,7 +86,13 @@ export declare function projectStatusRows(board: TaskBoard, scope: readonly stri
77
86
  priority: TaskPriority;
78
87
  disposition: TaskDisposition;
79
88
  })[];
89
+ export type TaskBoardObservation = Readonly<{
90
+ statusRows: ReturnType<typeof projectStatusRows>;
91
+ selectNamespace(namespace: readonly string[]): readonly TaskRow[];
92
+ selectCreatedBy(createdBy: string): readonly TaskRow[];
93
+ }>;
94
+ export declare function projectTaskBoardObservation(board: TaskBoard): TaskBoardObservation;
80
95
  export declare function relationProblem(board: TaskBoard, before: TaskDocument | null, after: TaskDocument): string | null;
81
96
  export declare function diagnoseBoard(board: TaskBoard): readonly TaskDoctorIssue[];
82
- export declare function buildTree(board: TaskBoard, root: TaskId): TaskTreeNode | null;
97
+ export declare function buildTree(board: TaskBoard, root: TaskId, relations: TaskRelationProjection): TaskTreeNode | null;
83
98
  export {};
@@ -1,42 +1,81 @@
1
1
  import { parseTaskId, sameNamespace } from "./identity.js";
2
2
  const terminal = (state) => state === "done" || state === "drop";
3
+ const none = Object.freeze([]);
3
4
  const sorted = (values) => [...values].sort((a, b) => Buffer.compare(Buffer.from(a.id), Buffer.from(b.id)));
4
- function taskRef(board, id) {
5
+ export function taskRef(board, id) {
5
6
  const task = board.tasks.get(id);
6
7
  return task === undefined ? { id, title: null, state: "missing" } : { id, title: task.title, state: task.state };
7
8
  }
8
- function outgoing(document, relation) { return document[relation]; }
9
- function reverse(board, id, relation) {
10
- return sorted([...board.tasks.values()].filter((task) => outgoing(task, relation).includes(id)).map((task) => taskRef(board, task.id)));
9
+ function append(index, key, value) {
10
+ const values = index.get(key);
11
+ if (values === undefined)
12
+ index.set(key, [value]);
13
+ else
14
+ values.push(value);
11
15
  }
12
- function children(board, id) {
13
- return sorted([...board.tasks.values()].filter((task) => task.parent === id).map((task) => taskRef(board, task.id)));
16
+ function freezeRefs(board, index) {
17
+ return new Map([...index].map(([id, targets]) => [id, sorted(targets.map((target) => taskRef(board, target)))]));
14
18
  }
15
- function related(board, task) {
16
- const ids = new Set(task.relates);
17
- for (const candidate of board.tasks.values())
18
- if (candidate.relates.includes(task.id))
19
- ids.add(candidate.id);
20
- return sorted([...ids].map((id) => taskRef(board, id)));
19
+ export function createTaskRelations(board) {
20
+ const children = new Map();
21
+ const blocks = new Map();
22
+ const supersededBy = new Map();
23
+ const incoming = new Map();
24
+ for (const task of board.tasks.values()) {
25
+ if (task.parent !== null)
26
+ append(children, task.parent, task.id);
27
+ for (const need of task.needs)
28
+ append(blocks, need, task.id);
29
+ for (const target of task.supersedes)
30
+ append(supersededBy, target, task.id);
31
+ for (const target of task.relates)
32
+ append(incoming, target, task.id);
33
+ }
34
+ const childRefs = freezeRefs(board, children);
35
+ const blockRefs = freezeRefs(board, blocks);
36
+ const successorRefs = freezeRefs(board, supersededBy);
37
+ const incomingRefs = freezeRefs(board, incoming);
38
+ return {
39
+ children: (id) => childRefs.get(id) ?? none,
40
+ blocks: (id) => blockRefs.get(id) ?? none,
41
+ supersededBy: (id) => successorRefs.get(id) ?? none,
42
+ related: (id) => {
43
+ const task = board.tasks.get(id);
44
+ const ids = new Set(task?.relates ?? []);
45
+ for (const ref of incomingRefs.get(id) ?? none)
46
+ ids.add(ref.id);
47
+ return ids.size === 0 ? none : sorted([...ids].map((related) => taskRef(board, related)));
48
+ },
49
+ };
50
+ }
51
+ function needReleased(board, id) {
52
+ const target = board.tasks.get(id);
53
+ return target !== undefined && terminal(target.state);
54
+ }
55
+ function hasUnresolvedNeeds(board, task) {
56
+ return task.needs.some((id) => !needReleased(board, id));
21
57
  }
22
58
  export function taskDisposition(board, task) {
23
59
  if (task.state !== "open")
24
60
  return task.state;
25
- return task.needs.every((id) => { const target = board.tasks.get(id); return target !== undefined && terminal(target.state); }) ? "ready" : "blocked";
61
+ return hasUnresolvedNeeds(board, task) ? "blocked" : "ready";
26
62
  }
27
- export function projectDetailFacts(board, id) {
63
+ export function taskBlocked(board, task) {
64
+ return (task.state === "open" || task.state === "in_progress") && hasUnresolvedNeeds(board, task);
65
+ }
66
+ export function projectDetailFacts(board, id, relations) {
28
67
  const task = board.tasks.get(id);
29
68
  if (task === undefined)
30
69
  return null;
31
- const needs = task.needs.map((need) => ({ ...taskRef(board, need), released: board.tasks.has(need) && terminal(board.tasks.get(need).state) }));
70
+ const needs = task.needs.map((need) => ({ ...taskRef(board, need), released: needReleased(board, need) }));
32
71
  return {
33
72
  task,
34
73
  needs,
35
74
  blockers: needs.filter((need) => !need.released).map(({ released: _released, ...ref }) => ref),
36
- blocks: reverse(board, id, "needs"),
37
- parent: task.parent === null ? null : taskRef(board, task.parent), children: children(board, id),
38
- supersedes: task.supersedes.map((target) => taskRef(board, target)), supersededBy: reverse(board, id, "supersedes"),
39
- related: related(board, task),
75
+ blocks: relations.blocks(id),
76
+ parent: task.parent === null ? null : taskRef(board, task.parent), children: relations.children(id),
77
+ supersedes: task.supersedes.map((target) => taskRef(board, target)), supersededBy: relations.supersededBy(id),
78
+ related: relations.related(id),
40
79
  };
41
80
  }
42
81
  function inScope(task, scope) { return scope === null || sameNamespace(parseTaskId(task.id).namespace, scope); }
@@ -50,21 +89,29 @@ export function projectRows(board, scope, selection) {
50
89
  export function projectReady(board, scope) {
51
90
  return projectRows(board, scope, "active").filter((row) => row.disposition === "ready");
52
91
  }
53
- export function projectBlocked(board, scope) {
92
+ export function projectBlocked(board, scope, relations) {
54
93
  return projectRows(board, scope, "active").flatMap((row) => {
55
- if (row.state !== "open" && row.state !== "in_progress")
94
+ if (!taskBlocked(board, board.tasks.get(row.id)))
56
95
  return [];
57
- const blockers = projectDetailFacts(board, row.id).blockers;
58
- return blockers.length === 0 ? [] : [{ ...row, blockers }];
96
+ return [{ ...row, blockers: projectDetailFacts(board, row.id, relations).blockers }];
59
97
  });
60
98
  }
61
99
  export function projectStatusRows(board, scope) {
62
- const blockers = new Map(projectBlocked(board, scope).map((row) => [row.id, row.blockers]));
100
+ const relations = createTaskRelations(board);
101
+ const blockers = new Map(projectBlocked(board, scope, relations).map((row) => [row.id, row.blockers]));
63
102
  return projectRows(board, scope, "all").map((row) => {
64
103
  const unresolved = blockers.get(row.id);
65
104
  return unresolved === undefined ? row : { ...row, blockers: unresolved };
66
105
  });
67
106
  }
107
+ export function projectTaskBoardObservation(board) {
108
+ const rows = projectRows(board, null, "all");
109
+ return {
110
+ statusRows: projectStatusRows(board, null),
111
+ selectNamespace: (namespace) => projectRows(board, namespace, "all"),
112
+ selectCreatedBy: (createdBy) => rows.filter((row) => board.tasks.get(row.id)?.createdBy === createdBy),
113
+ };
114
+ }
68
115
  function relationIds(task, relation) {
69
116
  return relation === "parent" ? task.parent === null ? [] : [task.parent] : task[relation];
70
117
  }
@@ -147,7 +194,7 @@ export function diagnoseBoard(board) {
147
194
  }
148
195
  return issues;
149
196
  }
150
- export function buildTree(board, root) {
197
+ export function buildTree(board, root, relations) {
151
198
  if (!board.tasks.has(root))
152
199
  return null;
153
200
  const walk = (id, path) => {
@@ -156,7 +203,7 @@ export function buildTree(board, root) {
156
203
  const node = { task: { ...ref, priority: task?.priority ?? null } };
157
204
  if (path.includes(id))
158
205
  return { ...node, cycle: true, children: [] };
159
- return { ...node, children: children(board, id).map((child) => walk(child.id, [...path, id])) };
206
+ return { ...node, children: relations.children(id).map((child) => walk(child.id, [...path, id])) };
160
207
  };
161
208
  return walk(root, []);
162
209
  }
@@ -5,7 +5,7 @@ import { type TaskCompositionResult } from "./compose.js";
5
5
  import { TaskAuthorityCorruptionError, type TaskPriority, type TaskState } from "./document.js";
6
6
  import { type TaskId } from "./identity.js";
7
7
  import { type AddTaskDocumentInput, type AddTaskInput, type TaskBatchResult, type TaskMutationResult, type TaskOutcome, type TaskRefusal, type TaskRetry, type TaskUpdateResult, type TaskView, type UpdateTaskInput } from "./operations.js";
8
- import { type TaskPage, type TaskQueryExpression, type TaskQueryPredicate, type TaskQueryRow, type TaskQuerySort } from "./query.js";
8
+ import { TASK_RELATION_PREDICATE_FIELDS, type TaskPage, type TaskQueryExpression, type TaskQueryPredicate, type TaskQueryRow, type TaskQuerySort, type TaskRelationPredicateField } from "./query.js";
9
9
  export type TaskDetail = Omit<TaskDetailFacts, "task"> & Readonly<{
10
10
  task: TaskView;
11
11
  }>;
@@ -17,8 +17,8 @@ export type TaskDoctorReport = Readonly<{
17
17
  issues: readonly TaskDoctorIssue[];
18
18
  }>;
19
19
  export type TaskDecompositionTree = TaskOutcome<TaskTreeNode>;
20
- export type { AddTaskDocumentInput, AddTaskInput, BlockedTaskRow, TaskBatchResult, TaskCompositionResult, TaskDoctorIssue, TaskId, TaskMutationResult, TaskOutcome, TaskPriority, TaskRef, TaskRefusal, TaskRetry, TaskRow, TaskState, TaskTreeNode, TaskUpdateResult, TaskView, UpdateTaskInput, TaskPage, TaskQueryExpression, TaskQueryPredicate, TaskQueryRow, TaskQuerySort };
21
- export { TaskAuthorityCorruptionError };
20
+ export type { AddTaskDocumentInput, AddTaskInput, BlockedTaskRow, TaskBatchResult, TaskCompositionResult, TaskDoctorIssue, TaskId, TaskMutationResult, TaskOutcome, TaskPriority, TaskRef, TaskRefusal, TaskRetry, TaskRow, TaskState, TaskTreeNode, TaskUpdateResult, TaskView, UpdateTaskInput, TaskPage, TaskQueryExpression, TaskQueryPredicate, TaskQueryRow, TaskQuerySort, TaskRelationPredicateField, };
21
+ export { TaskAuthorityCorruptionError, TASK_RELATION_PREDICATE_FIELDS };
22
22
  declare class TaskHandle {
23
23
  readonly id: TaskId;
24
24
  private readonly world;
@@ -1,11 +1,11 @@
1
- import { buildTree, diagnoseBoard, projectDetailFacts } from "./board.js";
1
+ import { buildTree, createTaskRelations, diagnoseBoard, projectDetailFacts, } from "./board.js";
2
2
  import { composeTasks } from "./compose.js";
3
3
  import { TaskAuthorityCorruptionError } from "./document.js";
4
4
  import { isTaskSegment, parseTaskId } from "./identity.js";
5
5
  import { addTask, addTaskDocument, batchTasks, blockedTasks, currentNamespace, lifecycleTask, listTasks, queryTasks, readyTasks, setCurrentNamespace, taskView, updateTask, } from "./operations.js";
6
6
  import { readBoard } from "./store.js";
7
- import { isValidTaskLimit, normalizeTaskQuery, MAX_TASK_LIMIT, DEFAULT_TASK_LIMIT } from "./query.js";
8
- export { TaskAuthorityCorruptionError };
7
+ import { isValidTaskLimit, normalizeTaskQuery, TASK_RELATION_PREDICATE_FIELDS, MAX_TASK_LIMIT, DEFAULT_TASK_LIMIT, } from "./query.js";
8
+ export { TaskAuthorityCorruptionError, TASK_RELATION_PREDICATE_FIELDS };
9
9
  function record(value, label) {
10
10
  if (typeof value !== "object" || value === null || Array.isArray(value))
11
11
  throw new TypeError(`${label} must be an object`);
@@ -139,13 +139,15 @@ class TaskHandle {
139
139
  this.world = world;
140
140
  }
141
141
  async read() {
142
- const facts = projectDetailFacts((await readBoard(this.world)).board, this.id);
142
+ const board = (await readBoard(this.world)).board;
143
+ const facts = projectDetailFacts(board, this.id, createTaskRelations(board));
143
144
  return facts === null ? null : { ...facts, task: taskView(facts.task) };
144
145
  }
145
146
  async tree() {
146
147
  if (arguments.length > 0)
147
148
  throw new TypeError("tree accepts no input");
148
- const node = buildTree((await readBoard(this.world)).board, this.id);
149
+ const board = (await readBoard(this.world)).board;
150
+ const node = buildTree(board, this.id, createTaskRelations(board));
149
151
  return node === null ? { kind: "refused", refusal: { kind: "task-missing", taskId: this.id } } : { kind: "accepted", value: node };
150
152
  }
151
153
  update(input) { return updateTask(this.world, this.id, updateInput(input)); }
@@ -1,4 +1,4 @@
1
- import { type BlockedTaskRow, type TaskRow } from "./board.js";
1
+ import { type BlockedTaskRow, type TaskBoardObservation, type TaskRow } from "./board.js";
2
2
  import { type TaskDocument, type TaskPriority, type TaskState } from "./document.js";
3
3
  import { type TaskId } from "./identity.js";
4
4
  import type { WorldRoot } from "../world.js";
@@ -116,24 +116,7 @@ export declare function readyTasks(world: WorldRoot, scope?: "namespace" | "worl
116
116
  export declare function blockedTasks(world: WorldRoot, scope?: "namespace" | "world", parent?: TaskId, limit?: number): Promise<TaskOutcome<TaskPage<BlockedTaskRow>>>;
117
117
  export declare function queryTasks(world: WorldRoot, expression: TaskQueryExpression, scope?: "namespace" | "world", sort?: TaskQuerySort, limit?: number): Promise<TaskOutcome<TaskPage<TaskQueryRow>>>;
118
118
  /** Internal composite observation from one complete Task board read. */
119
- export declare function observeTaskStatusRows(world: WorldRoot): Promise<(Readonly<{
120
- id: TaskId;
121
- title: string;
122
- state: TaskState;
123
- priority: TaskPriority;
124
- disposition: import("./board.js").TaskDisposition;
125
- }> | {
126
- blockers: readonly Readonly<{
127
- id: TaskId;
128
- title: string | null;
129
- state: TaskState | "missing";
130
- }>[];
131
- id: TaskId;
132
- title: string;
133
- state: TaskState;
134
- priority: TaskPriority;
135
- disposition: import("./board.js").TaskDisposition;
136
- })[]>;
119
+ export declare function observeTaskBoard(world: WorldRoot): Promise<TaskBoardObservation>;
137
120
  /** Internal identity catalog from one complete Task board read. */
138
121
  export declare function observeTaskCatalogRows(world: WorldRoot): Promise<readonly TaskRow[]>;
139
122
  export declare function setCurrentNamespace(world: WorldRoot, namespace: readonly string[]): Promise<void>;