@astrosheep/keiyaku 4.1.0 → 4.1.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.
Files changed (278) hide show
  1. package/README.md +1 -1
  2. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -0
  3. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +3 -0
  4. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +3 -6
  5. package/build/src/akuma/akuma-errors.d.ts +6 -0
  6. package/build/src/akuma/akuma-errors.js +9 -0
  7. package/build/src/akuma/akuma-handle.d.ts +39 -0
  8. package/build/src/akuma/akuma-handle.js +263 -0
  9. package/build/src/akuma/akuma-observe.d.ts +19 -0
  10. package/build/src/akuma/akuma-observe.js +74 -0
  11. package/build/src/akuma/akuma-product-symbols.d.ts +1 -0
  12. package/build/src/akuma/akuma-product-symbols.js +1 -0
  13. package/build/src/akuma/akuma-product.d.ts +18 -0
  14. package/build/src/akuma/akuma-product.js +154 -0
  15. package/build/src/akuma/akuma.d.ts +37 -81
  16. package/build/src/akuma/akuma.js +39 -447
  17. package/build/src/akuma/allowed.d.ts +1 -0
  18. package/build/src/akuma/allowed.js +6 -0
  19. package/build/src/akuma/archetype.js +8 -5
  20. package/build/src/akuma/body-supervisor.d.ts +26 -0
  21. package/build/src/akuma/body-supervisor.js +89 -0
  22. package/build/src/akuma/body.d.ts +45 -4
  23. package/build/src/akuma/body.js +145 -35
  24. package/build/src/akuma/coordinate.js +17 -7
  25. package/build/src/akuma/heart/facts.js +1 -1
  26. package/build/src/akuma/heart/index.d.ts +1 -1
  27. package/build/src/akuma/heart/index.js +2 -4
  28. package/build/src/akuma/heart/request-authority.js +4 -4
  29. package/build/src/akuma/heart/request-rows.js +27 -16
  30. package/build/src/akuma/heart/rows.js +79 -41
  31. package/build/src/akuma/heart/soul.js +1 -1
  32. package/build/src/akuma/heart/storage.d.ts +1 -0
  33. package/build/src/akuma/heart/storage.js +76 -11
  34. package/build/src/akuma/heart/tells.js +35 -18
  35. package/build/src/akuma/heart/timeline.js +19 -11
  36. package/build/src/akuma/identity.d.ts +1 -0
  37. package/build/src/akuma/identity.js +1 -0
  38. package/build/src/akuma/index.d.ts +3 -3
  39. package/build/src/akuma/index.js +1 -1
  40. package/build/src/akuma/nuke.js +106 -14
  41. package/build/src/akuma/projection-read.d.ts +17 -0
  42. package/build/src/akuma/projection-read.js +135 -0
  43. package/build/src/akuma/projection.d.ts +1 -16
  44. package/build/src/akuma/projection.js +77 -154
  45. package/build/src/akuma/provider-recipe.js +11 -5
  46. package/build/src/akuma/provider.js +105 -62
  47. package/build/src/akuma/providers/acp/config.js +5 -6
  48. package/build/src/akuma/providers/acp/core.js +17 -10
  49. package/build/src/akuma/providers/acp/events.js +38 -29
  50. package/build/src/akuma/providers/acp/index.js +13 -9
  51. package/build/src/akuma/providers/claude/events.js +8 -12
  52. package/build/src/akuma/providers/claude/index.d.ts +1 -0
  53. package/build/src/akuma/providers/claude/index.js +55 -26
  54. package/build/src/akuma/providers/claude/input.js +6 -2
  55. package/build/src/akuma/providers/codex-app-server/events.js +61 -36
  56. package/build/src/akuma/providers/codex-app-server/index.js +25 -13
  57. package/build/src/akuma/providers/grok-build/index.js +22 -25
  58. package/build/src/akuma/providers/index.js +2 -18
  59. package/build/src/akuma/providers/opencode-sdk/events.js +32 -18
  60. package/build/src/akuma/providers/opencode-sdk/index.js +26 -17
  61. package/build/src/akuma/providers/opencode-sdk/session.js +21 -5
  62. package/build/src/akuma/providers/pi/events.js +26 -19
  63. package/build/src/akuma/providers/pi/index.js +58 -40
  64. package/build/src/akuma/publication.js +9 -9
  65. package/build/src/akuma/request-execution.d.ts +6 -0
  66. package/build/src/akuma/request-execution.js +86 -0
  67. package/build/src/akuma/request-serve.d.ts +12 -1
  68. package/build/src/akuma/request-serve.js +19 -122
  69. package/build/src/akuma/request-wire.d.ts +2 -2
  70. package/build/src/akuma/request-wire.js +75 -102
  71. package/build/src/akuma/requests.js +2 -2
  72. package/build/src/akuma/{body-turn.d.ts → turn-drive.d.ts} +2 -25
  73. package/build/src/akuma/{body-turn.js → turn-drive.js} +20 -103
  74. package/build/src/akuma-body.js +6 -5
  75. package/build/src/body/amend.d.ts +4 -1
  76. package/build/src/body/amend.js +14 -6
  77. package/build/src/body/decode.js +2 -4
  78. package/build/src/body/envelope.js +2 -4
  79. package/build/src/body/region.d.ts +1 -2
  80. package/build/src/body/region.js +2 -70
  81. package/build/src/body/render.js +17 -12
  82. package/build/src/body/shape.js +1 -7
  83. package/build/src/cli/accepted.d.ts +1 -1
  84. package/build/src/cli/accepted.js +8 -1
  85. package/build/src/cli/commands/akuma-invoke.js +18 -6
  86. package/build/src/cli/commands/akuma.d.ts +0 -6
  87. package/build/src/cli/commands/akuma.js +11 -24
  88. package/build/src/cli/commands/amend.d.ts +2 -2
  89. package/build/src/cli/commands/amend.js +2 -4
  90. package/build/src/cli/commands/bind.d.ts +4 -4
  91. package/build/src/cli/commands/bind.js +16 -6
  92. package/build/src/cli/commands/contract-invoke.d.ts +25 -0
  93. package/build/src/cli/commands/contract-invoke.js +193 -0
  94. package/build/src/cli/commands/contract.d.ts +109 -10
  95. package/build/src/cli/commands/contract.js +339 -16
  96. package/build/src/cli/commands/install.d.ts +1 -1
  97. package/build/src/cli/commands/install.js +23 -15
  98. package/build/src/cli/commands/task-invoke.js +187 -67
  99. package/build/src/cli/commands/task-query.js +48 -24
  100. package/build/src/cli/commands/task.js +150 -38
  101. package/build/src/cli/coordinates.js +2 -4
  102. package/build/src/cli/invoke.js +92 -208
  103. package/build/src/cli/main.js +4 -2
  104. package/build/src/cli/parse.d.ts +3 -90
  105. package/build/src/cli/parse.js +27 -233
  106. package/build/src/cli/render/akuma-activity.d.ts +38 -0
  107. package/build/src/cli/render/akuma-activity.js +362 -0
  108. package/build/src/cli/render/akuma-tool-command.js +6 -7
  109. package/build/src/cli/render/akuma-tool.js +10 -8
  110. package/build/src/cli/render/akuma.d.ts +1 -1
  111. package/build/src/cli/render/akuma.js +61 -353
  112. package/build/src/cli/render/audit.js +5 -15
  113. package/build/src/cli/render/board.d.ts +1 -0
  114. package/build/src/cli/render/board.js +17 -0
  115. package/build/src/cli/render/catalog.js +81 -8
  116. package/build/src/cli/render/contract-history.d.ts +2 -0
  117. package/build/src/cli/render/contract-history.js +83 -0
  118. package/build/src/cli/render/contract-observation.d.ts +10 -0
  119. package/build/src/cli/render/contract-observation.js +58 -0
  120. package/build/src/cli/render/contract.d.ts +1 -2
  121. package/build/src/cli/render/contract.js +41 -142
  122. package/build/src/cli/render/kanshi.js +239 -215
  123. package/build/src/cli/render/receipt.js +2 -9
  124. package/build/src/cli/render/refusal.js +10 -15
  125. package/build/src/cli/render/region.js +22 -6
  126. package/build/src/cli/render/settings.js +4 -4
  127. package/build/src/cli/render/task.js +34 -14
  128. package/build/src/cli/render/terminal.js +16 -5
  129. package/build/src/cli/result.d.ts +3 -3
  130. package/build/src/cli/runtime.d.ts +6 -1
  131. package/build/src/cli/runtime.js +84 -63
  132. package/build/src/cli/selectors.js +2 -4
  133. package/build/src/cli/usage.js +6 -5
  134. package/build/src/contract-worktree.d.ts +1 -23
  135. package/build/src/contract-worktree.js +79 -155
  136. package/build/src/coordination/durable-file.js +7 -3
  137. package/build/src/coordination/sqlite-transaction-lock.js +29 -12
  138. package/build/src/core/facts/codec.js +7 -3
  139. package/build/src/core/facts/types.d.ts +1 -1
  140. package/build/src/core/subject.js +16 -9
  141. package/build/src/core/verbs/abandon.d.ts +1 -1
  142. package/build/src/core/verbs/abandon.js +1 -1
  143. package/build/src/core/verbs/amend.d.ts +1 -1
  144. package/build/src/core/verbs/amend.js +7 -7
  145. package/build/src/core/verbs/attestation.js +4 -1
  146. package/build/src/core/verbs/bind.d.ts +1 -1
  147. package/build/src/core/verbs/bind.js +1 -1
  148. package/build/src/core/verbs/placement.d.ts +1 -1
  149. package/build/src/core/verbs/placement.js +10 -8
  150. package/build/src/dispatch/index.js +8 -6
  151. package/build/src/duration.js +5 -1
  152. package/build/src/git/admission.js +5 -3
  153. package/build/src/git/hooks.d.ts +8 -0
  154. package/build/src/git/hooks.js +63 -14
  155. package/build/src/git/identity.js +1 -4
  156. package/build/src/git/integration.d.ts +2 -3
  157. package/build/src/git/integration.js +32 -39
  158. package/build/src/git/nuke.d.ts +1 -1
  159. package/build/src/git/nuke.js +110 -57
  160. package/build/src/git/observe.d.ts +13 -1
  161. package/build/src/git/observe.js +51 -44
  162. package/build/src/git/process.js +4 -2
  163. package/build/src/git/read-observation.js +10 -8
  164. package/build/src/git/reconcile.d.ts +22 -2
  165. package/build/src/git/reconcile.js +38 -274
  166. package/build/src/git/repository.d.ts +2 -0
  167. package/build/src/git/repository.js +13 -22
  168. package/build/src/git/scratch.d.ts +8 -5
  169. package/build/src/git/scratch.js +29 -2
  170. package/build/src/git/target-placement.d.ts +9 -10
  171. package/build/src/git/target-placement.js +59 -62
  172. package/build/src/git/tender.js +13 -46
  173. package/build/src/git/terminal-reconcile.d.ts +21 -0
  174. package/build/src/git/terminal-reconcile.js +278 -0
  175. package/build/src/git/terminal-seal.js +15 -12
  176. package/build/src/git/tree.js +5 -2
  177. package/build/src/git/workspace.d.ts +24 -2
  178. package/build/src/git/workspace.js +120 -16
  179. package/build/src/identity/coordinates.js +3 -1
  180. package/build/src/identity/selector.js +6 -3
  181. package/build/src/kanshi/fleet.d.ts +6 -0
  182. package/build/src/kanshi/fleet.js +27 -0
  183. package/build/src/kanshi/index.d.ts +1 -1
  184. package/build/src/kanshi/read.js +60 -48
  185. package/build/src/kanshi/report.d.ts +11 -23
  186. package/build/src/kanshi/select.js +25 -24
  187. package/build/src/library/address.d.ts +2 -1
  188. package/build/src/library/address.js +7 -8
  189. package/build/src/library/akuma-creation.js +20 -12
  190. package/build/src/library/audit.d.ts +0 -1
  191. package/build/src/library/audit.js +1 -2
  192. package/build/src/library/bind.d.ts +53 -5
  193. package/build/src/library/bind.js +98 -22
  194. package/build/src/library/catalog.d.ts +1 -0
  195. package/build/src/library/catalog.js +6 -3
  196. package/build/src/library/configuration.js +3 -1
  197. package/build/src/library/continuation.d.ts +15 -4
  198. package/build/src/library/continuation.js +24 -16
  199. package/build/src/library/contract-bind.d.ts +26 -0
  200. package/build/src/library/contract-bind.js +106 -0
  201. package/build/src/library/contract-operations.d.ts +77 -0
  202. package/build/src/library/contract-operations.js +130 -0
  203. package/build/src/library/contract-types.d.ts +95 -0
  204. package/build/src/library/contract-types.js +1 -0
  205. package/build/src/library/contract.d.ts +19 -154
  206. package/build/src/library/contract.js +46 -261
  207. package/build/src/library/fleet.d.ts +1 -1
  208. package/build/src/library/fleet.js +19 -11
  209. package/build/src/library/input.d.ts +2 -0
  210. package/build/src/library/input.js +14 -0
  211. package/build/src/library/keiyaku.d.ts +8 -7
  212. package/build/src/library/keiyaku.js +3 -3
  213. package/build/src/library/nuke.js +14 -5
  214. package/build/src/library/reconcile.d.ts +9 -4
  215. package/build/src/library/reconcile.js +30 -24
  216. package/build/src/library/refusal.d.ts +13 -5
  217. package/build/src/library/refusal.js +7 -0
  218. package/build/src/library/region.d.ts +8 -7
  219. package/build/src/library/region.js +31 -11
  220. package/build/src/library/repo.js +2 -2
  221. package/build/src/markdown/lex.js +9 -2
  222. package/build/src/markdown/parse.js +11 -7
  223. package/build/src/markdown/query.js +4 -2
  224. package/build/src/protocol/amend.js +3 -1
  225. package/build/src/protocol/attempt.d.ts +7 -3
  226. package/build/src/protocol/attempt.js +9 -5
  227. package/build/src/protocol/audit.d.ts +1 -1
  228. package/build/src/protocol/audit.js +4 -14
  229. package/build/src/protocol/bind.d.ts +19 -6
  230. package/build/src/protocol/bind.js +28 -13
  231. package/build/src/protocol/completion.d.ts +0 -1
  232. package/build/src/protocol/completion.js +1 -3
  233. package/build/src/protocol/deliver.d.ts +1 -3
  234. package/build/src/protocol/deliver.js +15 -39
  235. package/build/src/protocol/intent.d.ts +1 -0
  236. package/build/src/protocol/intent.js +9 -7
  237. package/build/src/protocol/operations.d.ts +3 -7
  238. package/build/src/protocol/operations.js +5 -3
  239. package/build/src/protocol/placement.d.ts +0 -1
  240. package/build/src/protocol/placement.js +6 -6
  241. package/build/src/protocol/read/observation.d.ts +11 -0
  242. package/build/src/protocol/read/observation.js +37 -0
  243. package/build/src/protocol/read/status.d.ts +23 -12
  244. package/build/src/protocol/read/status.js +90 -72
  245. package/build/src/protocol/reconcile.d.ts +1 -0
  246. package/build/src/protocol/reconcile.js +2 -3
  247. package/build/src/protocol/reintegrate.js +1 -1
  248. package/build/src/protocol/review.d.ts +0 -1
  249. package/build/src/protocol/review.js +15 -22
  250. package/build/src/protocol/run.d.ts +1 -0
  251. package/build/src/protocol/run.js +4 -5
  252. package/build/src/runtime/proc/launch.d.ts +33 -0
  253. package/build/src/runtime/proc/launch.js +98 -0
  254. package/build/src/runtime/proc/line-rpc.js +9 -3
  255. package/build/src/runtime/proc/run.d.ts +12 -6
  256. package/build/src/runtime/proc/run.js +78 -21
  257. package/build/src/runtime/proc/stdio.js +7 -10
  258. package/build/src/settings.js +6 -2
  259. package/build/src/settlement/holder.js +13 -10
  260. package/build/src/settlement/settle.js +39 -6
  261. package/build/src/task/board.js +12 -6
  262. package/build/src/task/compose.js +85 -22
  263. package/build/src/task/context.js +21 -8
  264. package/build/src/task/document.js +46 -10
  265. package/build/src/task/identity.js +5 -2
  266. package/build/src/task/index.d.ts +0 -1
  267. package/build/src/task/index.js +119 -34
  268. package/build/src/task/input.js +29 -7
  269. package/build/src/task/mutation.js +61 -19
  270. package/build/src/task/operations.d.ts +6 -1
  271. package/build/src/task/operations.js +83 -29
  272. package/build/src/task/query.js +55 -26
  273. package/build/src/task/store.d.ts +3 -1
  274. package/build/src/task/store.js +81 -17
  275. package/build/src/verification/execution.js +14 -11
  276. package/build/src/workspace-place.js +19 -7
  277. package/build/src/world.js +16 -6
  278. package/package.json +8 -3
@@ -2,7 +2,7 @@ import { decodeJournal } from "../core/facts/codec.js";
2
2
  import { AuthorityCorruptionError } from "../core/facts/errors.js";
3
3
  import { foldJournal } from "../core/facts/fold.js";
4
4
  import { contractJournalPath, contractJournalPaths, mintContractHead, mintSnapshotId, } from "./identity.js";
5
- import { isKeiyakuOwnedRef, } from "./repository.js";
5
+ import { isKeiyakuOwnedRef } from "./repository.js";
6
6
  import { GitPlumbingError, runGit } from "./process.js";
7
7
  import { readGitTreeSelection, withGitTargetedReadObservation, } from "./read-observation.js";
8
8
  export async function observeDeliveryTargetAt(observation, state) {
@@ -14,9 +14,7 @@ export async function observeDeliveryTargetAt(observation, state) {
14
14
  const integration = state.currentIntegration;
15
15
  if (integration === null)
16
16
  return { head, drift: false };
17
- const expected = state.terminal?.kind === "claimed"
18
- ? integration.snapshot
19
- : integration.predecessor;
17
+ const expected = state.terminal?.kind === "claimed" ? integration.snapshot : integration.predecessor;
20
18
  return { head, drift: head !== expected };
21
19
  }
22
20
  const TARGET_COORDINATES_FORMAT = "%(refname)%00%(objectname)%00";
@@ -43,12 +41,9 @@ export async function normalizeTargetBranch(repository, input) {
43
41
  /** Read one worktree's attached branch, or null for detached HEAD. */
44
42
  export async function currentBranch(repository, path) {
45
43
  try {
46
- const ref = (await runGit(repository, [
47
- ...(path === undefined ? [] : ["-C", path]),
48
- "symbolic-ref",
49
- "--quiet",
50
- "HEAD",
51
- ])).toString("utf8").trim();
44
+ const ref = (await runGit(repository, [...(path === undefined ? [] : ["-C", path]), "symbolic-ref", "--quiet", "HEAD"]))
45
+ .toString("utf8")
46
+ .trim();
52
47
  return ref.startsWith("refs/heads/") ? ref : null;
53
48
  }
54
49
  catch (error) {
@@ -66,39 +61,54 @@ function structuredFields(output, fieldCount) {
66
61
  malformedBindCoordinatesOutput();
67
62
  return fields.slice(0, -1);
68
63
  }
64
+ async function observeHeadCoordinates(repository) {
65
+ try {
66
+ return {
67
+ start: mintSnapshotId((await runGit(repository, ["rev-parse", "--verify", "HEAD"])).toString("utf8").trim()),
68
+ branch: await currentBranch(repository),
69
+ };
70
+ }
71
+ catch (error) {
72
+ if (error instanceof GitPlumbingError &&
73
+ error.status === 128 &&
74
+ error.stderr.toString("utf8").includes("Needed a single revision")) {
75
+ return { kind: "unborn-head", branch: await currentBranch(repository) };
76
+ }
77
+ throw error;
78
+ }
79
+ }
69
80
  /** Observe bind's tender start and optional reward target without creating persistent state. */
70
- export async function observeBindCoordinates(repository, requestedTarget) {
71
- if (requestedTarget === undefined) {
81
+ export async function observeBindCoordinates(repository, selection = { kind: "targetless" }) {
82
+ if (selection.kind === "explicit") {
83
+ const requestedTarget = selection.target;
84
+ if (isKeiyakuOwnedRef(requestedTarget)) {
85
+ throw new Error(`bind target names a Keiyaku-owned ref: ${requestedTarget}`);
86
+ }
87
+ const output = await runGit(repository, [
88
+ "for-each-ref",
89
+ `--format=${TARGET_COORDINATES_FORMAT}`,
90
+ "--",
91
+ requestedTarget,
92
+ ]);
93
+ if (output.length === 0)
94
+ return null;
95
+ const [target, start] = structuredFields(output, 2);
96
+ if (target !== requestedTarget || start === undefined)
97
+ malformedBindCoordinatesOutput();
72
98
  try {
73
- return {
74
- start: mintSnapshotId((await runGit(repository, ["rev-parse", "--verify", "HEAD"])).toString("utf8").trim()),
75
- branch: await currentBranch(repository),
76
- };
99
+ return { target, start: mintSnapshotId(start), branch: await currentBranch(repository) };
77
100
  }
78
101
  catch {
79
102
  malformedBindCoordinatesOutput();
80
103
  }
81
104
  }
82
- if (isKeiyakuOwnedRef(requestedTarget)) {
83
- throw new Error(`bind target names a Keiyaku-owned ref: ${requestedTarget}`);
84
- }
85
- const output = await runGit(repository, [
86
- "for-each-ref",
87
- `--format=${TARGET_COORDINATES_FORMAT}`,
88
- "--",
89
- requestedTarget,
90
- ]);
91
- if (output.length === 0)
92
- return null;
93
- const [target, start] = structuredFields(output, 2);
94
- if (target !== requestedTarget || start === undefined)
95
- malformedBindCoordinatesOutput();
96
- try {
97
- return { target, start: mintSnapshotId(start), branch: await currentBranch(repository) };
98
- }
99
- catch {
100
- malformedBindCoordinatesOutput();
105
+ const observed = await observeHeadCoordinates(repository);
106
+ if ("kind" in observed)
107
+ return observed;
108
+ if (selection.kind === "current-branch" && observed.branch !== null) {
109
+ return { start: observed.start, target: observed.branch, branch: observed.branch };
101
110
  }
111
+ return observed;
102
112
  }
103
113
  function decodeGitJournal(path, git, bytes, expectedId) {
104
114
  const journal = git.paths.get(path);
@@ -108,9 +118,9 @@ function decodeGitJournal(path, git, bytes, expectedId) {
108
118
  throw new AuthorityCorruptionError(`journal path is not a blob: ${path}`);
109
119
  const entries = decodeJournal(bytes.toString("utf8"));
110
120
  const first = entries[0];
111
- if (first === undefined
112
- || (expectedId !== undefined && first.contract !== expectedId)
113
- || entries.some((entry) => entry.contract !== (expectedId ?? first.contract))) {
121
+ if (first === undefined ||
122
+ (expectedId !== undefined && first.contract !== expectedId) ||
123
+ entries.some((entry) => entry.contract !== (expectedId ?? first.contract))) {
114
124
  if (expectedId !== undefined) {
115
125
  throw new AuthorityCorruptionError(`journal content does not canonically identify ${expectedId}: ${path}`);
116
126
  }
@@ -136,8 +146,7 @@ function observeDecodedJournal(id, decoded, path) {
136
146
  };
137
147
  }
138
148
  function observeFromGit(git, id, blobs) {
139
- const matches = contractJournalPaths(id)
140
- .flatMap((path) => git.paths.has(path) ? [path] : []);
149
+ const matches = contractJournalPaths(id).flatMap((path) => (git.paths.has(path) ? [path] : []));
141
150
  if (matches.length > 1)
142
151
  throw new AuthorityCorruptionError(`duplicate contract journal identity: ${id}`);
143
152
  const path = matches[0];
@@ -175,8 +184,7 @@ function enumerateContractObservations(git, blobs, journalClass) {
175
184
  }
176
185
  export async function observeActiveContractWorld(observation) {
177
186
  const git = observation.snapshot;
178
- const journals = [...git.paths]
179
- .filter(([path, entry]) => path.startsWith("contracts/active/") && path.endsWith(".jsonl") && entry.type === "blob");
187
+ const journals = [...git.paths].filter(([path, entry]) => path.startsWith("contracts/active/") && path.endsWith(".jsonl") && entry.type === "blob");
180
188
  const results = await observation.readBlobs(journals.map(([, entry]) => entry.oid));
181
189
  const blobs = new Map();
182
190
  for (const [path, entry] of journals) {
@@ -198,8 +206,7 @@ export async function observeActiveContractWorld(observation) {
198
206
  }
199
207
  export async function observeContractWorld(observation, requested = []) {
200
208
  const git = observation.snapshot;
201
- const journals = [...git.paths]
202
- .filter(([path, entry]) => /^contracts\/(?:active|terminal)\//u.test(path) && path.endsWith(".jsonl") && entry.type === "blob");
209
+ const journals = [...git.paths].filter(([path, entry]) => /^contracts\/(?:active|terminal)\//u.test(path) && path.endsWith(".jsonl") && entry.type === "blob");
203
210
  const results = await observation.readBlobs(journals.map(([, entry]) => entry.oid));
204
211
  const blobs = new Map();
205
212
  for (const [path, entry] of journals) {
@@ -19,7 +19,7 @@ function commandError(command, error) {
19
19
  const candidate = error;
20
20
  const stderr = candidate.stderr === undefined ? Buffer.alloc(0) : Buffer.from(candidate.stderr);
21
21
  const stdout = candidate.stdout === undefined ? Buffer.alloc(0) : Buffer.from(candidate.stdout);
22
- const detail = stderr.length === 0 ? candidate.message ?? "git command failed" : stderr.toString("utf8");
22
+ const detail = stderr.length === 0 ? (candidate.message ?? "git command failed") : stderr.toString("utf8");
23
23
  return new GitPlumbingError({
24
24
  stdout,
25
25
  stderr,
@@ -47,7 +47,9 @@ async function executeGit(repository, args, input, environment) {
47
47
  stderr.push(retained);
48
48
  stderrBytes += retained.length;
49
49
  });
50
- child.stdin.on("error", (error) => { inputError = error; });
50
+ child.stdin.on("error", (error) => {
51
+ inputError = error;
52
+ });
51
53
  child.stdin.end(input);
52
54
  const terminal = await new Promise((resolveTerminal) => {
53
55
  child.once("error", (error) => resolveTerminal({ code: null, error }));
@@ -2,7 +2,7 @@ import { spawn } from "node:child_process";
2
2
  import { AuthorityCorruptionError } from "../core/facts/errors.js";
3
3
  import { gitObjectId } from "./identity.js";
4
4
  import { parseTreeObject, validPath } from "./tree.js";
5
- import { GIT_FORMAT_BYTES, GIT_FORMAT_PATH, GIT_REF, readRef, } from "./repository.js";
5
+ import { GIT_FORMAT_BYTES, GIT_FORMAT_PATH, GIT_REF, readRef } from "./repository.js";
6
6
  import { GitPlumbingError } from "./process.js";
7
7
  const gitReadObservationBrand = Symbol("GitReadObservation");
8
8
  const gitDecodeChannelBrand = Symbol("GitDecodeChannel");
@@ -52,8 +52,12 @@ function batchObjectReader(repository) {
52
52
  let spawnError = null;
53
53
  let failure = null;
54
54
  child.stderr.on("data", (chunk) => stderr.push(Buffer.from(chunk)));
55
- child.on("error", (error) => { spawnError = error; });
56
- child.stdin.on("error", (error) => { spawnError ??= error; });
55
+ child.on("error", (error) => {
56
+ spawnError = error;
57
+ });
58
+ child.stdin.on("error", (error) => {
59
+ spawnError ??= error;
60
+ });
57
61
  const plumbingError = (message, status) => new GitPlumbingError({
58
62
  stderr: Buffer.concat(stderr),
59
63
  status,
@@ -64,7 +68,7 @@ function batchObjectReader(repository) {
64
68
  if (spawnError !== null)
65
69
  throw spawnError;
66
70
  await new Promise((resolve, reject) => {
67
- child.stdin.write(value, (error) => error === null || error === undefined ? resolve() : reject(error));
71
+ child.stdin.write(value, (error) => (error === null || error === undefined ? resolve() : reject(error)));
68
72
  });
69
73
  };
70
74
  const read = async (oid) => {
@@ -252,7 +256,7 @@ async function readFormat(observation) {
252
256
  export async function withGitDecodeChannel(repository, consume) {
253
257
  const transport = { reader: null };
254
258
  let active = true;
255
- const objectReader = () => transport.reader ??= batchObjectReader(repository);
259
+ const objectReader = () => (transport.reader ??= batchObjectReader(repository));
256
260
  const channel = {
257
261
  [gitDecodeChannelBrand]: true,
258
262
  readObjects: (oids) => {
@@ -329,9 +333,7 @@ async function observeEpoch(repository, channel, selection, consume) {
329
333
  if (commitObject === undefined)
330
334
  throw new Error(`missing state commit result: ${commit}`);
331
335
  const tree = await commitTree(commitObject, commit);
332
- const observed = selection === null
333
- ? await completeTree(channel, tree)
334
- : await readGitTreeSelection(channel, tree, selection);
336
+ const observed = selection === null ? await completeTree(channel, tree) : await readGitTreeSelection(channel, tree, selection);
335
337
  snapshot = { commit, tree, paths: observed.paths };
336
338
  treeDirectories = observed.directories;
337
339
  }
@@ -4,11 +4,17 @@ import type { ContractId, ContractState, SnapshotId } from "../core/facts/types.
4
4
  import type { GitDecodeChannel } from "./read-observation.js";
5
5
  import { type TargetCheckoutEffect, type TargetCheckoutLag } from "./target-placement.js";
6
6
  import { type WorktreeHookLag, type WorktreeHooks } from "./hooks.js";
7
- import { type UnsealedBytes } from "./terminal-seal.js";
7
+ import type { UnsealedBytes } from "./terminal-seal.js";
8
8
  export type Effect = Readonly<{
9
9
  kind: "worktree";
10
10
  path: string;
11
11
  action: "created" | "removed" | "unchanged";
12
+ }> | Readonly<{
13
+ kind: "worktree";
14
+ path: string;
15
+ action: "followed";
16
+ before: SnapshotId;
17
+ after: SnapshotId;
12
18
  }> | Readonly<{
13
19
  kind: "recovery-snapshot";
14
20
  action: "created";
@@ -34,12 +40,19 @@ type WorktreeRetained = Readonly<{
34
40
  kind: "worktree-retained";
35
41
  path: string;
36
42
  }>;
43
+ type WorktreeFollowRetained = Readonly<{
44
+ kind: "worktree-follow-retained";
45
+ path: string;
46
+ tender: SnapshotId;
47
+ head: SnapshotId;
48
+ reason: "head-moved" | "head-attached" | "operation-in-progress" | "unsupported-parent-shape";
49
+ }>;
37
50
  export type ReconcileFailure = Readonly<{
38
51
  kind: "reconcile-failed";
39
52
  stage: "observation" | "effect";
40
53
  diagnostic: string;
41
54
  }>;
42
- export type ReconcileLag = WorktreeRetained | UnsealedBytes | TargetCheckoutLag | WorktreeHookLag | ReconcileFailure;
55
+ export type ReconcileLag = WorktreeRetained | WorktreeFollowRetained | UnsealedBytes | TargetCheckoutLag | WorktreeHookLag | ReconcileFailure;
43
56
  export type ReconcileResult = Readonly<{
44
57
  effects: readonly Effect[];
45
58
  lag: readonly ReconcileLag[];
@@ -53,6 +66,13 @@ export type GitReconcileObservation = Readonly<{
53
66
  state: ContractState | null;
54
67
  result: ReconcileResult;
55
68
  }>;
69
+ export type WorktreeTopology = Readonly<{
70
+ paths: Set<string>;
71
+ }>;
72
+ export type ReconcileAccumulation = Readonly<{
73
+ effects: Effect[];
74
+ lag: ReconcileLag[];
75
+ }>;
56
76
  export declare function reconcile(input: ReconcileInput): Promise<GitReconcileObservation>;
57
77
  export declare function reconcileObservationFailure(error: unknown): ReconcileResult;
58
78
  export declare function reconcileEffectFailure(error: unknown, prior?: ReconcileResult): ReconcileResult;
@@ -1,16 +1,16 @@
1
1
  import { access, mkdir } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
- import { acquireSqliteTransactionLock } from "../coordination/sqlite-transaction-lock.js";
3
+ import { acquireSqliteTransactionLock, } from "../coordination/sqlite-transaction-lock.js";
4
4
  import { AuthorityCorruptionError } from "../core/facts/errors.js";
5
- import { CANDIDATE_PIN_REF_NAMESPACE, commonGitDirectory, DELIVERY_REF_NAMESPACE, isKeiyakuOwnedRef, readRef, registeredWorktreePaths, writeCommit, worktreeGitDirectory, } from "./repository.js";
6
- import { GitPlumbingError, runGit } from "./process.js";
7
- import { contractLocator, contractPhysicalName, gitObjectIdForSnapshot, mintSnapshotId, } from "./identity.js";
5
+ import { CANDIDATE_PIN_REF_NAMESPACE, commonGitDirectory, DELIVERY_REF_NAMESPACE, registeredWorktreePaths, worktreeGitDirectory, } from "./repository.js";
6
+ import { runGit } from "./process.js";
7
+ import { contractLocator, contractPhysicalName, gitObjectIdForSnapshot } from "./identity.js";
8
8
  import { observeContractAt } from "./observe.js";
9
9
  import { acquireTargetPlacementFence, recoverTargetPlacement, } from "./target-placement.js";
10
- import { runCreateHooks, runDestroyHooks, } from "./hooks.js";
11
- import { worktreePath } from "./workspace.js";
12
- import { collectableScratchWorktrees } from "./scratch.js";
13
- import { terminalSealExpectations as decodeTerminalSealExpectations, terminalSealSnapshots, observeTerminalWorkspace, } from "./terminal-seal.js";
10
+ import { runCreateHooks } from "./hooks.js";
11
+ import { followManagedWorktree, worktreePath } from "./workspace.js";
12
+ import { removeCollectableScratchWorktrees } from "./scratch.js";
13
+ import { reconcileTerminalManagedWorktree, removeRef, updateRef } from "./terminal-reconcile.js";
14
14
  const pathExists = (path) => access(path).then(() => true, () => false);
15
15
  function deliveryRefFor(contract) {
16
16
  return `${DELIVERY_REF_NAMESPACE}/${contractPhysicalName(contract)}`;
@@ -25,49 +25,9 @@ function missingPlaceLag(repository) {
25
25
  diagnostic: `managed Contract is unappointed: ${join(repository.commonDirectory, "keiyaku", "places.json")}`,
26
26
  };
27
27
  }
28
- async function updateRef(repository, ref, desired) {
29
- const before = await readRef(repository, ref);
30
- if (before === desired)
31
- return { kind: "ref", name: ref, action: "unchanged", before, after: desired };
32
- await runGit(repository, [
33
- "update-ref",
34
- "--no-deref",
35
- ref,
36
- gitObjectIdForSnapshot(desired),
37
- before ?? "0".repeat(desired.length),
38
- ]);
39
- return { kind: "ref", name: ref, action: before === null ? "created" : "updated", before, after: desired };
40
- }
41
- async function removeRef(repository, ref) {
42
- const before = await readRef(repository, ref);
43
- if (before === null)
44
- return { kind: "ref", name: ref, action: "unchanged", before: null, after: null };
45
- await runGit(repository, ["update-ref", "--no-deref", "-d", ref, before]);
46
- return { kind: "ref", name: ref, action: "removed", before, after: null };
47
- }
48
- async function removeRefWithCustody(repository, ref, custodian, expectedCustodian) {
49
- const before = await readRef(repository, ref);
50
- if (before === null)
51
- return { kind: "ref", name: ref, action: "unchanged", before: null, after: null };
52
- if (await readRef(repository, custodian) !== expectedCustodian) {
53
- return { kind: "ref", name: ref, action: "unchanged", before, after: before };
54
- }
55
- await runGit(repository, ["update-ref", "--stdin", "--no-deref"], [
56
- "start",
57
- `verify ${custodian} ${gitObjectIdForSnapshot(expectedCustodian)}`,
58
- `delete ${ref} ${before}`,
59
- "prepare",
60
- "commit",
61
- "",
62
- ].join("\n"));
63
- return { kind: "ref", name: ref, action: "removed", before, after: null };
64
- }
65
28
  async function acquireWorktreeTopology(repository) {
66
29
  return { paths: new Set(await registeredWorktreePaths(repository)) };
67
30
  }
68
- function fromPrimaryWorktree(repository) {
69
- return { ...repository, effectiveCwd: repository.primaryWorktree };
70
- }
71
31
  function diagnostic(error) {
72
32
  return error instanceof Error ? error.message : String(error);
73
33
  }
@@ -85,7 +45,7 @@ function reconcileLockPath(repository, contract) {
85
45
  }
86
46
  async function worktree(repository, topology, path, desired) {
87
47
  const registered = topology.paths.has(path);
88
- if (registered && await pathExists(path))
48
+ if (registered && (await pathExists(path)))
89
49
  return { kind: "worktree", path, action: "unchanged" };
90
50
  if (registered) {
91
51
  await runGit(repository, ["worktree", "remove", path]);
@@ -98,70 +58,12 @@ async function worktree(repository, topology, path, desired) {
98
58
  topology.paths.add(path);
99
59
  return { kind: "worktree", path, action: "created" };
100
60
  }
101
- async function removeWorktree(repository, topology, path, force = false) {
102
- const registered = topology.paths.has(path);
103
- if (!registered) {
104
- return {
105
- effect: { kind: "worktree", path, action: "unchanged" },
106
- retained: await pathExists(path),
107
- };
108
- }
109
- if (!await pathExists(path)) {
110
- await runGit(repository, ["worktree", "remove", path]);
111
- topology.paths.delete(path);
112
- return { effect: { kind: "worktree", path, action: "removed" }, retained: false };
113
- }
114
- try {
115
- await runGit(repository, ["worktree", "remove", ...(force ? ["--force"] : []), path]);
116
- }
117
- catch {
118
- return { effect: { kind: "worktree", path, action: "unchanged" }, retained: true };
119
- }
120
- topology.paths.delete(path);
121
- return { effect: { kind: "worktree", path, action: "removed" }, retained: false };
122
- }
123
61
  async function removeCollectableScratch(repository, topology, effects, lag) {
124
- for (const scratch of await collectableScratchWorktrees(topology.paths)) {
125
- try {
126
- const removal = await removeWorktree(repository, topology, scratch.path, true);
127
- effects.push(removal.effect);
128
- if (removal.retained)
129
- lag.push({ kind: "worktree-retained", path: scratch.path });
130
- }
131
- finally {
132
- scratch.release();
133
- }
134
- }
135
- }
136
- async function refRows(repository) {
137
- const output = (await runGit(repository, [
138
- "for-each-ref",
139
- "--format=%(refname)%00%(objectname)",
140
- ])).toString("utf8");
141
- if (output.length === 0)
142
- return [];
143
- return output.trimEnd().split("\n").map((row) => {
144
- const [ref, oid, extra] = row.split("\0");
145
- if (ref === undefined || oid === undefined || extra !== undefined)
146
- throw new Error("Git ref row is malformed");
147
- return { ref, oid };
148
- });
149
- }
150
- async function snapshotCustodian(repository, snapshot) {
151
- for (const row of await refRows(repository)) {
152
- if (isKeiyakuOwnedRef(row.ref))
153
- continue;
154
- try {
155
- await runGit(repository, ["merge-base", "--is-ancestor", gitObjectIdForSnapshot(snapshot), row.oid]);
156
- return { ref: row.ref, oid: mintSnapshotId(row.oid) };
157
- }
158
- catch (error) {
159
- if (error instanceof GitPlumbingError && error.status === 1)
160
- continue;
161
- throw error;
162
- }
62
+ for (const removal of await removeCollectableScratchWorktrees(repository, topology.paths)) {
63
+ effects.push({ kind: "worktree", path: removal.path, action: removal.action });
64
+ if (removal.retained)
65
+ lag.push({ kind: "worktree-retained", path: removal.path });
163
66
  }
164
- return null;
165
67
  }
166
68
  async function reconcileTargetCheckouts(repository, state) {
167
69
  if (state.terminal?.kind !== "claimed" || state.coordinates.target === undefined || state.delivery === null) {
@@ -201,171 +103,34 @@ async function reconcileTargetCheckouts(repository, state) {
201
103
  result = failed("effect", releaseFailure, result.effects, result.lag);
202
104
  return result;
203
105
  }
204
- async function reconcileHereWorkspaceRefs({ repository }, state, { effects, lag }) {
205
- const pin = candidatePinRefFor(state.id);
206
- if (state.terminal) {
207
- if (state.delivery === null)
208
- effects.push(await removeRef(repository, pin));
209
- else {
210
- const integration = state.currentIntegration?.snapshot;
211
- if (integration === undefined)
212
- return complete();
213
- const custodian = await snapshotCustodian(repository, integration);
214
- effects.push(await (custodian === null
215
- ? await updateRef(repository, pin, integration)
216
- : await removeRefWithCustody(repository, pin, custodian.ref, custodian.oid)));
217
- }
218
- }
219
- else if (state.bound) {
220
- effects.push(await (state.delivery === null
221
- ? await removeRef(repository, pin)
222
- : await updateRef(repository, pin, state.currentIntegration?.snapshot ?? state.delivery.data.integration.snapshot)));
223
- }
224
- return complete(effects, lag);
225
- }
226
- function retainTerminalWorktree(path, retainedLag, { effects, lag }) {
227
- effects.push({ kind: "worktree", path, action: "unchanged" });
228
- lag.push(retainedLag);
229
- return complete(effects, lag);
230
- }
231
- async function terminalSealExpectations(channel, state) {
232
- const snapshots = terminalSealSnapshots(state);
233
- const objects = await channel.readObjects(snapshots.map(gitObjectIdForSnapshot));
234
- return decodeTerminalSealExpectations(state, objects);
235
- }
236
- async function removeSealedTerminalWorktree({ repository, topology, path, state, expected, hooks, retryHooks, acc }) {
237
- if (topology.paths.has(path) && await pathExists(path)) {
238
- const terminal = state.terminal;
239
- if (terminal === null)
240
- throw new Error(`terminal worktree cleanup received active Contract ${state.id}`);
241
- const canRecover = terminal.kind === "abandoned";
242
- let recovery = null;
243
- const recordRecovery = async (workspace) => {
244
- const snapshot = mintSnapshotId(await writeCommit({
245
- repository,
246
- tree: workspace.tree,
247
- parent: gitObjectIdForSnapshot(recovery?.snapshot ?? workspace.head),
248
- message: `${state.id}: ephemeral abandoned-workspace recovery\n\nKeiyaku-Contract: ${state.id}`,
249
- actor: "Keiyaku Recovery",
250
- at: terminal.at,
251
- }));
252
- const effect = {
253
- kind: "recovery-snapshot",
254
- action: "created",
255
- snapshot,
256
- retention: "ephemeral",
257
- };
258
- const existing = acc.effects.findIndex((item) => item.kind === "recovery-snapshot");
259
- if (existing < 0)
260
- acc.effects.push(effect);
261
- else
262
- acc.effects[existing] = effect;
263
- return { snapshot, workspace };
264
- };
265
- const before = await observeTerminalWorkspace(repository, path, expected);
266
- const beforeWorkspace = before.workspace;
267
- const beforeHooks = before.unsealed;
268
- if (beforeHooks !== null) {
269
- if (!canRecover || beforeWorkspace.submodules.length > 0) {
270
- return retainTerminalWorktree(path, beforeHooks, acc);
271
- }
272
- recovery = await recordRecovery(beforeWorkspace);
273
- }
274
- const hookLag = await runDestroyHooks(path, await worktreeGitDirectory(repository, path), hooks, retryHooks);
275
- if (hookLag !== null)
276
- return retainTerminalWorktree(path, hookLag, acc);
277
- const after = await observeTerminalWorkspace(repository, path, expected);
278
- const afterWorkspace = after.workspace;
279
- const afterHooks = after.unsealed;
280
- if (afterHooks !== null) {
281
- if (!canRecover || afterWorkspace.submodules.length > 0) {
282
- return retainTerminalWorktree(path, afterHooks, acc);
283
- }
284
- }
285
- if (canRecover
286
- && (recovery === null
287
- || recovery.workspace.head !== afterWorkspace.head
288
- || recovery.workspace.tree !== afterWorkspace.tree)
289
- && (recovery !== null || beforeWorkspace.head !== afterWorkspace.head || beforeWorkspace.tree !== afterWorkspace.tree)) {
290
- recovery = await recordRecovery(afterWorkspace);
291
- }
292
- }
293
- const removal = await removeWorktree(repository, topology, path, true);
294
- if (removal.retained)
295
- return retainTerminalWorktree(path, { kind: "worktree-retained", path }, acc);
296
- acc.effects.push(removal.effect);
297
- return null;
298
- }
299
- async function targetCustodyForClaimedIntegration(repository, state, integration) {
300
- return state.terminal?.kind === "claimed" && state.coordinates.target !== undefined
301
- && await readRef(repository, state.coordinates.target) === integration
302
- ? { ref: state.coordinates.target, oid: integration }
303
- : null;
304
- }
305
- function sealedTree(expected, snapshot) {
306
- const tree = expected.treeBySnapshot.get(snapshot);
307
- if (tree === undefined)
308
- throw new Error(`terminal seal tree was not resolved: ${snapshot}`);
309
- return tree;
310
- }
311
- async function releaseTerminalCustody({ repository, state, expected, ref, pin, acc: { effects } }) {
312
- if (state.delivery === null) {
313
- const custodian = await snapshotCustodian(repository, state.coordinates.start);
314
- if (custodian !== null)
315
- effects.push(await removeRefWithCustody(repository, ref, custodian.ref, custodian.oid));
316
- effects.push(await removeRef(repository, pin));
317
- return;
318
- }
319
- const tender = state.delivery.data.tenderSnapshot;
320
- const integration = state.currentIntegration?.snapshot ?? state.delivery.data.integration.snapshot;
321
- const target = await targetCustodyForClaimedIntegration(repository, state, integration);
322
- if (sealedTree(expected, tender) === sealedTree(expected, integration) && target !== null) {
323
- effects.push(await removeRefWithCustody(repository, ref, target.ref, target.oid));
324
- effects.push(await removeRefWithCustody(repository, pin, target.ref, target.oid));
325
- }
326
- else if (tender === integration) {
327
- effects.push(await removeRefWithCustody(repository, pin, ref, tender));
328
- }
329
- else if (target !== null) {
330
- effects.push(await removeRefWithCustody(repository, pin, target.ref, target.oid));
331
- }
332
- }
333
- async function reconcileTerminalManagedWorktree({ repository, channel, hooks, retryHooks, retainTerminalWorktree, place }, state, topology, acc) {
334
- const primary = fromPrimaryWorktree(repository);
335
- const ref = deliveryRefFor(state.id);
336
- const pin = candidatePinRefFor(state.id);
337
- const path = place === undefined ? undefined : worktreePath(repository, place);
338
- if (path === undefined)
339
- return complete(acc.effects, acc.lag);
340
- const expected = await terminalSealExpectations(channel, state);
341
- acc.effects.push(await updateRef(primary, ref, state.delivery?.data.tenderSnapshot ?? state.coordinates.start));
342
- if (state.delivery !== null) {
343
- acc.effects.push(await updateRef(primary, pin, state.currentIntegration?.snapshot ?? state.delivery.data.integration.snapshot));
344
- }
345
- if (retainTerminalWorktree === true)
346
- return complete(acc.effects, acc.lag);
347
- const retained = await removeSealedTerminalWorktree({
348
- repository: primary,
349
- topology,
350
- path,
351
- state,
352
- expected,
353
- hooks,
354
- retryHooks,
355
- acc,
356
- });
357
- if (retained !== null)
358
- return retained;
359
- await releaseTerminalCustody({ repository: primary, state, expected, ref, pin, acc });
360
- return complete(acc.effects, acc.lag);
361
- }
362
106
  async function reconcileActiveManagedWorktree({ repository, hooks, retryHooks, place }, state, topology, { effects, lag }) {
363
107
  if (place === undefined)
364
108
  return complete(effects, [...lag, missingPlaceLag(repository)]);
365
109
  const path = worktreePath(repository, place);
366
110
  const desired = state.delivery?.data.tenderSnapshot ?? state.coordinates.start;
367
111
  effects.push(await updateRef(repository, deliveryRefFor(state.id), desired));
368
- effects.push(await worktree(repository, topology, path, desired));
112
+ const projection = await worktree(repository, topology, path, desired);
113
+ if (projection.kind === "worktree" && projection.action === "unchanged" && state.delivery !== null) {
114
+ const follow = await followManagedWorktree(repository, path, state.delivery.data.tenderSnapshot);
115
+ if (follow.kind === "followed") {
116
+ effects.push({ kind: "worktree", path, action: "followed", before: follow.before, after: follow.after });
117
+ }
118
+ else {
119
+ effects.push(projection);
120
+ if (follow.kind === "retained") {
121
+ lag.push({
122
+ kind: "worktree-follow-retained",
123
+ path,
124
+ tender: state.delivery.data.tenderSnapshot,
125
+ head: follow.head,
126
+ reason: follow.reason,
127
+ });
128
+ }
129
+ }
130
+ }
131
+ else {
132
+ effects.push(projection);
133
+ }
369
134
  const hookLag = await runCreateHooks(path, await worktreeGitDirectory(repository, path), hooks, retryHooks);
370
135
  if (hookLag !== null)
371
136
  lag.push(hookLag);
@@ -385,10 +150,9 @@ async function reconcileWithTopology(input, state, topology) {
385
150
  const targetCheckouts = await reconcileTargetCheckouts(repository, state);
386
151
  effects.push(...targetCheckouts.effects);
387
152
  lag.push(...targetCheckouts.lag);
388
- if (state.coordinates.workspace === "here")
389
- return await reconcileHereWorkspaceRefs(input, state, { effects, lag });
390
- if (state.terminal)
391
- return await reconcileTerminalManagedWorktree(input, state, topology, { effects, lag });
153
+ if (state.terminal) {
154
+ return await reconcileTerminalManagedWorktree(input, state, topology, { effects, lag }, { ref: deliveryRefFor(state.id), pin: candidatePinRefFor(state.id) });
155
+ }
392
156
  return await reconcileActiveManagedWorktree(input, state, topology, { effects, lag });
393
157
  }
394
158
  catch (error) {
@@ -4,6 +4,8 @@ export type { TreeChange } from "./tree.js";
4
4
  export declare const GIT_REF = "refs/heads/keiyaku-state";
5
5
  export declare const DELIVERY_REF_NAMESPACE = "refs/heads/keiyaku-delivery";
6
6
  export declare const CANDIDATE_PIN_REF_NAMESPACE = "refs/heads/keiyaku-candidate";
7
+ export declare const MIGRATION_DELIVERY_REF_NAMESPACE = "refs/keiyaku/delivery";
8
+ export declare const MIGRATION_CANDIDATE_PIN_REF_NAMESPACE = "refs/keiyaku/candidate";
7
9
  export declare const GIT_FORMAT_PATH = "meta/format.json";
8
10
  export declare const GIT_FORMAT_BYTES = "{\"version\":4}\n";
9
11
  export declare function isKeiyakuOwnedRef(ref: string): boolean;