@astrosheep/keiyaku 2.9.11 → 2.9.13

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 (218) hide show
  1. package/README.md +79 -85
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/call-terms.js +1 -0
  4. package/build/agents/providers/claude-agent-sdk/adapter.js +17 -4
  5. package/build/cli/commands/akuma/akuma/handler.js +2 -1
  6. package/build/cli/commands/akuma/akuma/meta.js +4 -3
  7. package/build/cli/commands/akuma/catalog.js +2 -0
  8. package/build/cli/commands/akuma/list/handler.js +1 -1
  9. package/build/cli/commands/akuma/list/meta-list.js +12 -0
  10. package/build/cli/commands/akuma/list/meta-ls.js +2 -2
  11. package/build/cli/commands/akuma.js +5 -0
  12. package/build/cli/commands/contract/amend/meta.js +4 -3
  13. package/build/cli/commands/contract/arc/meta.js +2 -0
  14. package/build/cli/commands/contract/audit/handler.js +28 -2
  15. package/build/cli/commands/contract/audit/meta.js +4 -3
  16. package/build/cli/commands/contract/bind/meta.js +4 -4
  17. package/build/cli/commands/contract/petition/handler.js +2 -1
  18. package/build/cli/commands/contract/petition/meta.js +2 -2
  19. package/build/cli/commands/contract/renew/handler.js +44 -3
  20. package/build/cli/commands/contract/renew/meta.js +5 -4
  21. package/build/cli/commands/metadata.js +14 -10
  22. package/build/cli/commands/projection/call/handler.js +1 -1
  23. package/build/cli/commands/projection/call/meta.js +1 -1
  24. package/build/cli/commands/projection/catalog.js +2 -2
  25. package/build/cli/commands/projection/{revive → fork}/handler.js +10 -5
  26. package/build/cli/commands/projection/fork/meta.js +12 -0
  27. package/build/cli/commands/projection/history/handler.js +46 -2
  28. package/build/cli/commands/projection/history/meta.js +3 -3
  29. package/build/cli/commands/projection/kill/handler.js +4 -5
  30. package/build/cli/commands/projection/kill/meta.js +2 -2
  31. package/build/cli/commands/projection/status/handler.js +29 -13
  32. package/build/cli/commands/projection/status/meta.js +14 -3
  33. package/build/cli/commands/projection/tell/handler.js +8 -4
  34. package/build/cli/commands/projection/tell/meta.js +1 -1
  35. package/build/cli/commands/projection/wait/handler.js +45 -20
  36. package/build/cli/commands/projection/wait/meta.js +1 -1
  37. package/build/cli/commands/shared.js +2 -2
  38. package/build/cli/commands/system/catalog.js +2 -0
  39. package/build/cli/commands/task/add/handler.js +28 -12
  40. package/build/cli/commands/task/add/meta.js +18 -6
  41. package/build/cli/commands/task/catalog.js +4 -0
  42. package/build/cli/commands/task/compose/handler.js +18 -0
  43. package/build/cli/commands/task/compose/meta.js +10 -0
  44. package/build/cli/commands/task/doctor/handler.js +2 -3
  45. package/build/cli/commands/task/doctor/meta.js +2 -2
  46. package/build/cli/commands/task/done/meta.js +2 -2
  47. package/build/cli/commands/task/drop/handler.js +7 -2
  48. package/build/cli/commands/task/drop/meta.js +6 -3
  49. package/build/cli/commands/task/hold/meta.js +2 -2
  50. package/build/cli/commands/task/log/meta.js +2 -2
  51. package/build/cli/commands/task/ls/meta.js +2 -2
  52. package/build/cli/commands/task/note/handler.js +6 -0
  53. package/build/cli/commands/task/note/meta.js +11 -0
  54. package/build/cli/commands/task/resume/meta.js +2 -2
  55. package/build/cli/commands/task/shared.js +29 -15
  56. package/build/cli/commands/task/show/meta.js +2 -2
  57. package/build/cli/commands/task/start/meta.js +2 -2
  58. package/build/cli/commands/task/stop/meta.js +2 -2
  59. package/build/cli/commands/task/update/meta.js +3 -2
  60. package/build/cli/commands/verification/handler.js +43 -0
  61. package/build/cli/commands/verification/meta.js +13 -0
  62. package/build/cli/completion.js +16 -13
  63. package/build/cli/flags.js +48 -12
  64. package/build/cli/help.js +29 -30
  65. package/build/cli/index.js +14 -8
  66. package/build/cli/parse-flags.js +60 -21
  67. package/build/cli/parse-metadata.js +10 -4
  68. package/build/cli/parse-selectors.js +22 -4
  69. package/build/cli/parse.js +43 -30
  70. package/build/cli/projection-address.js +7 -7
  71. package/build/cli/render/arc.js +12 -3
  72. package/build/cli/render/audit.js +2 -2
  73. package/build/cli/render/call.js +4 -4
  74. package/build/cli/render/kanshi.js +122 -85
  75. package/build/cli/render/misc.js +2 -2
  76. package/build/cli/render/petition.js +16 -1
  77. package/build/cli/render/projection-history.js +17 -4
  78. package/build/cli/render/shared.js +5 -5
  79. package/build/cli/render/status-indicator.js +47 -0
  80. package/build/cli/render/status.js +106 -76
  81. package/build/cli/render/success-response.js +20 -5
  82. package/build/cli/render/task.js +33 -5
  83. package/build/cli/render/tell.js +10 -4
  84. package/build/cli/render/tool-command-normalization.js +137 -0
  85. package/build/cli/render/tool-presentation.js +2 -1
  86. package/build/cli/render/verification.js +23 -0
  87. package/build/cli/render/wait.js +101 -33
  88. package/build/cli/types.js +2 -1
  89. package/build/config/akuma-loader.js +21 -1
  90. package/build/config/settings/knobs.js +7 -1
  91. package/build/config/settings/schema.js +10 -5
  92. package/build/core/addressing.js +3 -3
  93. package/build/core/amend.js +102 -17
  94. package/build/core/arc.js +4 -4
  95. package/build/core/audit/candidate.js +10 -18
  96. package/build/core/audit/coordinates.js +12 -17
  97. package/build/core/audit/evidence.js +6 -4
  98. package/build/core/audit/facade.js +3 -3
  99. package/build/core/audit/report.js +1 -1
  100. package/build/core/bind-reconciliation.js +42 -23
  101. package/build/core/bind.js +182 -81
  102. package/build/core/call/call.js +6 -7
  103. package/build/core/call/context.js +46 -33
  104. package/build/core/call/execution.js +21 -6
  105. package/build/core/call/prompt.js +11 -42
  106. package/build/core/contract-carrier-runtime.js +209 -105
  107. package/build/core/contract-carrier.js +156 -73
  108. package/build/core/contract-view.js +43 -6
  109. package/build/core/contract.js +2 -1
  110. package/build/core/derived-replay.js +168 -35
  111. package/build/core/draft.js +60 -27
  112. package/build/core/forfeit.js +1 -1
  113. package/build/core/lifecycle-history.js +13 -0
  114. package/build/core/lifecycle-recovery.js +32 -27
  115. package/build/core/lifecycle-runner.js +14 -14
  116. package/build/core/log.js +1 -1
  117. package/build/core/outcome-base.js +2 -2
  118. package/build/core/projection/akuma-name.js +43 -0
  119. package/build/core/projection/generation/database.js +141 -79
  120. package/build/core/projection/generation/ledger.js +91 -0
  121. package/build/core/projection/generation/model.js +19 -14
  122. package/build/core/projection/generation/projection-generation-continuation.js +1 -1
  123. package/build/core/projection/generation/projection-generation-doorbell.js +88 -0
  124. package/build/core/projection/generation/projection-generation-execution.js +24 -0
  125. package/build/core/projection/generation/projection-generation-identity.js +4 -0
  126. package/build/core/projection/generation/projection-generation-launcher.js +72 -138
  127. package/build/core/projection/generation/projection-generation-process.js +16 -15
  128. package/build/core/projection/generation/projection-generation-runner.js +132 -76
  129. package/build/core/projection/generation/projection-generation-runtime.js +10 -61
  130. package/build/core/projection/generation/protocol.js +68 -0
  131. package/build/core/projection/generation/store.js +17 -7
  132. package/build/core/projection/generation/transitions.js +24 -14
  133. package/build/core/projection/index.js +5 -5
  134. package/build/core/projection/projection-core.js +6 -2
  135. package/build/core/projection/projection-execution-observer.js +1 -1
  136. package/build/core/projection/projection-history.js +8 -8
  137. package/build/core/projection/projection-kill.js +20 -10
  138. package/build/core/projection/projection-life-observer.js +8 -8
  139. package/build/core/projection/projection-mint.js +6 -8
  140. package/build/core/projection/projection-status-observation.js +77 -8
  141. package/build/core/projection/projection-status.js +151 -4
  142. package/build/core/projection/projection-wait.js +48 -29
  143. package/build/core/projection/projection-wake.js +28 -88
  144. package/build/core/projection/tell/launch-store.js +1 -1
  145. package/build/core/projection/tell/store.js +1 -1
  146. package/build/core/renew-build.js +204 -74
  147. package/build/core/renew-plan.js +34 -9
  148. package/build/core/renew-rewrite.js +56 -15
  149. package/build/core/renew.js +39 -35
  150. package/build/core/repository-ledger/accepted-fold-read.js +6 -3
  151. package/build/core/repository-ledger/claim-evidence.js +1 -1
  152. package/build/core/repository-ledger/codec.js +11 -5
  153. package/build/core/repository-ledger/current-state-store.js +464 -349
  154. package/build/core/repository-ledger/fold-repository.js +5 -6
  155. package/build/core/repository-ledger/identity.js +60 -0
  156. package/build/core/repository-ledger/inventory.js +3 -3
  157. package/build/core/repository-ledger/read-model.js +57 -9
  158. package/build/core/repository-ledger/write-transaction.js +24 -17
  159. package/build/core/run-control/connection-bound-close.js +41 -0
  160. package/build/core/run-control/detached-lease-bootstrap.js +208 -0
  161. package/build/core/run-control/detached-process.js +14 -0
  162. package/build/core/run-control/index.js +6 -0
  163. package/build/core/{process-group.js → run-control/process-tree.js} +1 -1
  164. package/build/core/run-control/runner-lease.js +172 -0
  165. package/build/core/run-control/sqlite-process-lifetime-lock.js +174 -0
  166. package/build/core/seal.js +44 -32
  167. package/build/core/settlement/claim-delivery.js +22 -9
  168. package/build/core/settlement/claim.js +8 -6
  169. package/build/core/settlement/petition-claim-gates.js +9 -8
  170. package/build/core/settlement/petition-head-guard.js +5 -6
  171. package/build/core/settlement/petition-preview.js +21 -5
  172. package/build/core/settlement/petition.js +51 -17
  173. package/build/core/settlement/settlement.js +20 -15
  174. package/build/core/settlement/verdict.js +2 -2
  175. package/build/core/settlement/verification.js +492 -364
  176. package/build/core/status/board.js +184 -75
  177. package/build/core/status/drift.js +8 -10
  178. package/build/core/task/board.js +1 -0
  179. package/build/core/task/commands.js +25 -0
  180. package/build/core/task/compose.js +328 -0
  181. package/build/core/task/document.js +102 -39
  182. package/build/core/task/index.js +4 -3
  183. package/build/core/task/settlement-git.js +70 -30
  184. package/build/core/task/settlement-policy.js +27 -14
  185. package/build/core/task/source-board.js +1 -0
  186. package/build/core/task/task-bind-preparation.js +59 -9
  187. package/build/core/task/task-contract.js +2 -2
  188. package/build/core/task/task-git-store.js +1 -0
  189. package/build/core/task/task-store-repository.js +2 -0
  190. package/build/core/task/task.js +2 -2
  191. package/build/core/transcripts.js +4 -4
  192. package/build/core/verification-declaration.js +95 -0
  193. package/build/core/worktree-bootstrap.js +1 -1
  194. package/build/core/worktree-path.js +7 -11
  195. package/build/flow-error.js +15 -2
  196. package/build/generated/version.js +2 -2
  197. package/build/git/branches.js +41 -70
  198. package/build/git/commits.js +92 -21
  199. package/build/git/core.js +9 -161
  200. package/build/git/diff/preview.js +4 -4
  201. package/build/git/diff/read.js +4 -4
  202. package/build/git/diff/structured.js +5 -5
  203. package/build/git/process.js +229 -0
  204. package/build/git/refs.js +30 -5
  205. package/build/git/staging.js +39 -115
  206. package/build/git/streaming-batch.js +9 -16
  207. package/build/git/worktree.js +104 -91
  208. package/build/index.js +7 -0
  209. package/package.json +2 -2
  210. package/skills/keiyaku-akuma/SKILL.md +27 -21
  211. package/skills/keiyaku-task/SKILL.md +61 -5
  212. package/skills/keiyaku-workflow/SKILL.md +81 -26
  213. package/build/cli/commands/projection/revive/meta.js +0 -12
  214. package/build/core/projection/projection-life-protocol.js +0 -115
  215. package/build/core/projection/projection-runner-lock.js +0 -382
  216. package/build/core/projection/tell/database.js +0 -127
  217. package/build/core/settlement/verification-coordination.js +0 -305
  218. package/build/core/settlement/verification-supervisor.js +0 -275
@@ -1,15 +1,15 @@
1
1
  import { availableParallelism } from "node:os";
2
- import * as fs from "node:fs/promises";
3
2
  import * as path from "node:path";
4
3
  import { loadKeiyakuSettings } from "../../config/settings/loader.js";
5
4
  import { observeGitRepository } from "../../git/branches.js";
6
5
  import { currentLifecycleState } from "../repository-ledger/read-model.js";
7
- import { parseCarrierIdentity } from "../contract-carrier.js";
6
+ import { verifyCarrier } from "../contract-carrier.js";
7
+ import { resolveExplicitContractAddressFromRead, } from "../addressing.js";
8
8
  import { KEIYAKU_FILE } from "../../keiyaku.js";
9
9
  import { lookupCurrentContract, } from "../repository-ledger/read-model.js";
10
- import { readExactProjectionStatus, readOperationalProjectionStatusBoard, } from "../projection/index.js";
10
+ import { readOperationalProjectionStatusBoard, readProjectionRosterStatusBoard, paginateProjectionStatusRoster, selectProjectionStatus, } from "../projection/index.js";
11
11
  import { deriveQueueReadModel, queueMembership, } from "../settlement/index.js";
12
- import { openTaskStore, readTaskBoard, taskRepositoryFromStore, taskUpdatedAt, } from "../task/index.js";
12
+ import { openTaskStore, readTaskBoard, taskRepositoryFromStore, taskUpdatedAt, taskNamespaceFromScope, } from "../task/index.js";
13
13
  import { replayBlockingFact, } from "./lifecycle.js";
14
14
  import { findRegisteredContractWorktreePathFromModel, stableRepoRoot } from "../worktree-path.js";
15
15
  import { evaluateBaseDrift, resolveDefaultBranchFromSettings, } from "./drift.js";
@@ -28,27 +28,32 @@ function timestampAge(timestamp, now) {
28
28
  const value = Date.parse(timestamp);
29
29
  return Number.isFinite(value) ? Math.max(0, now - value) : 0;
30
30
  }
31
- export function deriveTaskStatusSummary(board, now) {
32
- const eligible = board.tasks.filter((task) => !task.contractId && (task.state === "open" || task.state === "in_progress" || task.state === "on_hold"));
31
+ function taskStatusRow(task, now) {
32
+ const updatedAt = taskUpdatedAt(task);
33
+ return {
34
+ id: task.id,
35
+ localId: task.id,
36
+ namespace: task.namespace,
37
+ coordinate: task.coordinate,
38
+ title: task.title,
39
+ pri: task.pri,
40
+ state: task.state,
41
+ blocked: task.blocked,
42
+ createdAt: task.createdAt,
43
+ updatedAt,
44
+ ageMs: timestampAge(task.createdAt, now),
45
+ activityAgeMs: timestampAge(updatedAt, now),
46
+ };
47
+ }
48
+ export function deriveTaskStatusSummary(board, now, attribution = new Map()) {
49
+ const eligible = board.tasks.filter((task) => !attribution.has(task.namespace ?? "")
50
+ && (task.state === "open" || task.state === "in_progress" || task.state === "on_hold"));
33
51
  const inProgress = eligible.filter((task) => task.state === "in_progress").length;
34
52
  const onHold = eligible.filter((task) => task.state === "on_hold").length;
35
53
  const topTasks = eligible
36
54
  .filter((task) => task.state === "in_progress" || task.state === "on_hold")
37
55
  .sort(compareTaskStatusRows)
38
- .map((task) => {
39
- const updatedAt = taskUpdatedAt(task);
40
- return {
41
- id: task.id,
42
- title: task.title,
43
- pri: task.pri,
44
- state: task.state,
45
- blocked: task.blocked,
46
- createdAt: task.createdAt,
47
- updatedAt,
48
- ageMs: timestampAge(task.createdAt, now),
49
- activityAgeMs: timestampAge(updatedAt, now),
50
- };
51
- });
56
+ .map((task) => taskStatusRow(task, now));
52
57
  const ready = eligible.filter((task) => task.state === "open" && !task.blocked).length;
53
58
  const blocked = eligible.filter((task) => task.state === "open" && task.blocked).length;
54
59
  return {
@@ -60,12 +65,65 @@ export function deriveTaskStatusSummary(board, now) {
60
65
  omitted: ready + blocked,
61
66
  topTasks,
62
67
  diseases: board.diseases,
68
+ contractProgress: deriveContractTaskProgress(board, now, attribution),
63
69
  };
64
70
  }
65
- async function readTaskStatusSummary(cwd, now) {
71
+ function deriveContractTaskProgress(board, now, attribution) {
72
+ const grouped = new Map();
73
+ for (const task of board.tasks) {
74
+ const fact = task.namespace === null ? undefined : attribution.get(task.namespace);
75
+ if (!fact || task.state === "drop")
76
+ continue;
77
+ const group = grouped.get(fact.contractId) ?? { fact, tasks: [] };
78
+ group.tasks.push(task);
79
+ grouped.set(fact.contractId, group);
80
+ }
81
+ return [...grouped.values()].map(({ fact, tasks }) => {
82
+ const ordered = [...tasks].sort(compareTaskStatusRows);
83
+ const visible = ordered.map((task) => taskStatusRow(task, now));
84
+ const current = visible.find((task) => task.state === "in_progress" || task.state === "on_hold" || task.state === "open");
85
+ return {
86
+ contractId: fact.contractId,
87
+ namespace: fact.namespace,
88
+ total: tasks.length,
89
+ done: tasks.filter((task) => task.state === "done").length,
90
+ inProgress: tasks.filter((task) => task.state === "in_progress").length,
91
+ onHold: tasks.filter((task) => task.state === "on_hold").length,
92
+ ready: tasks.filter((task) => task.state === "open" && !task.blocked).length,
93
+ blocked: tasks.filter((task) => task.state === "open" && task.blocked).length,
94
+ ...(current ? { current } : {}),
95
+ tasks: visible,
96
+ };
97
+ }).sort((left, right) => left.contractId.localeCompare(right.contractId));
98
+ }
99
+ export function deriveContractTaskNamespaceAttribution(facts) {
100
+ const byNamespace = new Map();
101
+ for (const fact of facts) {
102
+ const prior = byNamespace.get(fact.namespace);
103
+ if (prior && prior.contractId !== fact.contractId) {
104
+ throw new Error(`task namespace ${fact.namespace} is attributed to multiple contracts`);
105
+ }
106
+ byNamespace.set(fact.namespace, fact);
107
+ }
108
+ return byNamespace;
109
+ }
110
+ /** Attribute namespaces only from accepted lifecycle scope facts. */
111
+ export function contractTaskNamespaceFacts(models) {
112
+ return models.flatMap((model) => {
113
+ const namespaces = model.effectiveScope.flatMap((term) => taskNamespaceFromScope(term) ?? []);
114
+ if (namespaces.length !== 1)
115
+ return [];
116
+ return [{
117
+ contractId: model.contract,
118
+ namespace: namespaces[0],
119
+ lifecycle: model.phase === "claimed" || model.phase === "forfeited" ? "terminal" : "active",
120
+ }];
121
+ });
122
+ }
123
+ async function readTaskStatusSummary(cwd, now, attribution = new Map()) {
66
124
  try {
67
125
  const board = await readTaskBoard(taskRepositoryFromStore(await openTaskStore(cwd)));
68
- return deriveTaskStatusSummary(board, now);
126
+ return deriveTaskStatusSummary(board, now, attribution);
69
127
  }
70
128
  catch (error) {
71
129
  return {
@@ -77,6 +135,7 @@ async function readTaskStatusSummary(cwd, now) {
77
135
  omitted: 0,
78
136
  topTasks: [],
79
137
  diseases: [],
138
+ contractProgress: [],
80
139
  diagnostic: error instanceof Error ? error.message : String(error),
81
140
  };
82
141
  }
@@ -113,6 +172,9 @@ function bootstrapReplayBlockers(model) {
113
172
  : [];
114
173
  }
115
174
  function deriveNextStep(input) {
175
+ if (input.state.state === "claimed" || input.state.state === "forfeited") {
176
+ return { kind: "terminal", detail: input.state.state };
177
+ }
116
178
  if (input.state.state === "corrupt") {
117
179
  return { kind: "inspect-log", detail: "inspect log" };
118
180
  }
@@ -123,9 +185,6 @@ function deriveNextStep(input) {
123
185
  if (input.state.state === "petitioned") {
124
186
  return { kind: "await-claim", detail: "await claim" };
125
187
  }
126
- if (input.baseDrift.state === "drift" || input.baseDrift.state === "orphaned") {
127
- return { kind: "renew", detail: "renew" };
128
- }
129
188
  // Derived a0 (bound/active, no explicit arc) petitions directly; explicit open arcs still suggest arc work.
130
189
  if (input.currentArc.state === "open" && input.currentArc.number === 0) {
131
190
  return { kind: "petition", detail: "petition" };
@@ -171,24 +230,24 @@ export async function evaluateDistinctBaseDrifts(cwd, bases, defaultBranch, eval
171
230
  export async function readKanshiBoard(cwd, now, opts) {
172
231
  const targetFilter = opts?.target ?? null;
173
232
  const loadedSettings = await loadKeiyakuSettings(cwd);
174
- const tasks = await readTaskStatusSummary(cwd, now);
175
233
  const repository = await observeGitRepository(cwd);
176
234
  if (repository.capability === "unknown")
177
235
  throw repository.error;
178
236
  if (repository.capability !== "available" || repository.coordinate !== "repository") {
179
- const exactProjection = opts?.projectionId === undefined
237
+ const exactProjection = opts?.projectionAddress === undefined
180
238
  ? undefined
181
- : await readExactProjectionStatus(cwd, now, opts.projectionId);
239
+ : await selectProjectionStatus(cwd, now, { kind: "address", token: opts.projectionAddress });
182
240
  return {
183
241
  contracts: [],
184
242
  contractsObservable: false,
185
243
  commissionPlaces: new Map(),
186
- projections: opts?.projectionId === undefined
187
- ? await readOperationalProjectionStatusBoard(cwd, now)
188
- : exactProjection ? { rows: [exactProjection] } : null,
244
+ projections: opts?.projectionAddress === undefined
245
+ ? opts?.roster ? await readProjectionRosterStatusBoard(cwd, now) : await readOperationalProjectionStatusBoard(cwd, now)
246
+ : exactProjection?.board ?? null,
189
247
  queueLength: 0,
190
248
  diseases: loadedSettings.diseases,
191
249
  targetFilter,
250
+ tasks: await readTaskStatusSummary(cwd, now),
192
251
  // Task and contract boards are repository organs; a non-repository cwd
193
252
  // cannot truthfully turn their absence into a zero count.
194
253
  };
@@ -203,7 +262,18 @@ export async function readKanshiBoard(cwd, now, opts) {
203
262
  defaultBranch = null;
204
263
  }
205
264
  const lifecycle = await readStatusLifecycle(cwd);
265
+ const taskAttribution = deriveContractTaskNamespaceAttribution(contractTaskNamespaceFacts(lifecycle.contracts));
266
+ const tasks = await readTaskStatusSummary(cwd, now, taskAttribution);
206
267
  const stableRoot = await stableRepoRoot(cwd);
268
+ const commissionFilter = opts?.commission === undefined
269
+ ? undefined
270
+ : resolveExplicitContractAddressFromRead({
271
+ read: lifecycle,
272
+ stableRoot,
273
+ workdir: cwd,
274
+ raw: opts.commission.raw,
275
+ source: opts.commission.source,
276
+ }).address.binding.commissionId;
207
277
  const terminalById = new Map(lifecycle.contracts.map((model) => [
208
278
  model.contract,
209
279
  model.phase === "claimed" || model.phase === "forfeited",
@@ -227,16 +297,19 @@ export async function readKanshiBoard(cwd, now, opts) {
227
297
  commissionPlaces.set(model.contract, model.resources.worktree?.place ?? null);
228
298
  derivedTargetById.set(model.contract, model.pins.target.ref);
229
299
  const base = model.pins.base;
230
- // Terminal lifecycle facts retire their carrier and worktree projections.
231
- // Do not interpret a different active workspace-here carrier as terminal
232
- // replay debt.
233
- if (model.phase === "claimed" || model.phase === "forfeited")
300
+ const terminal = model.phase === "claimed" || model.phase === "forfeited";
301
+ // Terminal contracts retire from the aggregate board. An exact selector
302
+ // may still open the folded row as a read-only carrier for its task
303
+ // namespace, including completed tasks.
304
+ if (terminal && commissionFilter !== model.contract)
234
305
  continue;
235
306
  const after = model.causal.after.map((id) => ({ id, terminal: terminalById.get(id) ?? false }));
236
307
  const currentArc = deriveCurrentArc(model);
237
- const baseDrift = base
238
- ? baseDrifts.get(base) ?? { state: "unknown", label: "n/a (base drift unavailable)" }
239
- : { state: "unknown", label: "n/a (no default branch)" };
308
+ const baseDrift = terminal
309
+ ? { state: "unknown", label: "n/a (terminal contract)" }
310
+ : base
311
+ ? baseDrifts.get(base) ?? { state: "unknown", label: "n/a (base drift unavailable)" }
312
+ : { state: "unknown", label: "n/a (no default branch)" };
240
313
  const nextStep = deriveNextStep({
241
314
  id: model.contract,
242
315
  state,
@@ -244,40 +317,39 @@ export async function readKanshiBoard(cwd, now, opts) {
244
317
  baseDrift,
245
318
  after,
246
319
  });
247
- if (nextStep.kind === "renew" && baseDrift.state === "drift") {
248
- nextStep.hint = `run: keiyaku @${model.contract} renew — follows ${baseDrift.defaultBranch} ▲${baseDrift.behindBy}`;
249
- }
250
- const membership = queueMembership(queueModel, model.contract);
320
+ const membership = terminal ? undefined : queueMembership(queueModel, model.contract);
251
321
  const queuePosition = membership?.position ?? null;
252
322
  const targetQueueLength = membership?.queueLength ?? 0;
253
- const replayBlockers = bootstrapReplayBlockers(model);
254
- const carrierRoot = model.resources.worktree === null
255
- ? stableRoot
256
- : await findRegisteredContractWorktreePathFromModel(stableRoot, model);
257
- if (model.resources.worktree !== null && carrierRoot === undefined) {
323
+ const replayBlockers = terminal ? [] : bootstrapReplayBlockers(model);
324
+ const carrierRoot = terminal
325
+ ? undefined
326
+ : model.resources.worktree === null
327
+ ? stableRoot
328
+ : await findRegisteredContractWorktreePathFromModel(stableRoot, model);
329
+ if (!terminal && model.resources.worktree !== null && carrierRoot === undefined) {
258
330
  replayBlockers.unshift(replayBlockingFact(model.contract, "worktree"));
259
331
  }
260
- else if (carrierRoot !== undefined) {
261
- let carrierIdentity = null;
262
- try {
263
- carrierIdentity = parseCarrierIdentity(await fs.readFile(path.join(carrierRoot, KEIYAKU_FILE), "utf8"));
264
- }
265
- catch {
266
- carrierIdentity = null;
267
- }
268
- if (carrierIdentity?.contractId !== model.contract || carrierIdentity.ledgerHead !== model.observedLedgerHead) {
269
- const reason = carrierIdentity === null
332
+ else if (!terminal && carrierRoot !== undefined) {
333
+ const verification = await verifyCarrier({ target: path.join(carrierRoot, KEIYAKU_FILE), lifecycle: model });
334
+ if (verification.status !== "current") {
335
+ const actual = "actual" in verification ? verification.actual : null;
336
+ const reason = verification.status === "missing" || verification.status === "invalid-header"
270
337
  ? "missing-or-invalid"
271
- : carrierIdentity.contractId !== model.contract
272
- ? "contract-id-mismatch"
273
- : "ledger-head-mismatch";
338
+ : verification.status === "modified"
339
+ ? "carrier-modified"
340
+ : actual?.contractId !== model.contract
341
+ ? "contract-id-mismatch"
342
+ : actual?.contractHead !== model.contractHead
343
+ ? "contract-head-mismatch"
344
+ : "carrier-digest-mismatch";
274
345
  replayBlockers.push(replayBlockingFact(model.contract, "carrier", {
275
346
  reason,
276
- expected: { contractId: model.contract, ledgerHead: model.observedLedgerHead },
277
- observed: carrierIdentity,
347
+ expected: verification.expected,
348
+ observed: actual,
278
349
  }));
279
350
  }
280
351
  }
352
+ const taskProgress = tasks?.contractProgress.find((progress) => progress.contractId === model.contract);
281
353
  contracts.push({
282
354
  id: model.contract,
283
355
  slug: contractSlugFromId(model.contract),
@@ -301,6 +373,7 @@ export async function readKanshiBoard(cwd, now, opts) {
301
373
  lifecycleDiagnostics: outcome.diagnostics,
302
374
  replayBlockers,
303
375
  queueDiagnostics: membership ? [...membership.diagnostics] : [],
376
+ ...(taskProgress ? { tasks: taskProgress } : {}),
304
377
  });
305
378
  }
306
379
  for (const contract of lifecycle.isolatedContracts) {
@@ -317,30 +390,66 @@ export async function readKanshiBoard(cwd, now, opts) {
317
390
  replayBlockers: [], queueDiagnostics: [],
318
391
  });
319
392
  }
320
- const filteredContracts = targetFilter === null
321
- ? contracts
322
- : contracts.filter((contract) => {
323
- const derived = derivedTargetById.get(contract.id);
324
- return derived === undefined || derived === targetFilter;
325
- });
393
+ const filteredContracts = contracts.filter((contract) => {
394
+ if (commissionFilter !== undefined && contract.id !== commissionFilter)
395
+ return false;
396
+ if (targetFilter === null)
397
+ return true;
398
+ const derived = derivedTargetById.get(contract.id);
399
+ return derived === undefined || derived === targetFilter;
400
+ });
326
401
  const queueLength = targetFilter === null
327
402
  ? boardQueueLength
328
403
  : (queueModel.targetQueues.find((queue) => queue.target === targetFilter)?.ordered.length ?? 0);
329
- const exactProjection = opts?.projectionId === undefined
404
+ const exactProjection = opts?.projectionAddress === undefined
330
405
  ? undefined
331
- : await readExactProjectionStatus(cwd, now, opts.projectionId);
406
+ : await selectProjectionStatus(cwd, now, { kind: "address", token: opts.projectionAddress });
407
+ const operationalProjections = opts?.projectionAddress === undefined
408
+ ? opts?.roster ? await readProjectionRosterStatusBoard(cwd, now) : await readOperationalProjectionStatusBoard(cwd, now)
409
+ : undefined;
410
+ // The aggregate reader deliberately folds terminal rows before their identity
411
+ // is opened. A commission scope therefore keeps only attributable live rows,
412
+ // never an aggregate whose counts could include a different commission.
413
+ const selectedProjections = opts?.projectionAddress === undefined
414
+ ? commissionFilter === undefined
415
+ ? operationalProjections ?? null
416
+ : operationalProjections
417
+ ? {
418
+ rows: operationalProjections.rows.filter((row) => row.binding?.kind === "commission" && row.binding.commissionId === commissionFilter),
419
+ }
420
+ : null
421
+ : exactProjection?.board ?? null;
422
+ const projections = opts?.roster && selectedProjections
423
+ ? paginateProjectionStatusRoster(selectedProjections, { limit: opts.rosterLimit, offset: opts.rosterOffset })
424
+ : selectedProjections;
425
+ const filteredCommissionPlaces = commissionFilter === undefined
426
+ ? commissionPlaces
427
+ : new Map([[commissionFilter, commissionPlaces.get(commissionFilter) ?? null]]);
332
428
  return {
333
429
  contracts: filteredContracts,
334
430
  contractsObservable: true,
335
- commissionPlaces,
336
- projections: opts?.projectionId === undefined
337
- ? await readOperationalProjectionStatusBoard(cwd, now)
338
- : exactProjection ? { rows: [exactProjection] } : null,
431
+ commissionPlaces: filteredCommissionPlaces,
432
+ projections,
339
433
  queueLength,
340
434
  defaultBranch: defaultBranch?.branch,
341
435
  defaultHead: defaultBranch?.head,
342
436
  diseases: loadedSettings.diseases,
343
437
  targetFilter,
344
- ...(tasks ? { tasks } : {}),
438
+ ...(tasks ? {
439
+ tasks: commissionFilter === undefined
440
+ ? tasks
441
+ : {
442
+ ...tasks,
443
+ total: 0,
444
+ inProgress: 0,
445
+ onHold: 0,
446
+ ready: 0,
447
+ blocked: 0,
448
+ omitted: 0,
449
+ topTasks: [],
450
+ contractProgress: tasks.contractProgress.filter((progress) => progress.contractId === commissionFilter),
451
+ },
452
+ } : {}),
453
+ ...(commissionFilter === undefined ? {} : { commissionFilter }),
345
454
  };
346
455
  }
@@ -2,7 +2,8 @@ import { assertSettingsKnobUsable } from "../../config/settings/disease.js";
2
2
  import { loadKeiyakuSettings } from "../../config/settings/loader.js";
3
3
  import { FlowError } from "../../flow-error.js";
4
4
  import { resolveLocalBranchHead } from "../../git/refs.js";
5
- import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, wrapGitError } from "../../git/core.js";
5
+ import { readCommitCoordinate, commitCoordinateFailureError } from "../../git/commits.js";
6
+ import { createGit } from "../../git/core.js";
6
7
  function findBindEntry(entries) {
7
8
  return entries.find((entry) => entry.kind === "bind");
8
9
  }
@@ -26,15 +27,12 @@ export function commissionTarget(entries) {
26
27
  };
27
28
  }
28
29
  export async function resolveCommit(cwd, ref) {
29
- const git = createGit(cwd);
30
- try {
31
- return (await git.raw(["rev-parse", "--verify", `${ref}^{commit}`])).trim();
32
- }
33
- catch (error) {
34
- if (errorContainsAnyPattern(error, MISSING_HEAD_PATTERNS))
35
- return null;
36
- throw wrapGitError(`rev-parse ${ref}`, error, cwd);
37
- }
30
+ const read = await readCommitCoordinate(cwd, ref);
31
+ if (read.status === "ok")
32
+ return read.coordinate.commit;
33
+ if (read.status === "missing" || read.status === "non-commit")
34
+ return null;
35
+ throw commitCoordinateFailureError(read, cwd);
38
36
  }
39
37
  export async function readSettingsDiseases(cwd) {
40
38
  const loaded = await loadKeiyakuSettings(cwd);
@@ -198,6 +198,7 @@ export function buildTaskBoardFromDocuments(documents) {
198
198
  from: [...task.from],
199
199
  supersedes: [...task.supersedes],
200
200
  relatesTo: [...task.relatesTo],
201
+ notes: task.notes.map((note) => ({ ...note })),
201
202
  createdAt: task.createdAt,
202
203
  ...(task.updatedAt === undefined ? {} : { updatedAt: task.updatedAt }),
203
204
  creator: task.creator,
@@ -21,6 +21,7 @@ function documentFromView(view) {
21
21
  from: [...view.from],
22
22
  supersedes: [...view.supersedes],
23
23
  relatesTo: [...view.relatesTo],
24
+ notes: view.notes.map((note) => ({ ...note })),
24
25
  createdAt: view.createdAt,
25
26
  ...(view.updatedAt === undefined ? {} : { updatedAt: view.updatedAt }),
26
27
  creator: view.creator,
@@ -140,6 +141,7 @@ export async function createTask(repository, input, deps) {
140
141
  from,
141
142
  supersedes,
142
143
  relatesTo,
144
+ notes: [],
143
145
  createdAt: dateTimestamp("createdAt", now),
144
146
  updatedAt: dateTimestamp("updatedAt", now),
145
147
  creator: input.creator,
@@ -322,6 +324,26 @@ export async function doneTask(repository, fullId, now = new Date()) {
322
324
  }, (id) => `Done task ${id}`, now);
323
325
  }
324
326
  export async function dropTask(repository, fullId, now = new Date()) {
327
+ return dropTaskWithNote(repository, fullId, undefined, now);
328
+ }
329
+ export async function appendTaskNote(repository, fullId, input, now = new Date()) {
330
+ nonblank("task note actor", input.actor);
331
+ nonblank("task note text", input.text);
332
+ return repository.mutate((board) => {
333
+ requireHealthy(board);
334
+ const id = resolveTaskId(board, fullId);
335
+ const current = requireExisting(board, id);
336
+ const document = documentFromView(current);
337
+ document.notes.push({ actor: input.actor, timestamp: dateTimestamp("task note timestamp", now), text: input.text });
338
+ document.updatedAt = advancedTaskUpdatedAt(current, now);
339
+ return replaceDocument(document, `Note task ${id}`);
340
+ });
341
+ }
342
+ export async function dropTaskWithNote(repository, fullId, note, now = new Date()) {
343
+ if (note !== undefined) {
344
+ nonblank("task note actor", note.actor);
345
+ nonblank("task note text", note.text);
346
+ }
325
347
  return transition(repository, fullId, (document) => {
326
348
  rejectBound(document, "drop");
327
349
  if (document.state !== "open" && document.state !== "in_progress" && document.state !== "on_hold") {
@@ -329,6 +351,9 @@ export async function dropTask(repository, fullId, now = new Date()) {
329
351
  }
330
352
  document.state = "drop";
331
353
  delete document.startedAt;
354
+ if (note !== undefined) {
355
+ document.notes.push({ actor: note.actor, timestamp: dateTimestamp("task note timestamp", now), text: note.text });
356
+ }
332
357
  }, (id) => `Drop task ${id}`, now);
333
358
  }
334
359
  export function selectSoleInProgress(board) {