@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,7 +1,8 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import * as path from "node:path";
3
3
  import { FlowError } from "../flow-error.js";
4
- import { createGit, wrapGitError } from "./core.js";
4
+ import { runGitProcessBytes, runGitOrThrow } from "./process.js";
5
+ import { wrapGitError } from "./core.js";
5
6
  export const DIRTY_FILE_CATEGORY = {
6
7
  modified: "modified",
7
8
  added: "added",
@@ -11,44 +12,106 @@ export const DIRTY_FILE_CATEGORY = {
11
12
  copied: "copied",
12
13
  untracked: "untracked",
13
14
  };
15
+ export class GitPathEncodingError extends Error {
16
+ name = "GitPathEncodingError";
17
+ code = "GIT_PATH_ENCODING";
18
+ constructor() {
19
+ super("Git returned a path that is not valid UTF-8");
20
+ }
21
+ }
14
22
  const DIRTY_WORKTREE_LIST_LIMIT = 10;
15
23
  const KEIYAKU_ARTIFACT_DIR_PREFIX = ".keiyaku/";
24
+ const CONFLICT_STATUS_CODES = new Set(["DD", "AU", "UD", "UA", "DU", "AA", "UU"]);
16
25
  function normalizeGitPath(filePath) {
17
- return filePath.trim().replace(/^\.\/+/, "");
18
- }
19
- function toDirtyStatusPathSets(status) {
20
- const renamed = new Set(status.renamed.flatMap((entry) => [entry.from, entry.to]).filter((value) => value.length > 0));
21
- return {
22
- modified: new Set(status.modified),
23
- added: new Set([...status.created, ...status.staged]),
24
- deleted: new Set(status.deleted),
25
- renamed,
26
- conflicted: new Set(status.conflicted),
27
- untracked: new Set(status.not_added),
28
- };
29
- }
30
- function deriveDirtyFileCategory(file, statusPathSets) {
31
- if (statusPathSets.untracked.has(file.path)) {
26
+ return filePath.replace(/^\.\/+/, "");
27
+ }
28
+ function dirtyCategory(index, workingDir) {
29
+ const code = `${index}${workingDir}`;
30
+ if (code === "??")
32
31
  return DIRTY_FILE_CATEGORY.untracked;
33
- }
34
- if (statusPathSets.conflicted.has(file.path)) {
32
+ if (CONFLICT_STATUS_CODES.has(code) || index === "U" || workingDir === "U")
35
33
  return DIRTY_FILE_CATEGORY.conflicted;
36
- }
37
- if (statusPathSets.renamed.has(file.path)) {
34
+ if (index === "R" || workingDir === "R")
38
35
  return DIRTY_FILE_CATEGORY.renamed;
39
- }
40
- if (statusPathSets.deleted.has(file.path)) {
36
+ if (index === "C" || workingDir === "C")
37
+ return DIRTY_FILE_CATEGORY.copied;
38
+ if (index === "D" || workingDir === "D")
41
39
  return DIRTY_FILE_CATEGORY.deleted;
42
- }
43
- if (statusPathSets.added.has(file.path)) {
40
+ if (index === "A" || workingDir === "A")
44
41
  return DIRTY_FILE_CATEGORY.added;
42
+ return DIRTY_FILE_CATEGORY.modified;
43
+ }
44
+ function splitNulFields(output) {
45
+ const fields = [];
46
+ let start = 0;
47
+ for (;;) {
48
+ const end = output.indexOf(0, start);
49
+ if (end < 0)
50
+ break;
51
+ fields.push(output.subarray(start, end));
52
+ start = end + 1;
53
+ }
54
+ if (start < output.length)
55
+ fields.push(output.subarray(start));
56
+ return fields;
57
+ }
58
+ function decodeGitPath(value) {
59
+ try {
60
+ return new TextDecoder("utf-8", { fatal: true }).decode(value);
61
+ }
62
+ catch {
63
+ throw new GitPathEncodingError();
64
+ }
65
+ }
66
+ /** Parse porcelain v1 -z bytes without quoting, trimming, or lossy decoding. */
67
+ export function parsePorcelainStatusZ(output) {
68
+ const fields = splitNulFields(output);
69
+ const files = [];
70
+ for (let index = 0; index < fields.length; index += 1) {
71
+ const field = fields[index] ?? Buffer.alloc(0);
72
+ if (field.length === 0)
73
+ continue;
74
+ const indexCode = String.fromCharCode(field[0] ?? 0x20);
75
+ const workingDirCode = String.fromCharCode(field[1] ?? 0x20);
76
+ if (field[2] !== 0x20)
77
+ continue;
78
+ const filePath = decodeGitPath(field.subarray(3));
79
+ if (indexCode === "!" || workingDirCode === "!")
80
+ continue;
81
+ const renameOrCopy = indexCode === "R" || workingDirCode === "R" || indexCode === "C" || workingDirCode === "C";
82
+ if (renameOrCopy) {
83
+ decodeGitPath(fields[index + 1] ?? Buffer.alloc(0));
84
+ index += 1; // The second NUL field is the original path, not another status record.
85
+ }
86
+ files.push({
87
+ path: normalizeGitPath(filePath),
88
+ index: indexCode,
89
+ working_dir: workingDirCode,
90
+ category: dirtyCategory(indexCode, workingDirCode),
91
+ });
92
+ }
93
+ return files;
94
+ }
95
+ async function gitBytes(cwd, args, label) {
96
+ const result = await runGitProcessBytes(cwd, args);
97
+ if (result.error)
98
+ throw wrapGitError(label, result.error, cwd);
99
+ if (result.status !== 0) {
100
+ throw wrapGitError(label, Object.assign(new Error(`git exited with status ${result.status ?? "unknown"}`), {
101
+ stdout: result.stdout.toString("utf8"),
102
+ stderr: result.stderr.toString("utf8"),
103
+ status: result.status,
104
+ }), cwd);
105
+ }
106
+ return result.stdout;
107
+ }
108
+ async function gitOutput(cwd, args, label) {
109
+ try {
110
+ return (await runGitOrThrow(cwd, args)).stdout;
45
111
  }
46
- if (statusPathSets.modified.has(file.path)) {
47
- return DIRTY_FILE_CATEGORY.modified;
112
+ catch (error) {
113
+ throw wrapGitError(label, error, cwd);
48
114
  }
49
- return file.index === "C" || file.working_dir === "C"
50
- ? DIRTY_FILE_CATEGORY.copied
51
- : DIRTY_FILE_CATEGORY.modified;
52
115
  }
53
116
  export function renderDirtyFileStatusLine(file) {
54
117
  const indexCode = file.index.length > 0 ? file.index : " ";
@@ -56,21 +119,7 @@ export function renderDirtyFileStatusLine(file) {
56
119
  return `${indexCode}${worktreeCode} ${file.path}`;
57
120
  }
58
121
  export async function getDirtyFiles(cwd) {
59
- const git = createGit(cwd);
60
- let status;
61
- try {
62
- status = await git.status();
63
- }
64
- catch (err) {
65
- throw wrapGitError("status --porcelain", err, cwd);
66
- }
67
- const statusPathSets = toDirtyStatusPathSets(status);
68
- return status.files.map((file) => ({
69
- path: file.path,
70
- index: file.index,
71
- working_dir: file.working_dir,
72
- category: deriveDirtyFileCategory(file, statusPathSets),
73
- }));
122
+ return parsePorcelainStatusZ(await gitBytes(cwd, ["status", "--porcelain=v1", "-z", "--untracked-files=all"], "status --porcelain"));
74
123
  }
75
124
  function normalizePathForDirtyMatch(cwd, filePath) {
76
125
  const normalizedPath = filePath.trim();
@@ -90,36 +139,16 @@ export async function assertCleanTrackedWorkingTree(cwd) {
90
139
  const overflowCount = dirtyFiles.length - shown.length;
91
140
  const overflowLine = overflowCount > 0 ? `\n ... (+${overflowCount} more)` : "";
92
141
  const hasDirtyKeiyakuArtifacts = dirtyFiles.some((file) => normalizePathForDirtyMatch(cwd, file.path).startsWith(KEIYAKU_ARTIFACT_DIR_PREFIX));
93
- throw new FlowError("DIRTY_WORKTREE", `Uncommitted changes detected in working tree:\n${list}${overflowLine}\n\nBlocking changes must be committed or stashed before proceeding.`, hasDirtyKeiyakuArtifacts
94
- ? { facts: { kind: "dirty_worktree", hasKeiyakuArtifacts: true } }
95
- : undefined);
142
+ throw new FlowError("DIRTY_WORKTREE", `Uncommitted changes detected in working tree:\n${list}${overflowLine}\n\nBlocking changes must be committed or stashed before proceeding.`, hasDirtyKeiyakuArtifacts ? { facts: { kind: "dirty_worktree", hasKeiyakuArtifacts: true } } : undefined);
96
143
  }
97
144
  export async function getUntrackedFiles(cwd) {
98
- const git = createGit(cwd);
99
- let output;
100
- try {
101
- output = await git.raw(["ls-files", "--others", "--exclude-standard"]);
102
- }
103
- catch (err) {
104
- throw wrapGitError("ls-files --others --exclude-standard", err, cwd);
105
- }
106
- const files = output
107
- .split(/\r?\n/)
108
- .map((line) => normalizeGitPath(line))
109
- .filter((line) => line.length > 0);
110
- return Array.from(new Set(files));
145
+ const output = await gitBytes(cwd, ["ls-files", "--others", "--exclude-standard", "-z"], "ls-files --others --exclude-standard");
146
+ return Array.from(new Set(splitNulFields(output).filter((filePath) => filePath.length > 0).map(decodeGitPath).map(normalizeGitPath)));
111
147
  }
112
148
  /** Capture conflicted paths while the index still contains unmerged entries. */
113
149
  export async function getUnmergedPaths(cwd) {
114
- const git = createGit(cwd);
115
- let output;
116
- try {
117
- output = await git.raw(["diff", "--name-only", "--diff-filter=U", "-z"]);
118
- }
119
- catch (err) {
120
- throw wrapGitError("diff --name-only --diff-filter=U", err, cwd);
121
- }
122
- return Array.from(new Set(output.split("\0").filter((filePath) => filePath.length > 0)));
150
+ const output = await gitBytes(cwd, ["diff", "--name-only", "--diff-filter=U", "-z"], "diff --name-only --diff-filter=U");
151
+ return Array.from(new Set(splitNulFields(output).filter((filePath) => filePath.length > 0).map(decodeGitPath)));
123
152
  }
124
153
  async function directoryExists(directory) {
125
154
  try {
@@ -133,30 +162,14 @@ async function directoryExists(directory) {
133
162
  }
134
163
  /** Git owns these paths; a lingering REBASE_HEAD alone is not active-rebase evidence. */
135
164
  export async function hasActiveRebase(cwd) {
136
- const git = createGit(cwd);
137
- let paths;
138
- try {
139
- paths = await Promise.all([
140
- git.raw(["rev-parse", "--git-path", "rebase-merge"]),
141
- git.raw(["rev-parse", "--git-path", "rebase-apply"]),
142
- ]);
143
- }
144
- catch (error) {
145
- throw wrapGitError("rev-parse --git-path rebase state", error, cwd);
146
- }
165
+ const paths = await Promise.all([
166
+ gitOutput(cwd, ["rev-parse", "--git-path", "rebase-merge"], "rev-parse --git-path rebase state"),
167
+ gitOutput(cwd, ["rev-parse", "--git-path", "rebase-apply"], "rev-parse --git-path rebase state"),
168
+ ]);
147
169
  const directories = paths.map((candidate) => path.resolve(cwd, candidate.trim()));
148
170
  return (await Promise.all(directories.map(directoryExists))).some(Boolean);
149
171
  }
150
172
  export async function isPathTracked(cwd, filePath) {
151
- const git = createGit(cwd);
152
- let output;
153
- try {
154
- output = await git.raw(["ls-files", "--", filePath]);
155
- }
156
- catch (err) {
157
- throw wrapGitError(`ls-files -- ${filePath}`, err, cwd);
158
- }
159
- return output
160
- .split(/\r?\n/)
161
- .some((line) => line.trim() === filePath);
173
+ const output = await gitOutput(cwd, ["ls-files", "-z", "--", filePath], `ls-files -- ${filePath}`);
174
+ return output.split("\0").some((line) => line === filePath);
162
175
  }
package/build/index.js CHANGED
@@ -5,6 +5,7 @@ import { getConfig, initializeConfig } from "./config/env.js";
5
5
  import { ENV_KEYS } from "./config/env-keys.js";
6
6
  import { formatErrorWithCause } from "./telemetry/logger.js";
7
7
  import { runProjectionGenerationRuntime } from "./core/projection/index.js";
8
+ import { runVerificationRuntime } from "./core/settlement/verification.js";
8
9
  import { renderCliHelp, runCliCommand } from "./cli/index.js";
9
10
  import { installCliOutputStreamErrorPolicy, writeCliOutput } from "./cli/output-stream-error-policy.js";
10
11
  import { assertSubagentCliCommandAllowed } from "./cli/subagent-guard.js";
@@ -37,6 +38,12 @@ async function main() {
37
38
  await runProjectionGenerationRuntime(projectionDirectory, cliArgs[2]);
38
39
  return;
39
40
  }
41
+ if (cliArgs[0] === "__keiyaku-verify") {
42
+ if (cliArgs.length !== 3 || !cliArgs[1] || !cliArgs[2])
43
+ throw new Error("invalid internal verification invocation");
44
+ await runVerificationRuntime(cliArgs[1], cliArgs[2]);
45
+ return;
46
+ }
40
47
  initializeConfig(process.env, findCliCwd(cliArgs) ?? process.cwd());
41
48
  const config = getConfig();
42
49
  assertSubagentCliCommandAllowed({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/keiyaku",
3
- "version": "2.9.11",
3
+ "version": "2.9.13",
4
4
  "description": "CLI for running iterative keiyaku workflows with Codex subagents.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -33,7 +33,7 @@
33
33
  "start": "node build/index.js",
34
34
  "test:unit": "node scripts/run-unit-tests.mjs",
35
35
  "test:boundaries": "node scripts/check-import-boundaries.mjs",
36
- "test:integration": "node --import ./tests/support/real-provider-path-sentinel.mjs --import tsx --test tests/integration/integration.test.ts",
36
+ "test:integration": "node --import ./tests/support/real-provider-path-sentinel.mjs --import tsx --test --test-concurrency=4 tests/integration/*.test.ts",
37
37
  "pretest:cli-expect": "npm run build",
38
38
  "test:cli-expect": "node --import ./tests/support/real-provider-path-sentinel.mjs --test --test-concurrency=8 tests/cli-expect/suite/*.test.mjs",
39
39
  "test:real-providers": "KEIYAKU_REAL_PROVIDERS=1 node --import tsx --test --test-concurrency=1 tests/real-provider/matrix.test.ts",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: keiyaku-akuma
3
- description: Use whenever a request mentions an Akuma, akuma, devil, demon, or 恶魔 and the work may involve calling, watching, telling, killing, or reviving one; covers Keiyaku Akuma profiles, projections, artifacts, lifecycle control, and rejoin operations.
3
+ description: Use whenever a request mentions an Akuma, akuma, devil, demon, or 恶魔 and the work may involve calling, waiting, telling, killing, or forking one; covers Keiyaku Akuma profiles, projections, artifacts, lifecycle control, and fork lineage.
4
4
  allowed-tools: Bash(keiyaku *)
5
5
  ---
6
6
 
@@ -13,11 +13,11 @@ Running devils. Contract lifecycle = `keiyaku-workflow` skill.
13
13
  - **profile** — callable configuration: frontmatter selects provider/model;
14
14
  Markdown body gives instructions. Project overrides user, which overrides builtin.
15
15
  - **projection** (`name/8hex`) — one live run of a devil. This is the id for `wait` / `tell` / `kill`.
16
- - **artifact** (`rsp_…`) — what a finished run left behind. This is the id for `revive`.
16
+ - **artifact** (`rsp_…`) — what a retained finished run left behind. This is the id for `fork`.
17
17
 
18
18
  Rule of thumb: continue the same conversation with its projection id. Use an
19
- artifact id with `revive` only when deliberately starting a new projection;
20
- failed/dead state alone is not a reason to revive.
19
+ artifact id with `fork` only when deliberately starting a new projection;
20
+ failed/terminal state alone is not a reason to fork.
21
21
 
22
22
  ## Projection aliases
23
23
 
@@ -35,7 +35,7 @@ Aliases are per selected projection ledger, not projection identity. Keep the
35
35
  full `name/8hex` ID in durable records and exact recovery. Reusing an alias
36
36
  word atomically moves only that per-ledger ref to the new projection; it does
37
37
  not stop, mutate, or delete the prior projection. Its old full ID remains
38
- addressable. `revive` starts a new projection and does not inherit an alias.
38
+ addressable. `fork` starts a new projection and does not inherit an alias.
39
39
 
40
40
  ## Common usage
41
41
 
@@ -43,16 +43,17 @@ addressable. `revive` starts a new projection and does not inherit an alias.
43
43
  keiyaku akuma ls # who's callable
44
44
  keiyaku akuma show NAME # resolved profile + live projections
45
45
  keiyaku call NAME "do the thing" # run in foreground until it finishes
46
- keiyaku call NAME --wait 10m "do the thing" # bounded: after 10m you get a snapshot, it keeps running
46
+ keiyaku call NAME --wait 10m "do the thing" # bounded deadline snapshot; it keeps running
47
47
  keiyaku call NAME -d "do the thing" # short for --detach; note the projection id
48
48
  keiyaku call NAME - < prompt.md # long prompt: literal `-` reads stdin
49
- keiyaku wait <proj-id> # join a running one; prints its activity
50
- keiyaku wait <proj-id> --timeout 5m # peek for 5m; timeout = snapshot, not death
49
+ keiyaku wait <proj-id> # observe one retained or active projection
50
+ keiyaku wait <proj-id> --timeout 5m # deadline snapshot; timeout does not stop it
51
51
  keiyaku wait <proj-a> <proj-b> --any # return when the first member finishes
52
52
  keiyaku wait <proj-a> <proj-b> --all # return when every member finishes
53
53
  keiyaku tell <proj-id> [--wait DURATION] <BODY|-> # optionally tell, then bounded-join
54
- keiyaku revive rsp_XXXX "continue..." # new run continuing from a record
55
- keiyaku kill <proj-id> # stop it; record survives, revive still works
54
+ keiyaku fork rsp_XXXX "continue..." # new run continuing from a retained record
55
+ keiyaku kill <proj-id> # request a stop; returns an acceptance receipt
56
+ keiyaku kill <proj-id> --wait 5m # request a stop, then observe the result for 5m
56
57
  keiyaku status # forgot an id? it's on the board
57
58
  ```
58
59
 
@@ -67,17 +68,22 @@ Use `keiyaku akuma show NAME` to inspect the resolved source and configuration.
67
68
 
68
69
  ## Expectations
69
70
 
70
- - Omit tell's bracketed `--wait DURATION` option for silent success: exit 0,
71
- zero output. Supply it to perform the ordinary bounded join after acceptance.
72
- Silence **is** the receipt do not re-send. Effects show up in `wait`/`status`.
73
- - `tell --interrupt` is intentionally destructive. On an active runner, silent
74
- success means the tell and interrupt intent are durable; provider stop and
75
- successor wake continue asynchronously under the same projection id. Idle or
76
- terminal state uses ordinary tell plus wake and reports its typed wake error.
71
+ - `tell` succeeds only after durable acceptance for the addressed projection.
72
+ Its receipt never claims provider consumption. `tell --wait DURATION` then
73
+ performs the ordinary bounded wait and returns that response and exit status.
74
+ - `tell --interrupt` records tell acceptance and interruption intent; provider
75
+ stop/successor adoption remain asynchronous. An idle or terminal wake failure
76
+ preserves the accepted tell and reports its typed wake failure.
77
+ - `kill` returns an acceptance receipt; it does not include the final result.
78
+ Use `kill --wait DURATION` when you need to observe the ordinary final result.
79
+ `DURATION` is required. A timeout ends observation only and does not undo the
80
+ stop request.
77
81
  - Body is required for `call`/`tell`. Literal `-` selects stdin; unselected
78
82
  piped bytes are ignored and cannot change an argv body.
79
- - A `--wait`/`--timeout` expiry prints a live snapshot and exits 0 — the run continues; rejoin with `wait` anytime.
80
- - `wait` is repeatable: on a finished run it shows the final transcript again.
83
+ - A `--wait`/`--timeout` expiry prints the admitted-set deadline snapshot and
84
+ exits 124. It changes no lifecycle state; observe again with `wait` anytime.
85
+ - `wait` is repeatable: a terminal view re-renders durable terminal facts,
86
+ including the typed final answer and real artifact lineage when retained.
81
87
  - Supervise multiple projections with one plural `wait`. Choose `--any` to
82
88
  handle the first terminal member and refill its lane; choose `--all` only for
83
89
  final batch convergence. Keep foreground waits blocking; if bounded, use one
@@ -85,7 +91,7 @@ Use `keiyaku akuma show NAME` to inspect the resolved source and configuration.
85
91
 
86
92
  ## Occasional flags
87
93
 
88
- - `--incognito` — no artifact written, no revive possible (call)
89
- - `--effort LEVEL` — provider effort override; needs a model set (call, revive, tell)
94
+ - `--incognito` — no artifact written, no fork possible (call)
95
+ - `--effort LEVEL` — provider effort override; needs a model set (call, fork, tell)
90
96
  - `--repo DIR` — override repository inference only when the contract ledger
91
97
  lives in a different repository.
@@ -24,8 +24,16 @@ tasks; binding is what turns ready planning facts into one executable contract.
24
24
  ## Common usage
25
25
 
26
26
  ```bash
27
- keiyaku task add - < task.md # literal - reads the task body from stdin
28
- keiyaku task add "title" --pri 1 --needs k-aaa # priority + dependency at birth
27
+ keiyaku task add - <<'EOF' # canonical stdin task document
28
+ ---
29
+ title: Parse task documents
30
+ pri: 1
31
+ needs:
32
+ - task-model
33
+ ---
34
+ Keep parser and model validation together.
35
+ EOF
36
+ keiyaku task add "title" --pri 1 --needs k-aaa # concise title plus creation flags
29
37
  keiyaku task ls # all nonterminal tasks
30
38
  keiyaku task ls --filter state=ready --sort priority,modified
31
39
  keiyaku task show k-xxxx # full detail and parent-children tree
@@ -36,8 +44,31 @@ keiyaku task drop k-xxxx # won't do; kept in history, not
36
44
  keiyaku task update k-xxxx --pri 0 # also --title, --body TEXT|-, --needs,
37
45
  # --drop-needs — see --help
38
46
  keiyaku task doctor # check board integrity
47
+ keiyaku task ls --contract hades # inspect one commissioned workspace from the hub
48
+ ```
49
+
50
+ ## Atomic reorganization
51
+
52
+ Use `task compose` when the final tree and its complete outgoing relation sets
53
+ need review as one operation. Pass the compact sketch through stdin and check
54
+ it before applying it:
55
+
56
+ ```bash
57
+ keiyaku task compose --check - <<'EOF'
58
+ + Project grouping
59
+ @parser needs=@types
60
+ @renderer relates=
61
+ @release parent=
62
+ EOF
63
+ keiyaku task compose - < composition.md
39
64
  ```
40
65
 
66
+ An indented line assigns its parent. A top-level `@reference` keeps its parent;
67
+ `parent=` explicitly clears it. `needs=`, `supersedes=`, and `relates=` replace
68
+ only their named complete relation sets, and an explicit empty assignment clears
69
+ that set. Omit a relation to preserve it. Use the command help for the current
70
+ sketch grammar and receipt fields; this skill does not redefine task policy.
71
+
41
72
  ## Dependencies
42
73
 
43
74
  `--needs` stores the forward edge; `blocks` on the other task is derived automatically. Cycles are rejected at write time. Use needs to express "don't start B before A lands" — nothing more.
@@ -45,18 +76,43 @@ keiyaku task doctor # check board integrity
45
76
  ## Promoting to a contract
46
77
 
47
78
  ```bash
48
- keiyaku bind --task fix-parser
49
- keiyaku bind --task fix-parser --task fix-parser-help - < contract.md
79
+ keiyaku bind --task fix-parser - <<'EOF'
80
+ # Fix parser
81
+
82
+ ## Context
83
+ Valid input is rejected.
84
+
85
+ ## Objective
86
+ Accept valid input.
87
+
88
+ ## Design & Approach
89
+ Correct the parser at its existing boundary.
90
+
91
+ ## Scope
92
+ ~~~
93
+ src/parser.ts
94
+ ~~~
95
+
96
+ ## Criteria
97
+ - Valid input parses.
98
+ EOF
50
99
  ```
51
100
 
101
+ For a multi-task delivery, repeat `--task TASK_ID` on the same bind.
102
+
52
103
  Only ready tasks bind. Multi-bind only when the tasks describe one delivery
53
104
  intent, share a coherent write surface, and should settle together; otherwise
54
105
  keep separate contracts and express order with task `needs` or contract
55
106
  `--after`. The first task supplies title/objective defaults; provide a partial
56
107
  bind document for every remaining resolved section. Final bind admission always
57
- requires Title, Context, Objective, Design, Scope, and Criteria; Verification
108
+ requires Title, Context, Objective, Design & Approach, Scope, and Criteria; Verification
58
109
  is optional.
59
110
 
60
111
  ## Gotcha
61
112
 
62
113
  Task files are ordinary tracked files: checking out another branch shows *that branch's* board. Don't panic when states differ across branches — you're reading a different branch's truth, and the old one is still in git.
114
+
115
+ When a task operation targets a commissioned workspace from the hub, use
116
+ `--contract <place|slug|full-id>`. It resolves the ledger-owned materialized
117
+ checkout and follows its normal task authority; do not use a physical
118
+ `.keiyaku/wt/...` path as the commission identity.
@@ -15,36 +15,88 @@ allowed-tools: Bash(keiyaku *)
15
15
  - Criteria state what must be true. Verification supplies executable evidence.
16
16
  - Implementation happens in the linked worktree. Commit accepted bytes before
17
17
  `arc`, `renew`, or `petition`.
18
- - An arc is a coherent chapter of the same delivery. It keeps the contract,
19
- worktree, branch, and final settlement.
20
- - `audit` reports evidence for one pinned candidate. `petition` asks to settle.
18
+ - An arc is a coherent stage of the same delivery. It keeps the contract,
19
+ worktree, branch, Scope, and final settlement; it is not independently
20
+ claimable.
21
+ - `audit` is the primary integrated review of one pinned candidate; `petition`
22
+ asks to settle that candidate.
21
23
 
22
24
  ```text
23
25
  bound -> active -> petitioned -> claimed | forfeited
24
26
  ```
25
27
 
28
+ ## Choosing Scope
29
+
30
+ Scope names the files this contract is currently expected to write. Use exact
31
+ repository-relative paths when known, a one-component wildcard for unknown
32
+ files in one directory, and a recursive wildcard only when the Objective calls
33
+ for changing the whole subtree.
34
+
35
+ ## Scope
36
+ ~~~
37
+ src/pump.ts
38
+ ~~~
39
+
40
+ Scope overlap means contracts may write the same paths; it does not prove their
41
+ changes conflict or forbid parallel work. If intents are independent, proceed
42
+ in parallel and use `--after` to order settlement; use `--exclusive` only when
43
+ concurrent work is unsafe.
44
+
45
+ Amend is cheap. Broad Scope consumes parallel coordination and weakens audit
46
+ signal, so broader patterns need a reason while narrower terms do not.
47
+
48
+ ## Choose Topology
49
+
50
+ Use arcs only when successive stages share one delivery, acceptance boundary,
51
+ ownership, and final settlement. Scope overlap alone does not choose topology.
52
+ Base drift calls for `renew`, not an arc. `arc --append-scope` adds to the
53
+ cumulative contract Scope; it does not create arc-local Scope enforcement.
54
+
55
+ Use separate tasks and contracts for independently deliverable work, distinct
56
+ acceptance or ownership, or a slice that must wait on another contract. Record
57
+ external ordering with task needs or `--after`; do not keep an otherwise
58
+ claimable slice open merely to wait.
59
+
60
+ One contract can migrate a storage format with an arc for the writer and a
61
+ later arc for the reader, then settle once. A parent task for an API migration
62
+ can instead track producer and consumer child contracts with separate owners
63
+ and acceptance, ordered by a task need or `--after`.
64
+
26
65
  ## Bind
27
66
 
28
- Bind when one implementation journey is decided: motivating facts, owning
29
- modules, inputs and outputs, consequences, failure behavior, invariants,
30
- forbidden expansion, write set, acceptance criteria, and decisive checks.
67
+ Bind a Keiyaku only after its motivating facts and design are clear. When they
68
+ are not, complete the necessary investigation first; that investigation may
69
+ itself be a prerequisite Keiyaku.
31
70
  Use a task only when planning or dependencies need a durable place.
32
71
 
33
- Declare the smallest useful Scope:
72
+ ```bash
73
+ keiyaku bind - <<'EOF'
74
+ # Fix parser
34
75
 
35
- 1. exact paths when known;
36
- 2. a one-component wildcard when names inside one directory are still open;
37
- 3. a recursive wildcard only for a real subtree rewrite, migration, or
38
- generated output.
76
+ ## Context
77
+ Valid input is rejected.
39
78
 
40
- ```bash
41
- keiyaku bind - < contract.md
42
- keiyaku bind --task TASK_ID - < contract.md
79
+ ## Objective
80
+ Accept valid input.
81
+
82
+ ## Design & Approach
83
+ Correct the parser at its existing boundary.
84
+
85
+ ## Scope
86
+ ~~~
87
+ src/parser.ts
88
+ ~~~
89
+
90
+ ## Criteria
91
+ - Valid input parses.
92
+ EOF
43
93
  ```
44
94
 
45
- The contract document carries `Title`, `Context`, `Objective`, `Design`,
46
- `Scope`, `Criteria`, and optional `Verification`. Add newly expected exact paths
47
- with `amend`; do not pre-authorize a broad tree for convenience.
95
+ The H1 is the Title. The H2 sections are `Context`, `Objective`,
96
+ `Design & Approach`, `Scope`, `Criteria`, and optional `Verification`. Use
97
+ `bind --task TASK_ID -` to bind a ready task with the same document shape. Add
98
+ newly expected exact paths with `amend`; do not pre-authorize a broad tree for
99
+ convenience.
48
100
 
49
101
  ## Select The Contract
50
102
 
@@ -81,16 +133,14 @@ keiyaku status
81
133
  - `amend` records a changed intent, invariant, Scope, Criteria, or Verification
82
134
  before implementation relies on that change.
83
135
 
84
- Scope changes are ordered terms. Use flags for simple additions or one
85
- `Scope Append` block for an ordered delta:
136
+ Scope changes are ordered terms. Use `--append-scope` for a simple addition or
137
+ one `Scope Append` block for an ordered delta:
86
138
 
87
- ````markdown
88
139
  ## Scope Append
89
- ```
140
+ ~~~
90
141
  src/new-owner.ts
91
142
  !src/retired/**
92
- ```
93
- ````
143
+ ~~~
94
144
 
95
145
  Later `!pattern` terms narrow earlier matches. Do not mix the block with
96
146
  `--append-scope` flags.
@@ -113,9 +163,14 @@ been reviewed again.
113
163
 
114
164
  ## Settle
115
165
 
116
- Before settlement, renew if the target moved, then rerun the declared evidence.
117
- `audit` is read-only; exit 0 means it produced a report, not that the candidate
118
- passed. Petition requires an oath and runs the settlement gates:
166
+ Commit, then use `renew --audit` when the delivery may be stale; it performs
167
+ the ordinary delivery rewrite first and audits the resulting fresh candidate.
168
+ Use `audit` directly when no renewal is needed. Audit already combines the
169
+ candidate diff, Scope, Criteria, Verification, and delivery context.
170
+
171
+ If the evidence is sufficient, petition. Otherwise, run only the check needed
172
+ for a specific gap. `not configured` is not itself a failure. Audit exit 0 means
173
+ a report was produced, not that the candidate passed.
119
174
 
120
175
  ```bash
121
176
  keiyaku audit
@@ -1,12 +0,0 @@
1
- export const meta = {
2
- command: "revive",
3
- summary: "Revive an Akuma helper session",
4
- usage: ["keiyaku revive ARTIFACT [BODY|-]"],
5
- stdin: {
6
- mode: "argument",
7
- selector: { kind: "positional", index: 1, required: false, rejectBlank: true },
8
- },
9
- flags: ["cwd", "repo", "model", "effort"],
10
- purpose: "Continue a persisted helper session.",
11
- completionRank: 4,
12
- };