@astrosheep/keiyaku 2.8.4 → 2.9.0

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 (229) hide show
  1. package/README.md +35 -15
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/{call-terms_v2.js → call-terms.js} +12 -8
  4. package/build/agents/harness/control-types.js +1 -0
  5. package/build/agents/harness/execution-handle.js +23 -3
  6. package/build/agents/harness/index.js +6 -607
  7. package/build/agents/harness/outcome.js +141 -0
  8. package/build/agents/harness/projection.js +310 -0
  9. package/build/agents/harness/router.js +201 -0
  10. package/build/agents/harness/runtime.js +2 -1
  11. package/build/agents/{launch-snapshot_v2.js → launch-snapshot/model.js} +69 -74
  12. package/build/agents/launch-snapshot/resolve.js +72 -0
  13. package/build/agents/opencode-sdk.js +24 -3
  14. package/build/agents/providers/{claude-agent-sdk.js → claude-agent-sdk/adapter.js} +46 -368
  15. package/build/agents/providers/claude-agent-sdk/events.js +218 -0
  16. package/build/agents/providers/claude-agent-sdk/session.js +158 -0
  17. package/build/agents/providers/codex-app-server/adapter.js +347 -0
  18. package/build/agents/providers/codex-app-server/events.js +352 -0
  19. package/build/agents/providers/codex-app-server/session.js +266 -0
  20. package/build/agents/providers/codex-sdk.js +23 -5
  21. package/build/agents/providers/opencode-sdk/adapter.js +344 -0
  22. package/build/agents/providers/opencode-sdk/events.js +354 -0
  23. package/build/agents/providers/opencode-sdk/session.js +180 -0
  24. package/build/agents/providers/pi/adapter.js +269 -0
  25. package/build/agents/providers/pi/checkpoint.js +41 -0
  26. package/build/agents/providers/pi/events.js +282 -0
  27. package/build/agents/{selector_v2.js → selector.js} +8 -6
  28. package/build/cli/commands/akuma/akuma/handler.js +4 -0
  29. package/build/cli/commands/akuma/akuma/meta.js +16 -0
  30. package/build/cli/commands/akuma/catalog.js +8 -0
  31. package/build/cli/commands/akuma/list/handler.js +15 -0
  32. package/build/cli/commands/akuma/list/meta-ls.js +9 -0
  33. package/build/cli/commands/akuma/view/handler.js +34 -0
  34. package/build/cli/commands/akuma/view/meta.js +8 -0
  35. package/build/cli/commands/akuma.js +1 -1
  36. package/build/cli/commands/contract/amend/handler.js +10 -0
  37. package/build/cli/commands/contract/amend/meta.js +12 -0
  38. package/build/cli/commands/contract/arc/handler.js +9 -0
  39. package/build/cli/commands/contract/arc/meta.js +26 -0
  40. package/build/cli/commands/contract/bind/handler.js +23 -0
  41. package/build/cli/commands/contract/bind/meta.js +30 -0
  42. package/build/cli/commands/contract/catalog.js +16 -0
  43. package/build/cli/commands/contract/forfeit/handler.js +13 -0
  44. package/build/cli/commands/contract/forfeit/meta.js +12 -0
  45. package/build/cli/commands/contract/log/handler.js +12 -0
  46. package/build/cli/commands/contract/log/meta.js +12 -0
  47. package/build/cli/commands/contract/petition/handler.js +26 -0
  48. package/build/cli/commands/contract/petition/meta.js +23 -0
  49. package/build/cli/commands/contract/renew/handler.js +10 -0
  50. package/build/cli/commands/contract/renew/meta.js +17 -0
  51. package/build/cli/commands/projection/call/handler.js +39 -0
  52. package/build/cli/commands/projection/call/meta.js +9 -0
  53. package/build/cli/commands/projection/catalog.js +14 -0
  54. package/build/cli/commands/projection/kill/handler.js +35 -0
  55. package/build/cli/commands/projection/kill/meta.js +11 -0
  56. package/build/cli/commands/projection/revive/handler.js +53 -0
  57. package/build/cli/commands/projection/revive/meta.js +9 -0
  58. package/build/cli/commands/projection/status/handler.js +23 -0
  59. package/build/cli/commands/projection/status/meta.js +9 -0
  60. package/build/cli/commands/projection/tell/handler.js +34 -0
  61. package/build/cli/commands/projection/tell/meta.js +9 -0
  62. package/build/cli/commands/projection/wait/handler.js +21 -0
  63. package/build/cli/commands/projection/wait/meta.js +11 -0
  64. package/build/cli/commands/registry.js +100 -0
  65. package/build/cli/commands/shared.js +71 -0
  66. package/build/cli/commands/skills/catalog.js +6 -0
  67. package/build/cli/commands/skills/install/handler.js +10 -0
  68. package/build/cli/commands/skills/install/meta.js +9 -0
  69. package/build/cli/commands/skills/skills/handler.js +4 -0
  70. package/build/cli/commands/skills/skills/meta.js +10 -0
  71. package/build/cli/commands/system/catalog.js +8 -0
  72. package/build/cli/commands/system/completion/handler.js +10 -0
  73. package/build/cli/commands/system/completion/meta.js +9 -0
  74. package/build/cli/commands/system/dump-env/handler.js +3 -0
  75. package/build/cli/commands/system/dump-env/meta.js +10 -0
  76. package/build/cli/commands/system/guide/handler.js +4 -0
  77. package/build/cli/commands/system/guide/meta.js +9 -0
  78. package/build/cli/commands/task/add/handler.js +21 -0
  79. package/build/cli/commands/task/add/meta.js +8 -0
  80. package/build/cli/commands/task/catalog.js +22 -0
  81. package/build/cli/commands/task/doctor/handler.js +4 -0
  82. package/build/cli/commands/task/doctor/meta.js +8 -0
  83. package/build/cli/commands/task/done/handler.js +17 -0
  84. package/build/cli/commands/task/done/meta.js +8 -0
  85. package/build/cli/commands/task/drop/handler.js +6 -0
  86. package/build/cli/commands/task/drop/meta.js +8 -0
  87. package/build/cli/commands/task/ls/handler.js +13 -0
  88. package/build/cli/commands/task/ls/meta.js +8 -0
  89. package/build/cli/commands/task/shared.js +131 -0
  90. package/build/cli/commands/task/start/handler.js +6 -0
  91. package/build/cli/commands/task/start/meta.js +8 -0
  92. package/build/cli/commands/task/stop/handler.js +17 -0
  93. package/build/cli/commands/task/stop/meta.js +8 -0
  94. package/build/cli/commands/task/task/handler.js +5 -0
  95. package/build/cli/commands/task/task/meta.js +11 -0
  96. package/build/cli/commands/task/update/handler.js +18 -0
  97. package/build/cli/commands/task/update/meta.js +8 -0
  98. package/build/cli/commands/task/view/handler.js +14 -0
  99. package/build/cli/commands/task/view/meta.js +8 -0
  100. package/build/cli/completion.js +26 -27
  101. package/build/cli/flags.js +79 -2
  102. package/build/cli/help.js +27 -49
  103. package/build/cli/index.js +121 -374
  104. package/build/cli/output-stream-error-policy.js +17 -0
  105. package/build/cli/parse.js +159 -72
  106. package/build/cli/projection-address.js +5 -0
  107. package/build/cli/render/arc.js +1 -1
  108. package/build/cli/render/errors.js +4 -3
  109. package/build/cli/render/format.js +14 -0
  110. package/build/cli/render/kanshi.js +106 -0
  111. package/build/cli/render/petition.js +24 -1
  112. package/build/cli/render/projection-activity.js +5 -1
  113. package/build/cli/render/shared.js +2 -2
  114. package/build/cli/render/status.js +79 -13
  115. package/build/cli/render/task.js +113 -0
  116. package/build/cli/render/wait.js +23 -3
  117. package/build/cli/types.js +1 -1
  118. package/build/config/{akuma-loader_v2.js → akuma-loader.js} +2 -2
  119. package/build/config/env.js +9 -4
  120. package/build/config/settings/disease.js +26 -0
  121. package/build/config/settings/knobs.js +8 -0
  122. package/build/config/settings/loader.js +244 -0
  123. package/build/config/settings/schema.js +276 -0
  124. package/build/core/addressing.js +1 -1
  125. package/build/core/amend.js +3 -3
  126. package/build/core/arc.js +5 -4
  127. package/build/core/bind-workspace.js +123 -0
  128. package/build/core/bind.js +75 -155
  129. package/build/core/call/call.js +224 -0
  130. package/build/core/call/context.js +238 -0
  131. package/build/core/call/execution.js +217 -0
  132. package/build/core/call/prompt.js +42 -0
  133. package/build/core/call/reference.js +24 -0
  134. package/build/core/call-persist.js +1 -1
  135. package/build/core/claim-delivery.js +247 -0
  136. package/build/core/claim.js +39 -277
  137. package/build/core/context.js +2 -1
  138. package/build/core/{render.js → contract-view.js} +12 -140
  139. package/build/core/draft.js +107 -9
  140. package/build/core/entry.js +9 -1
  141. package/build/core/execution-coordinate.js +4 -20
  142. package/build/core/execution-pact.js +16 -31
  143. package/build/core/forfeit.js +35 -22
  144. package/build/core/hints.js +2 -2
  145. package/build/core/log.js +2 -2
  146. package/build/core/petition-claim-gates.js +8 -6
  147. package/build/core/petition-preview.js +97 -0
  148. package/build/core/petition-run.js +3 -2
  149. package/build/core/petition.js +39 -26
  150. package/build/core/projection/generation/database.js +306 -0
  151. package/build/core/projection/generation/identity.js +84 -0
  152. package/build/core/projection/generation/model.js +201 -0
  153. package/build/core/projection/generation/store.js +73 -0
  154. package/build/core/projection/generation/transitions.js +258 -0
  155. package/build/core/projection/heart.js +0 -307
  156. package/build/core/projection/tell/database.js +109 -0
  157. package/build/core/projection/tell/model.js +97 -0
  158. package/build/core/{projection-tells.js → projection/tell/store.js} +3 -158
  159. package/build/core/projection-activity.js +23 -2
  160. package/build/core/projection-core.js +2 -1
  161. package/build/core/projection-execution-observer.js +259 -0
  162. package/build/core/projection-generation-continuation.js +1 -1
  163. package/build/core/projection-generation-execution.js +90 -20
  164. package/build/core/projection-generation-launcher.js +1 -1
  165. package/build/core/projection-generation-runner.js +198 -4
  166. package/build/core/projection-identity.js +1 -1
  167. package/build/core/projection-kill.js +50 -17
  168. package/build/core/projection-life-observer.js +1 -1
  169. package/build/core/projection-life-protocol.js +10 -0
  170. package/build/core/projection-mint.js +1 -1
  171. package/build/core/projection-status.js +45 -32
  172. package/build/core/projection-wait.js +12 -2
  173. package/build/core/projection-wake.js +5 -2
  174. package/build/core/{queue_v2.js → queue.js} +2 -1
  175. package/build/core/registry.js +1 -1
  176. package/build/core/renew.js +7 -7
  177. package/build/core/response-artifact-catalog-sqlite.js +112 -0
  178. package/build/core/response-artifact-catalog.js +7 -0
  179. package/build/core/seal.js +44 -16
  180. package/build/core/status/board.js +286 -0
  181. package/build/core/status/drift.js +99 -0
  182. package/build/core/status/lifecycle.js +84 -0
  183. package/build/core/status/reconciliation.js +131 -0
  184. package/build/core/stored-agent-event.js +28 -4
  185. package/build/core/target-ref.js +10 -0
  186. package/build/core/task/board.js +175 -0
  187. package/build/core/task/commands.js +211 -0
  188. package/build/core/task/document.js +189 -0
  189. package/build/core/task/model.js +1 -0
  190. package/build/core/task/settlement-git.js +207 -0
  191. package/build/core/task/settlement-policy.js +82 -0
  192. package/build/core/task/source-board.js +84 -0
  193. package/build/core/task/source-model.js +1 -0
  194. package/build/core/task/validation.js +44 -0
  195. package/build/core/task-contract.js +224 -0
  196. package/build/core/task-doctor.js +14 -0
  197. package/build/core/task-git-actor.js +38 -0
  198. package/build/core/task-git-runtime.js +34 -0
  199. package/build/core/task-git-store.js +375 -0
  200. package/build/core/task-repository.js +1 -0
  201. package/build/core/task-store-repository.js +86 -0
  202. package/build/core/task-store.js +1 -0
  203. package/build/core/task-worktree.js +51 -0
  204. package/build/core/task.js +4 -0
  205. package/build/core/transcripts.js +138 -56
  206. package/build/core/validation.js +17 -0
  207. package/build/core/verdict.js +2 -2
  208. package/build/core/worktree-path.js +24 -6
  209. package/build/flow-error.js +3 -2
  210. package/build/generated/version.js +1 -1
  211. package/build/git/commits.js +3 -0
  212. package/build/git/diff/parsers.js +0 -94
  213. package/build/index.js +2 -0
  214. package/build/keiyaku-constants.js +2 -0
  215. package/build/keiyaku.js +4 -3
  216. package/package.json +3 -2
  217. package/skills/keiyaku/SKILL.md +1 -1
  218. package/skills/keiyaku-akuma/SKILL.md +11 -6
  219. package/build/agents/harness/pump.js +0 -158
  220. package/build/agents/providers/codex-app-server.js +0 -864
  221. package/build/agents/providers/opencode-sdk.js +0 -363
  222. package/build/agents/providers/pi.js +0 -557
  223. package/build/config/settings.js +0 -533
  224. package/build/core/call.js +0 -686
  225. package/build/core/projection/generation.js +0 -412
  226. package/build/core/projection/identity.js +0 -81
  227. package/build/core/projection/mint.js +0 -174
  228. package/build/core/projection-generation-store.js +0 -707
  229. package/build/core/status.js +0 -539
@@ -1,10 +1,8 @@
1
- import { deriveContractState, } from "./status.js";
1
+ import { shortCommitHash } from "../git/commits.js";
2
+ import { deriveContractState } from "./status/lifecycle.js";
2
3
  const VERB_COLUMN_WIDTH = 11;
3
4
  const TIME_COLUMN_WIDTH = 6;
4
5
  const ACTOR_COLUMN_WIDTH = 11;
5
- export function shortCommitHash(hash) {
6
- return hash.slice(0, 12);
7
- }
8
6
  function compactWhitespace(value) {
9
7
  return value.replace(/\s+/g, " ").trim();
10
8
  }
@@ -245,144 +243,18 @@ export function renderContractView(input) {
245
243
  "scope:",
246
244
  ...bind.data.scope.map((pattern) => `- ${pattern}`),
247
245
  "",
246
+ "checks:",
247
+ ...bind.data.checks.map((check) => `- ${check}`),
248
+ "",
249
+ ...(bind.data.extensions.length > 0
250
+ ? [
251
+ ...bind.data.extensions.flatMap((extension) => extension.content.length > 0
252
+ ? [`## ${extension.title}`, extension.content, ""]
253
+ : [`## ${extension.title}`, ""]),
254
+ ]
255
+ : []),
248
256
  "log:",
249
257
  renderVerticalLog(input),
250
258
  "",
251
259
  ].join("\n");
252
260
  }
253
- function shortHead(hash) {
254
- if (!hash)
255
- return "????????";
256
- return hash.slice(0, 7);
257
- }
258
- export function formatAgeMs(ms) {
259
- if (ms === undefined)
260
- return undefined;
261
- const seconds = Math.max(0, Math.floor(ms / 1_000));
262
- if (seconds < 60)
263
- return `${seconds}s`;
264
- const minutes = Math.floor(seconds / 60);
265
- if (minutes < 60)
266
- return `${minutes}m`;
267
- const hours = Math.floor(minutes / 60);
268
- if (hours < 24)
269
- return `${hours}h`;
270
- return `${Math.floor(hours / 24)}d`;
271
- }
272
- function arcSparkline(arcs) {
273
- if (arcs <= 0)
274
- return "";
275
- return Array.from({ length: arcs }, () => "·").join("──");
276
- }
277
- function pendingAfterIds(contract) {
278
- return contract.after.filter((item) => !item.terminal).map((item) => item.id);
279
- }
280
- function kanshiGlyph(contract) {
281
- if (contract.state.state === "corrupt")
282
- return "×";
283
- if (pendingAfterIds(contract).length > 0)
284
- return "○";
285
- if (contract.state.state === "petitioned")
286
- return "◎";
287
- return "●";
288
- }
289
- export function projectionGlyph(state) {
290
- switch (state) {
291
- case "out":
292
- return "●";
293
- case "lost":
294
- return "○";
295
- case "done":
296
- return "◎";
297
- case "dismissed":
298
- return "×";
299
- case "dead":
300
- return "✕";
301
- case "minting":
302
- return "…";
303
- case "startup-timeout":
304
- return "!";
305
- case "unknown":
306
- return "?";
307
- }
308
- }
309
- function renderDriftMark(drift) {
310
- if (drift.state === "drift")
311
- return `▲${drift.behindBy}`;
312
- if (drift.state === "orphaned")
313
- return "▲orphaned";
314
- return undefined;
315
- }
316
- export function renderKanshiBoard(status) {
317
- const head = shortHead(status.defaultHead);
318
- if (status.contracts.length === 0) {
319
- if (status.targetFilter !== null) {
320
- return `現世 ${head} · target ${status.targetFilter}\nno commissions on target ${status.targetFilter}\n`;
321
- }
322
- return `現世 ${head} 无在飞契约\n`;
323
- }
324
- const header = status.targetFilter !== null
325
- ? `現世 ${head} · target ${status.targetFilter}`
326
- : `現世 ${head}`;
327
- const lines = [header, ""];
328
- for (const contract of status.contracts) {
329
- const glyph = kanshiGlyph(contract);
330
- const name = contract.slug;
331
- const spark = arcSparkline(contract.arcs);
332
- const facts = [];
333
- const lifecycle = contract.state.state;
334
- if (lifecycle === "bound" || lifecycle === "active" || lifecycle === "petitioned") {
335
- facts.push(lifecycle);
336
- }
337
- if (contract.place) {
338
- facts.push(`@${contract.place}`);
339
- }
340
- const arcObjective = compactWhitespace(contract.currentArc.objective || "(no arc objective)");
341
- const arcMark = contract.currentArc.state === "open"
342
- ? `a${contract.currentArc.number}+`
343
- : `a${contract.currentArc.number}`;
344
- facts.push(`${arcMark} ${arcObjective}`);
345
- facts.push(`next ${contract.nextStep.detail}`);
346
- const age = formatAgeMs(contract.lastActiveMs);
347
- if (age)
348
- facts.push(age);
349
- if (contract.queuePosition)
350
- facts.push(`Q${contract.queuePosition}`);
351
- const drift = renderDriftMark(contract.baseDrift);
352
- if (drift)
353
- facts.push(drift);
354
- if (contract.nextStep.hint)
355
- facts.push(contract.nextStep.hint);
356
- const pending = pendingAfterIds(contract);
357
- if (pending.length > 0) {
358
- facts.push(`⧗ ${pending.map((id) => {
359
- const dot = id.indexOf(".");
360
- return dot > 0 ? id.slice(0, dot) : id;
361
- }).join(",")}`);
362
- }
363
- if (contract.state.state === "corrupt") {
364
- facts.push(`corrupt (${contract.state.reason ?? contract.corruptReason ?? "unknown"})`);
365
- }
366
- if (contract.queueHeadStalled) {
367
- facts.push("朱 stall 去广场");
368
- }
369
- const sparkCol = spark.padEnd(14);
370
- const factText = facts.join(" · ");
371
- lines.push(`${glyph} ${name.padEnd(18)} ${sparkCol} ${factText}`.trimEnd());
372
- if (contract.bootstrapDiagnostics.some((diagnostic) => diagnostic.kind === "plan-without-result")) {
373
- lines.push(" ! bootstrap · no result recorded · forfeit and re-bind");
374
- }
375
- for (const diagnostic of contract.queueDiagnostics) {
376
- if (diagnostic.kind === "legacy-petition") {
377
- lines.push(" ! legacy petition · no seat · reject then re-petition");
378
- continue;
379
- }
380
- if (diagnostic.kind === "duplicate-seat") {
381
- lines.push(` ! seat ${diagnostic.seat} duplicated with ${diagnostic.with} · ordered by contract id`);
382
- continue;
383
- }
384
- lines.push(` ! target mismatch · petitioned ${diagnostic.petitioned} · derived ${diagnostic.derived}`);
385
- }
386
- }
387
- return `${lines.join("\n")}\n`;
388
- }
@@ -1,14 +1,34 @@
1
1
  import { FlowError } from "../flow-error.js";
2
2
  import { extractListItems, parseToAST, renderSectionContent } from "./markdown/parser.js";
3
3
  import { normalizeScopePattern } from "./scope.js";
4
- const KNOWN_SECTIONS = new Set(["objective", "scope"]);
4
+ const KNOWN_SECTIONS = new Set(["objective", "scope", "checks"]);
5
5
  function invalidBindDraft(message) {
6
6
  throw new FlowError("INVALID_KEIYAKU_DRAFT", `INVALID_BIND_DRAFT: ${message}`);
7
7
  }
8
8
  function normalizeSectionName(name) {
9
9
  return name.trim().toLowerCase();
10
10
  }
11
- function findUnexpectedScopeLine(section) {
11
+ /**
12
+ * Canonicalize extension Markdown by dropping only leading/trailing blank
13
+ * physical lines. Retained lines keep every byte (indentation, trailing
14
+ * spaces, fences, internal blanks). Entirely blank input becomes "".
15
+ */
16
+ function normalizeExtensionContent(content) {
17
+ const lines = content.split(/\r?\n/);
18
+ let start = 0;
19
+ while (start < lines.length && lines[start].trim().length === 0) {
20
+ start += 1;
21
+ }
22
+ let end = lines.length;
23
+ while (end > start && lines[end - 1].trim().length === 0) {
24
+ end -= 1;
25
+ }
26
+ if (start >= end) {
27
+ return "";
28
+ }
29
+ return lines.slice(start, end).join("\n");
30
+ }
31
+ function findUnexpectedListOnlyLine(section) {
12
32
  for (const child of section.children) {
13
33
  if (child.type === "list")
14
34
  continue;
@@ -27,40 +47,78 @@ function findUnexpectedScopeLine(section) {
27
47
  }
28
48
  return null;
29
49
  }
50
+ function extractSingleLineChecks(section) {
51
+ const unexpected = findUnexpectedListOnlyLine(section);
52
+ if (unexpected) {
53
+ invalidBindDraft(`Checks must be a bullet list; found '${unexpected}'`);
54
+ }
55
+ const items = extractListItems(section);
56
+ if (items.length === 0) {
57
+ const hasListNode = section.children.some((child) => child.type === "list");
58
+ if (hasListNode) {
59
+ invalidBindDraft("empty check in bind draft");
60
+ }
61
+ invalidBindDraft("Checks must be a bullet list; found empty Checks section");
62
+ }
63
+ return items.map((item) => {
64
+ const trimmed = item.trim();
65
+ if (trimmed.length === 0) {
66
+ invalidBindDraft("empty check in bind draft");
67
+ }
68
+ if (item.includes("\n") || item.includes("\r")) {
69
+ invalidBindDraft("check items must be a single line");
70
+ }
71
+ return trimmed;
72
+ });
73
+ }
30
74
  export function parseBindDraft(content) {
31
75
  const ast = parseToAST(content);
32
76
  let name;
77
+ let seenH1 = false;
33
78
  const sections = new Map();
79
+ const extensions = [];
34
80
  for (const node of ast.children) {
35
81
  if (node.type !== "section")
36
82
  continue;
37
- if (node.level === 1 && name === undefined) {
83
+ if (node.level === 1) {
84
+ if (seenH1) {
85
+ invalidBindDraft("bind draft allows exactly one H1 heading");
86
+ }
87
+ seenH1 = true;
38
88
  name = node.title.trim() || undefined;
39
89
  continue;
40
90
  }
41
91
  if (node.level !== 2)
42
92
  continue;
43
93
  const normalized = normalizeSectionName(node.title);
44
- if (!KNOWN_SECTIONS.has(normalized))
94
+ if (KNOWN_SECTIONS.has(normalized)) {
95
+ if (sections.has(normalized)) {
96
+ invalidBindDraft(`duplicate section '${node.title}' in bind draft`);
97
+ }
98
+ sections.set(normalized, node);
45
99
  continue;
46
- if (sections.has(normalized)) {
47
- invalidBindDraft(`duplicate section '${node.title}' in bind draft`);
48
100
  }
49
- sections.set(normalized, node);
101
+ extensions.push({
102
+ title: node.title.trim(),
103
+ content: normalizeExtensionContent(renderSectionContent(node)),
104
+ });
50
105
  }
51
106
  if (!name) {
52
- invalidBindDraft("missing H1 in bind draft; expected '# <name>' followed by '## Objective' and '## Scope'");
107
+ invalidBindDraft("missing H1 in bind draft; expected '# <name>' followed by '## Objective', '## Scope', and '## Checks'");
53
108
  }
54
109
  const objective = sections.get("objective");
55
110
  const scope = sections.get("scope");
111
+ const checks = sections.get("checks");
56
112
  if (!objective)
57
113
  invalidBindDraft("missing Objective section in bind draft");
58
114
  if (!scope)
59
115
  invalidBindDraft("missing Scope section in bind draft");
116
+ if (!checks)
117
+ invalidBindDraft("missing Checks section in bind draft");
60
118
  const objectiveText = renderSectionContent(objective).trim();
61
119
  if (!objectiveText)
62
120
  invalidBindDraft("Objective section cannot be empty");
63
- const unexpectedScopeLine = findUnexpectedScopeLine(scope);
121
+ const unexpectedScopeLine = findUnexpectedListOnlyLine(scope);
64
122
  if (unexpectedScopeLine) {
65
123
  invalidBindDraft(`Scope must be a bullet list; found '${unexpectedScopeLine}'`);
66
124
  }
@@ -77,5 +135,45 @@ export function parseBindDraft(content) {
77
135
  }
78
136
  return normalizeScopePattern(item);
79
137
  }),
138
+ checks: extractSingleLineChecks(checks),
139
+ extensions,
140
+ };
141
+ }
142
+ export function renderBindDraft(draft) {
143
+ const sections = [
144
+ `# ${draft.name}`,
145
+ `## Objective\n${draft.objective}`,
146
+ `## Scope\n${draft.scope.map((pattern) => `- ${pattern}`).join("\n")}`,
147
+ `## Checks\n${draft.checks.map((check) => `- ${check}`).join("\n")}`,
148
+ ];
149
+ for (const extension of draft.extensions) {
150
+ const content = normalizeExtensionContent(extension.content);
151
+ if (content.length > 0) {
152
+ sections.push(`## ${extension.title}\n${content}`);
153
+ }
154
+ else {
155
+ sections.push(`## ${extension.title}`);
156
+ }
157
+ }
158
+ return `${sections.join("\n\n")}\n`;
159
+ }
160
+ export function compileBindDraftFlags(input) {
161
+ for (const [flag, value] of [
162
+ ["--place", input.place],
163
+ ["--objective", input.objective],
164
+ ["--scope", input.scope],
165
+ ["--checks", input.checks],
166
+ ]) {
167
+ if (value.includes("\n") || value.includes("\r")) {
168
+ throw new FlowError("EMPTY_PARAM", `${flag} must be a single line`);
169
+ }
170
+ }
171
+ const draft = {
172
+ name: input.place,
173
+ objective: input.objective,
174
+ scope: [input.scope],
175
+ checks: [input.checks],
176
+ extensions: [],
80
177
  };
178
+ return renderBindDraft(parseBindDraft(renderBindDraft(draft)));
81
179
  }
@@ -52,6 +52,14 @@ export const bindEntryDataSchema = z
52
52
  name: nonEmptyString,
53
53
  objective: nonEmptyString,
54
54
  scope: z.array(scopePatternString).min(1),
55
+ checks: z.array(nonBlankString).min(1),
56
+ extensions: z
57
+ .array(z
58
+ .object({
59
+ title: nonBlankString,
60
+ content: z.string(),
61
+ })
62
+ .strict()),
55
63
  base: shaSchema,
56
64
  target: fullBranchRefSchema,
57
65
  workspace: z.enum(["worktree", "here"]),
@@ -111,7 +119,7 @@ export const arcEntryDataSchema = z
111
119
  .strict();
112
120
  export const sealEntryDataSchema = z
113
121
  .object({
114
- arc: z.number().int().positive(),
122
+ arc: z.number().int().nonnegative(),
115
123
  sealedBy: z.enum(["arc", "renew", "petition"]),
116
124
  oldFence: shaSchema,
117
125
  newFence: shaSchema,
@@ -1,23 +1,7 @@
1
1
  import { isCommissionId } from "./ids.js";
2
- function isRecord(value) {
3
- return value !== null && typeof value === "object" && !Array.isArray(value);
4
- }
5
- function assertExactFields(value, required, optional, prefix, invalid) {
6
- for (const field of required) {
7
- if (!(field in value))
8
- throw invalid(prefix ? `${prefix}.${field}` : field, "is missing");
9
- }
10
- const allowed = new Set([...required, ...optional]);
11
- const unknown = Object.keys(value).filter((field) => !allowed.has(field)).sort()[0];
12
- if (unknown !== undefined)
13
- throw invalid(prefix ? `${prefix}.${unknown}` : unknown, "is unknown");
14
- }
15
- function requireNonBlank(value, field, invalid) {
16
- if (typeof value !== "string" || value.trim() === "")
17
- throw invalid(field, "must be a non-blank string");
18
- }
2
+ import { assertExactFields, assertNonBlankString, isPlainRecord, } from "./validation.js";
19
3
  export function parseExecutionAuthority(value, invalid) {
20
- if (!isRecord(value))
4
+ if (!isPlainRecord(value))
21
5
  throw invalid("authority", "must be an object");
22
6
  if (!("kind" in value))
23
7
  throw invalid("authority.kind", "is missing");
@@ -34,13 +18,13 @@ export function parseExecutionAuthority(value, invalid) {
34
18
  throw invalid("authority.kind", "must be 'repository' or 'user'");
35
19
  }
36
20
  export function parseExecutionBinding(value, invalid) {
37
- if (!isRecord(value))
21
+ if (!isPlainRecord(value))
38
22
  throw invalid("binding", "must be an object");
39
23
  if (!("kind" in value))
40
24
  throw invalid("binding.kind", "is missing");
41
25
  if (value.kind === "commission") {
42
26
  assertExactFields(value, ["kind", "commissionId"], [], "binding", invalid);
43
- requireNonBlank(value.commissionId, "binding.commissionId", invalid);
27
+ assertNonBlankString(value.commissionId, "binding.commissionId", invalid);
44
28
  if (!isCommissionId(value.commissionId))
45
29
  throw invalid("binding.commissionId", "must be a full commission ID");
46
30
  return { kind: "commission", commissionId: value.commissionId };
@@ -1,6 +1,7 @@
1
1
  import * as path from "node:path";
2
2
  import { ProjectionStateError } from "./atomic-publish.js";
3
3
  import { parseExecutionBinding as parseCanonicalExecutionBinding, parseExecutionCoordinates, } from "./execution-coordinate.js";
4
+ import { assertExactFields, assertNonBlankString, isPlainRecord, } from "./validation.js";
4
5
  const TOP_LEVEL_FIELDS = [
5
6
  "version",
6
7
  "pid",
@@ -16,31 +17,14 @@ const PACT_FIELD_NAMES = TOP_LEVEL_FIELDS.filter((field) => field !== "authority
16
17
  function pactError(pactPath, field, reason) {
17
18
  return new ProjectionStateError(`invalid projection pact at ${pactPath}: field '${field}' ${reason}`);
18
19
  }
19
- function isRecord(value) {
20
- return value !== null && typeof value === "object" && !Array.isArray(value);
21
- }
22
- function assertExactFields(value, required, optional, pactPath, fieldPrefix) {
23
- for (const field of required) {
24
- if (!(field in value)) {
25
- throw pactError(pactPath, fieldPrefix ? `${fieldPrefix}.${field}` : field, "is missing");
26
- }
27
- }
28
- const allowed = new Set([...required, ...optional]);
29
- const unknown = Object.keys(value).filter((field) => !allowed.has(field)).sort()[0];
30
- if (unknown !== undefined) {
31
- throw pactError(pactPath, fieldPrefix ? `${fieldPrefix}.${unknown}` : unknown, "is unknown");
32
- }
33
- }
34
- function assertNonBlankString(value, pactPath, field) {
35
- if (typeof value !== "string" || value.trim() === "") {
36
- throw pactError(pactPath, field, "must be a non-blank string");
37
- }
20
+ function pactInvalid(pactPath) {
21
+ return (field, reason) => pactError(pactPath, field, reason);
38
22
  }
39
23
  /** Parse the pre-publication authority choice without inventing a durable identity. */
40
24
  export function parseExecutionAuthoritySelection(value, pactPath = "projection identity", expectedKind) {
41
- if (!isRecord(value))
25
+ if (!isPlainRecord(value))
42
26
  throw pactError(pactPath, "authority", "must be an object");
43
- assertExactFields(value, ["kind"], [], pactPath, "authority");
27
+ assertExactFields(value, ["kind"], [], "authority", pactInvalid(pactPath));
44
28
  if (value.kind !== "repository" && value.kind !== "user") {
45
29
  throw pactError(pactPath, "authority.kind", "must be 'repository' or 'user'");
46
30
  }
@@ -57,9 +41,9 @@ export function assertExecutionPactCoordinates(authorityValue, bindingValue, pac
57
41
  return parseExecutionCoordinates(authorityValue, bindingValue, (field, reason) => pactError(pactPath, field, reason));
58
42
  }
59
43
  function parseEffectivePolicy(value, pactPath) {
60
- if (!isRecord(value))
44
+ if (!isPlainRecord(value))
61
45
  throw pactError(pactPath, "effectivePolicy", "must be an object");
62
- assertExactFields(value, [], ["access", "network", "webSearch"], pactPath, "effectivePolicy");
46
+ assertExactFields(value, [], ["access", "network", "webSearch"], "effectivePolicy", pactInvalid(pactPath));
63
47
  if (value.access !== undefined && value.access !== "read" && value.access !== "write" && value.access !== "auto") {
64
48
  throw pactError(pactPath, "effectivePolicy.access", "must be 'read', 'write', or 'auto'");
65
49
  }
@@ -77,14 +61,15 @@ function parseExecutionPactFieldsRecord(value, pactPath) {
77
61
  if (!Number.isInteger(value.pid) || value.pid < 0) {
78
62
  throw pactError(pactPath, "pid", "must be a non-negative integer");
79
63
  }
80
- assertNonBlankString(value.akuma, pactPath, "akuma");
81
- assertNonBlankString(value.provider, pactPath, "provider");
82
- assertNonBlankString(value.mintedAt, pactPath, "mintedAt");
64
+ const invalid = pactInvalid(pactPath);
65
+ assertNonBlankString(value.akuma, "akuma", invalid);
66
+ assertNonBlankString(value.provider, "provider", invalid);
67
+ assertNonBlankString(value.mintedAt, "mintedAt", invalid);
83
68
  const mintedAtMs = Date.parse(value.mintedAt);
84
69
  if (!Number.isFinite(mintedAtMs) || new Date(mintedAtMs).toISOString() !== value.mintedAt) {
85
70
  throw pactError(pactPath, "mintedAt", "must be an ISO-8601 timestamp");
86
71
  }
87
- assertNonBlankString(value.workspaceRoot, pactPath, "workspaceRoot");
72
+ assertNonBlankString(value.workspaceRoot, "workspaceRoot", invalid);
88
73
  if (!path.isAbsolute(value.workspaceRoot)) {
89
74
  throw pactError(pactPath, "workspaceRoot", "must be an absolute path");
90
75
  }
@@ -104,15 +89,15 @@ function parseExecutionPactFieldsRecord(value, pactPath) {
104
89
  }
105
90
  /** Validate every pact field that exists before a repository identity is minted. */
106
91
  export function parseExecutionPactFields(value, pactPath = "projection identity") {
107
- if (!isRecord(value))
92
+ if (!isPlainRecord(value))
108
93
  throw pactError(pactPath, "$", "must be an object");
109
- assertExactFields(value, PACT_FIELD_NAMES, TOP_LEVEL_OPTIONAL_FIELDS, pactPath, "");
94
+ assertExactFields(value, PACT_FIELD_NAMES, TOP_LEVEL_OPTIONAL_FIELDS, "", pactInvalid(pactPath));
110
95
  return parseExecutionPactFieldsRecord(value, pactPath);
111
96
  }
112
97
  export function parseExecutionPact(value, pactPath = "projection identity") {
113
- if (!isRecord(value))
98
+ if (!isPlainRecord(value))
114
99
  throw pactError(pactPath, "$", "must be an object");
115
- assertExactFields(value, TOP_LEVEL_FIELDS, TOP_LEVEL_OPTIONAL_FIELDS, pactPath, "");
100
+ assertExactFields(value, TOP_LEVEL_FIELDS, TOP_LEVEL_OPTIONAL_FIELDS, "", pactInvalid(pactPath));
116
101
  const fields = parseExecutionPactFieldsRecord(value, pactPath);
117
102
  const { authority, binding } = assertExecutionPactCoordinates(value.authority, fields.binding, pactPath);
118
103
  return {
@@ -1,21 +1,27 @@
1
1
  import { FlowError } from "../flow-error.js";
2
2
  import { activeLedgerRef, appendWithRetry, readLedger } from "./ledger.js";
3
3
  import { releaseScope } from "./registry.js";
4
- import { deriveContractState, isTerminalState } from "./status.js";
4
+ import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
5
+ import { reconcileTaskForfeit } from "./task-contract.js";
5
6
  import { cleanupContractWorkspace, stableRepoRoot } from "./worktree-path.js";
6
7
  async function cleanupForfeit(cwd, contractId, actor, at) {
7
8
  const stableRoot = await stableRepoRoot(cwd);
8
9
  await releaseScope(stableRoot, { contractId, actor, at });
9
10
  return await cleanupContractWorkspace(stableRoot, contractId);
10
11
  }
11
- export async function forfeitContract(input) {
12
+ export async function forfeitContract(input, deps = {}) {
12
13
  const at = new Date(input.nowMs).toISOString();
13
- const ledger = await readLedger(input.cwd, input.contractId);
14
+ const stableRoot = await stableRepoRoot(input.cwd);
15
+ const ledger = await readLedger(stableRoot, input.contractId);
14
16
  if (ledger) {
15
17
  const state = deriveContractState(ledger.entries);
16
18
  if (state.state === "forfeited") {
17
- // Already-forfeited retry only performs idempotent cleanup.
18
- const cleanup = await cleanupForfeit(input.cwd, input.contractId, input.actor, at);
19
+ await reconcileTaskForfeit({
20
+ cwd: stableRoot,
21
+ contractId: input.contractId,
22
+ publishLedger: async () => undefined,
23
+ });
24
+ const cleanup = await (deps.cleanupForfeit ?? cleanupForfeit)(stableRoot, input.contractId, input.actor, at);
19
25
  return { contractId: input.contractId, cleanup };
20
26
  }
21
27
  }
@@ -26,25 +32,32 @@ export async function forfeitContract(input) {
26
32
  if (state.state === "corrupt" || isTerminalState(state)) {
27
33
  throw new FlowError("INTERNAL_STATE", `contract ${input.contractId} cannot forfeit from state ${state.state}`);
28
34
  }
29
- const result = await appendWithRetry(input.cwd, activeLedgerRef(input.contractId), () => {
30
- const entry = {
31
- v: 1,
32
- kind: "forfeit",
33
- contract: input.contractId,
34
- at,
35
- actor: input.actor,
36
- data: {
37
- reason: input.reason,
38
- by: input.actor,
39
- ...(input.note?.trim() ? { note: input.note.trim() } : {}),
40
- },
41
- };
42
- return entry;
35
+ await reconcileTaskForfeit({
36
+ cwd: stableRoot,
37
+ contractId: input.contractId,
38
+ publishLedger: async () => {
39
+ const result = await appendWithRetry(stableRoot, activeLedgerRef(input.contractId), () => {
40
+ const entry = {
41
+ v: 1,
42
+ kind: "forfeit",
43
+ contract: input.contractId,
44
+ at,
45
+ actor: input.actor,
46
+ data: {
47
+ reason: input.reason,
48
+ by: input.actor,
49
+ ...(input.note?.trim() ? { note: input.note.trim() } : {}),
50
+ },
51
+ };
52
+ return entry;
53
+ });
54
+ if (!result.ok)
55
+ throw new FlowError("INTERNAL_STATE", `failed to forfeit ${input.contractId}: ${result.reason}`);
56
+ return result;
57
+ },
43
58
  });
44
- if (!result.ok)
45
- throw new FlowError("INTERNAL_STATE", `failed to forfeit ${input.contractId}: ${result.reason}`);
46
59
  // Forfeit entry naturally removes membership; no dequeue record.
47
- const cleanup = await cleanupForfeit(input.cwd, input.contractId, input.actor, at);
60
+ const cleanup = await (deps.cleanupForfeit ?? cleanupForfeit)(stableRoot, input.contractId, input.actor, at);
48
61
  return { contractId: input.contractId, cleanup };
49
62
  }
50
63
  export async function forfeitContractCommand(input) {
@@ -9,7 +9,7 @@ const HELP_BODY_TEMPLATE = [
9
9
  `- **\${bind}**: Create the active contract ledger and workspace files from Markdown stdin. The worktree must be clean; call it once per Keiyaku.`,
10
10
  `- **\${arc}**: Record the delivery arc title, objective, and brief before work starts. It seals any open arc and does not run agents or mutate delivery code.`,
11
11
  `- **\${call}**: Call a ${ACTOR_IDENTITY} for bounded work. An open arc supplies scoped context; the caller still owns review, integration, and commits.`,
12
- `- **akuma list / akuma show**: Inspect available ${ACTOR_IDENTITY_PLURAL} and their resolved provider/executable before choosing \`keiyaku call NAME\`.`,
12
+ `- **akuma ls / akuma view**: Inspect available ${ACTOR_IDENTITY_PLURAL} and their resolved provider/executable before choosing \`keiyaku call NAME\`.`,
13
13
  "- **delivery commits**: Commit the actual code or documentation changes in git before sealing them.",
14
14
  `- **\${renew}**: Capture receipts, update delivery onto the default branch, seal the open arc, and record the new base.`,
15
15
  `- **\${amend}**: Update the Keiyaku when scope, rules, criteria, or intent changed.`,
@@ -22,7 +22,7 @@ const HELP_BODY_TEMPLATE = [
22
22
  `- \`\${arc}\` and \`\${renew}\` are ledger operations; they do not call agents and do not edit delivery code.`,
23
23
  `- \`\${call}\` can run outside a Keiyaku, but an open arc gives the ${ACTOR_IDENTITY} better context.`,
24
24
  `- \`\${call}\` starts a new session. Use \`keiyaku revive ARTIFACT_ID\` to continue a prior response artifact.`,
25
- "- `akuma list` and `akuma show <name>` are read-only inspection commands; they do not start a helper.",
25
+ "- `akuma ls` and `akuma view <name>` are read-only inspection commands; they do not start a helper.",
26
26
  "- `keiyaku revive` restores the agent from the response artifact; `--incognito` calls cannot be revived later.",
27
27
  "- Premature `petition` requests are rejected; continue with `${arc}`, `${call}`, or `${renew}` until the work is verified.",
28
28
  "",
package/build/core/log.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { FlowError } from "../flow-error.js";
2
2
  import { readLedger } from "./ledger.js";
3
- import { renderContractView } from "./render.js";
4
- import { deriveContractState } from "./status.js";
3
+ import { renderContractView } from "./contract-view.js";
4
+ import { deriveContractState } from "./status/lifecycle.js";
5
5
  export async function readContractLog(input) {
6
6
  const ledger = await readLedger(input.cwd, input.contractId);
7
7
  if (!ledger) {
@@ -1,8 +1,10 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { getConfig } from "../config/env.js";
3
- import { assertSettingsKnobUsable, loadKeiyakuSettings, resolveReviewerAgentName, } from "../config/settings.js";
4
- import { loadAkumaCatalogV2 } from "../config/akuma-loader_v2.js";
5
- import { formatAvailableAgentNamesV2 } from "../agents/selector_v2.js";
3
+ import { assertSettingsKnobUsable } from "../config/settings/disease.js";
4
+ import { resolveReviewerAgentName } from "../config/settings/knobs.js";
5
+ import { loadKeiyakuSettings } from "../config/settings/loader.js";
6
+ import { loadAkumaCatalog } from "../config/akuma-loader.js";
7
+ import { formatAvailableAgentNames } from "../agents/selector.js";
6
8
  import { getDiffStats } from "../git/diff/preview.js";
7
9
  import { readDiff } from "../git/diff/read.js";
8
10
  import { getCurrentBranch } from "../git/branches.js";
@@ -11,7 +13,7 @@ import { DIFF_PREVIEW_TEXT_MAX_CHARS } from "../keiyaku.js";
11
13
  import { FlowError } from "../flow-error.js";
12
14
  import { persistResponseHistory } from "./transcripts.js";
13
15
  import { logInfo } from "../telemetry/logger.js";
14
- import { execSubagent } from "./call.js";
16
+ import { execSubagent } from "./call/call.js";
15
17
  import { readContractLog } from "./log.js";
16
18
  import { buildVerificationFailureHints } from "./hints.js";
17
19
  const CLAIM_VERIFICATION_OUTPUT_MAX_CHARS = 1200;
@@ -168,10 +170,10 @@ export function createAgentReviewGate() {
168
170
  }
169
171
  const settings = loaded.knobs;
170
172
  const reviewerAgentName = resolveReviewerAgentName(settings);
171
- const catalog = await loadAkumaCatalogV2(cwd);
173
+ const catalog = await loadAkumaCatalog(cwd);
172
174
  const availableNames = Object.keys(catalog.selected);
173
175
  if (catalog.selected[reviewerAgentName] === undefined) {
174
- throw new FlowError("UNKNOWN_SUBAGENT", `Unknown agent '${reviewerAgentName}'. Expected one of: ${formatAvailableAgentNamesV2(availableNames)}`);
176
+ throw new FlowError("UNKNOWN_SUBAGENT", `Unknown agent '${reviewerAgentName}'. Expected one of: ${formatAvailableAgentNames(availableNames)}`);
175
177
  }
176
178
  const [contractLog, diffStat, fullDiff, currentBranch, headRef] = await Promise.all([
177
179
  readContractLog({ cwd, contractId }),