@astrosheep/keiyaku 2.9.12 → 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 (212) hide show
  1. package/README.md +22 -18
  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 +2 -2
  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/petition/handler.js +2 -1
  17. package/build/cli/commands/contract/petition/meta.js +2 -2
  18. package/build/cli/commands/contract/renew/handler.js +44 -3
  19. package/build/cli/commands/contract/renew/meta.js +5 -4
  20. package/build/cli/commands/metadata.js +12 -9
  21. package/build/cli/commands/projection/call/handler.js +1 -1
  22. package/build/cli/commands/projection/call/meta.js +1 -1
  23. package/build/cli/commands/projection/catalog.js +2 -2
  24. package/build/cli/commands/projection/{revive → fork}/handler.js +10 -5
  25. package/build/cli/commands/projection/fork/meta.js +12 -0
  26. package/build/cli/commands/projection/history/handler.js +46 -2
  27. package/build/cli/commands/projection/history/meta.js +3 -3
  28. package/build/cli/commands/projection/kill/meta.js +2 -2
  29. package/build/cli/commands/projection/status/handler.js +29 -13
  30. package/build/cli/commands/projection/status/meta.js +14 -3
  31. package/build/cli/commands/projection/tell/handler.js +8 -4
  32. package/build/cli/commands/projection/tell/meta.js +1 -1
  33. package/build/cli/commands/projection/wait/handler.js +45 -20
  34. package/build/cli/commands/projection/wait/meta.js +1 -1
  35. package/build/cli/commands/shared.js +1 -1
  36. package/build/cli/commands/system/catalog.js +2 -0
  37. package/build/cli/commands/task/add/handler.js +28 -12
  38. package/build/cli/commands/task/add/meta.js +18 -6
  39. package/build/cli/commands/task/catalog.js +2 -0
  40. package/build/cli/commands/task/compose/handler.js +2 -2
  41. package/build/cli/commands/task/compose/meta.js +2 -2
  42. package/build/cli/commands/task/doctor/handler.js +2 -3
  43. package/build/cli/commands/task/doctor/meta.js +2 -2
  44. package/build/cli/commands/task/done/meta.js +2 -2
  45. package/build/cli/commands/task/drop/handler.js +7 -2
  46. package/build/cli/commands/task/drop/meta.js +6 -3
  47. package/build/cli/commands/task/hold/meta.js +2 -2
  48. package/build/cli/commands/task/log/meta.js +2 -2
  49. package/build/cli/commands/task/ls/meta.js +2 -2
  50. package/build/cli/commands/task/note/handler.js +6 -0
  51. package/build/cli/commands/task/note/meta.js +11 -0
  52. package/build/cli/commands/task/resume/meta.js +2 -2
  53. package/build/cli/commands/task/shared.js +29 -15
  54. package/build/cli/commands/task/show/meta.js +2 -2
  55. package/build/cli/commands/task/start/meta.js +2 -2
  56. package/build/cli/commands/task/stop/meta.js +2 -2
  57. package/build/cli/commands/task/update/meta.js +3 -2
  58. package/build/cli/commands/verification/handler.js +43 -0
  59. package/build/cli/commands/verification/meta.js +13 -0
  60. package/build/cli/completion.js +16 -13
  61. package/build/cli/flags.js +43 -11
  62. package/build/cli/help.js +29 -30
  63. package/build/cli/index.js +6 -5
  64. package/build/cli/parse-flags.js +39 -0
  65. package/build/cli/parse-metadata.js +5 -2
  66. package/build/cli/parse-selectors.js +18 -0
  67. package/build/cli/parse.js +21 -8
  68. package/build/cli/render/arc.js +12 -3
  69. package/build/cli/render/audit.js +2 -2
  70. package/build/cli/render/call.js +4 -4
  71. package/build/cli/render/kanshi.js +122 -85
  72. package/build/cli/render/misc.js +2 -2
  73. package/build/cli/render/petition.js +16 -1
  74. package/build/cli/render/projection-history.js +17 -4
  75. package/build/cli/render/shared.js +5 -5
  76. package/build/cli/render/status-indicator.js +47 -0
  77. package/build/cli/render/status.js +106 -76
  78. package/build/cli/render/success-response.js +20 -5
  79. package/build/cli/render/task.js +6 -5
  80. package/build/cli/render/tell.js +10 -4
  81. package/build/cli/render/tool-command-normalization.js +137 -0
  82. package/build/cli/render/tool-presentation.js +2 -1
  83. package/build/cli/render/verification.js +23 -0
  84. package/build/cli/render/wait.js +101 -33
  85. package/build/cli/types.js +2 -1
  86. package/build/config/akuma-loader.js +21 -1
  87. package/build/config/settings/knobs.js +7 -1
  88. package/build/config/settings/schema.js +10 -5
  89. package/build/core/amend.js +102 -17
  90. package/build/core/arc.js +4 -4
  91. package/build/core/audit/candidate.js +10 -18
  92. package/build/core/audit/coordinates.js +12 -17
  93. package/build/core/audit/evidence.js +6 -4
  94. package/build/core/audit/facade.js +3 -3
  95. package/build/core/audit/report.js +1 -1
  96. package/build/core/bind-reconciliation.js +42 -23
  97. package/build/core/bind.js +182 -81
  98. package/build/core/call/call.js +6 -7
  99. package/build/core/call/context.js +43 -30
  100. package/build/core/call/execution.js +21 -6
  101. package/build/core/call/prompt.js +11 -42
  102. package/build/core/contract-carrier-runtime.js +209 -105
  103. package/build/core/contract-carrier.js +156 -73
  104. package/build/core/contract-view.js +42 -5
  105. package/build/core/contract.js +2 -1
  106. package/build/core/derived-replay.js +168 -35
  107. package/build/core/draft.js +9 -5
  108. package/build/core/forfeit.js +1 -1
  109. package/build/core/lifecycle-history.js +13 -0
  110. package/build/core/lifecycle-recovery.js +32 -27
  111. package/build/core/lifecycle-runner.js +14 -14
  112. package/build/core/log.js +1 -1
  113. package/build/core/outcome-base.js +2 -2
  114. package/build/core/projection/akuma-name.js +43 -0
  115. package/build/core/projection/generation/database.js +141 -79
  116. package/build/core/projection/generation/ledger.js +91 -0
  117. package/build/core/projection/generation/model.js +18 -13
  118. package/build/core/projection/generation/projection-generation-continuation.js +1 -1
  119. package/build/core/projection/generation/projection-generation-doorbell.js +88 -0
  120. package/build/core/projection/generation/projection-generation-execution.js +24 -0
  121. package/build/core/projection/generation/projection-generation-identity.js +4 -0
  122. package/build/core/projection/generation/projection-generation-launcher.js +72 -138
  123. package/build/core/projection/generation/projection-generation-process.js +16 -15
  124. package/build/core/projection/generation/projection-generation-runner.js +125 -79
  125. package/build/core/projection/generation/projection-generation-runtime.js +10 -61
  126. package/build/core/projection/generation/protocol.js +68 -0
  127. package/build/core/projection/generation/store.js +17 -7
  128. package/build/core/projection/generation/transitions.js +14 -14
  129. package/build/core/projection/index.js +5 -5
  130. package/build/core/projection/projection-core.js +6 -2
  131. package/build/core/projection/projection-execution-observer.js +1 -1
  132. package/build/core/projection/projection-history.js +7 -7
  133. package/build/core/projection/projection-kill.js +15 -6
  134. package/build/core/projection/projection-life-observer.js +8 -8
  135. package/build/core/projection/projection-mint.js +6 -8
  136. package/build/core/projection/projection-status-observation.js +77 -8
  137. package/build/core/projection/projection-status.js +151 -4
  138. package/build/core/projection/projection-wait.js +46 -27
  139. package/build/core/projection/projection-wake.js +27 -89
  140. package/build/core/projection/tell/launch-store.js +1 -1
  141. package/build/core/projection/tell/store.js +1 -1
  142. package/build/core/renew-build.js +204 -74
  143. package/build/core/renew-plan.js +34 -9
  144. package/build/core/renew-rewrite.js +56 -15
  145. package/build/core/renew.js +39 -35
  146. package/build/core/repository-ledger/accepted-fold-read.js +6 -3
  147. package/build/core/repository-ledger/codec.js +11 -5
  148. package/build/core/repository-ledger/current-state-store.js +13 -16
  149. package/build/core/repository-ledger/fold-repository.js +5 -6
  150. package/build/core/repository-ledger/identity.js +60 -0
  151. package/build/core/repository-ledger/inventory.js +3 -3
  152. package/build/core/repository-ledger/read-model.js +54 -6
  153. package/build/core/repository-ledger/write-transaction.js +9 -4
  154. package/build/core/run-control/connection-bound-close.js +41 -0
  155. package/build/core/run-control/detached-lease-bootstrap.js +208 -0
  156. package/build/core/run-control/detached-process.js +14 -0
  157. package/build/core/run-control/index.js +6 -0
  158. package/build/core/{process-group.js → run-control/process-tree.js} +1 -1
  159. package/build/core/run-control/runner-lease.js +172 -0
  160. package/build/core/{sqlite-process-lifetime-lock.js → run-control/sqlite-process-lifetime-lock.js} +4 -24
  161. package/build/core/seal.js +44 -32
  162. package/build/core/settlement/claim-delivery.js +22 -9
  163. package/build/core/settlement/claim.js +8 -6
  164. package/build/core/settlement/petition-claim-gates.js +9 -8
  165. package/build/core/settlement/petition-head-guard.js +5 -6
  166. package/build/core/settlement/petition-preview.js +21 -5
  167. package/build/core/settlement/petition.js +51 -17
  168. package/build/core/settlement/settlement.js +20 -15
  169. package/build/core/settlement/verdict.js +2 -2
  170. package/build/core/settlement/verification.js +492 -364
  171. package/build/core/status/board.js +184 -75
  172. package/build/core/status/drift.js +8 -10
  173. package/build/core/task/board.js +1 -0
  174. package/build/core/task/commands.js +25 -0
  175. package/build/core/task/compose.js +3 -2
  176. package/build/core/task/document.js +102 -39
  177. package/build/core/task/index.js +3 -3
  178. package/build/core/task/settlement-git.js +1 -4
  179. package/build/core/task/settlement-policy.js +27 -14
  180. package/build/core/task/source-board.js +1 -0
  181. package/build/core/task/task-bind-preparation.js +59 -9
  182. package/build/core/task/task-contract.js +2 -2
  183. package/build/core/task/task-store-repository.js +1 -0
  184. package/build/core/task/task.js +2 -2
  185. package/build/core/transcripts.js +4 -4
  186. package/build/core/verification-declaration.js +95 -0
  187. package/build/core/worktree-bootstrap.js +1 -1
  188. package/build/core/worktree-path.js +7 -11
  189. package/build/flow-error.js +9 -1
  190. package/build/generated/version.js +2 -2
  191. package/build/git/branches.js +41 -70
  192. package/build/git/commits.js +92 -21
  193. package/build/git/core.js +9 -161
  194. package/build/git/diff/preview.js +4 -4
  195. package/build/git/diff/read.js +4 -4
  196. package/build/git/diff/structured.js +5 -5
  197. package/build/git/process.js +229 -0
  198. package/build/git/refs.js +30 -5
  199. package/build/git/staging.js +39 -115
  200. package/build/git/streaming-batch.js +9 -16
  201. package/build/git/worktree.js +104 -91
  202. package/build/index.js +7 -0
  203. package/package.json +1 -1
  204. package/skills/keiyaku-akuma/SKILL.md +21 -20
  205. package/skills/keiyaku-task/SKILL.md +38 -4
  206. package/skills/keiyaku-workflow/SKILL.md +51 -11
  207. package/build/cli/commands/projection/revive/meta.js +0 -12
  208. package/build/core/projection/projection-life-protocol.js +0 -115
  209. package/build/core/projection/projection-runner-lock.js +0 -211
  210. package/build/core/projection/tell/database.js +0 -127
  211. package/build/core/settlement/verification-coordination.js +0 -305
  212. 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.12",
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",
@@ -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,15 +43,15 @@ 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
54
+ keiyaku fork rsp_XXXX "continue..." # new run continuing from a retained record
55
55
  keiyaku kill <proj-id> # request a stop; returns an acceptance receipt
56
56
  keiyaku kill <proj-id> --wait 5m # request a stop, then observe the result for 5m
57
57
  keiyaku status # forgot an id? it's on the board
@@ -68,21 +68,22 @@ Use `keiyaku akuma show NAME` to inspect the resolved source and configuration.
68
68
 
69
69
  ## Expectations
70
70
 
71
- - Omit tell's bracketed `--wait DURATION` option for silent success: exit 0,
72
- zero output. Supply it to perform the ordinary bounded join after acceptance.
73
- Silence **is** the receipt do not re-send. Effects show up in `wait`/`status`.
74
- - `tell --interrupt` is intentionally destructive. On an active runner, silent
75
- success means the tell and interrupt intent are durable; provider stop and
76
- successor wake continue asynchronously under the same projection id. Idle or
77
- 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.
78
77
  - `kill` returns an acceptance receipt; it does not include the final result.
79
78
  Use `kill --wait DURATION` when you need to observe the ordinary final result.
80
79
  `DURATION` is required. A timeout ends observation only and does not undo the
81
80
  stop request.
82
81
  - Body is required for `call`/`tell`. Literal `-` selects stdin; unselected
83
82
  piped bytes are ignored and cannot change an argv body.
84
- - A `--wait`/`--timeout` expiry prints a live snapshot and exits 0 — the run continues; rejoin with `wait` anytime.
85
- - `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.
86
87
  - Supervise multiple projections with one plural `wait`. Choose `--any` to
87
88
  handle the first terminal member and refill its lane; choose `--all` only for
88
89
  final batch convergence. Keep foreground waits blocking; if bounded, use one
@@ -90,7 +91,7 @@ Use `keiyaku akuma show NAME` to inspect the resolved source and configuration.
90
91
 
91
92
  ## Occasional flags
92
93
 
93
- - `--incognito` — no artifact written, no revive possible (call)
94
- - `--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)
95
96
  - `--repo DIR` — override repository inference only when the contract ledger
96
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,6 +44,7 @@ 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
39
48
  ```
40
49
 
41
50
  ## Atomic reorganization
@@ -67,10 +76,30 @@ sketch grammar and receipt fields; this skill does not redefine task policy.
67
76
  ## Promoting to a contract
68
77
 
69
78
  ```bash
70
- keiyaku bind --task fix-parser
71
- 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
72
99
  ```
73
100
 
101
+ For a multi-task delivery, repeat `--task TASK_ID` on the same bind.
102
+
74
103
  Only ready tasks bind. Multi-bind only when the tasks describe one delivery
75
104
  intent, share a coherent write surface, and should settle together; otherwise
76
105
  keep separate contracts and express order with task `needs` or contract
@@ -82,3 +111,8 @@ is optional.
82
111
  ## Gotcha
83
112
 
84
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,8 +15,9 @@ 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.
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.
20
21
  - `audit` is the primary integrated review of one pinned candidate; `petition`
21
22
  asks to settle that candidate.
22
23
 
@@ -44,21 +45,58 @@ concurrent work is unsafe.
44
45
  Amend is cheap. Broad Scope consumes parallel coordination and weakens audit
45
46
  signal, so broader patterns need a reason while narrower terms do not.
46
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
+
47
65
  ## Bind
48
66
 
49
- Bind when one implementation journey is decided: motivating facts, owning
50
- modules, inputs and outputs, consequences, failure behavior, invariants,
51
- 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.
52
70
  Use a task only when planning or dependencies need a durable place.
53
71
 
54
72
  ```bash
55
- keiyaku bind - < contract.md
56
- keiyaku bind --task TASK_ID - < contract.md
73
+ keiyaku bind - <<'EOF'
74
+ # Fix parser
75
+
76
+ ## Context
77
+ Valid input is rejected.
78
+
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
57
93
  ```
58
94
 
59
- The contract document carries `Title`, `Context`, `Objective`, `Design & Approach`,
60
- `Scope`, `Criteria`, and optional `Verification`. Add newly expected exact paths
61
- 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.
62
100
 
63
101
  ## Select The Contract
64
102
 
@@ -125,7 +163,9 @@ been reviewed again.
125
163
 
126
164
  ## Settle
127
165
 
128
- Commit, renew if stale, then run `audit` first. Audit already combines the
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
129
169
  candidate diff, Scope, Criteria, Verification, and delivery context.
130
170
 
131
171
  If the evidence is sufficient, petition. Otherwise, run only the check needed
@@ -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
- };
@@ -1,115 +0,0 @@
1
- export const GENERATION_VERDICT_STATES = [
2
- "completed",
3
- "failed",
4
- "launch-failed",
5
- "lost-runner",
6
- "dismissed",
7
- "interrupted",
8
- "killed",
9
- ];
10
- function asLaunch(record) {
11
- return record.kind === "launch"
12
- ? record
13
- : undefined;
14
- }
15
- function asAdoption(record) {
16
- return record.kind === "adoption"
17
- ? record
18
- : undefined;
19
- }
20
- function asKillIntent(record) {
21
- return record.kind === "kill-intent"
22
- ? record
23
- : undefined;
24
- }
25
- function asInterruptIntent(record) {
26
- return record.kind === "interrupt-intent"
27
- ? record
28
- : undefined;
29
- }
30
- function asVerdict(record) {
31
- return record.kind === "verdict"
32
- ? record
33
- : undefined;
34
- }
35
- /**
36
- * Select the current generation using ledger sequence only. The store validates
37
- * dense sequence and transition legality before records reach this fold.
38
- */
39
- export function selectCurrentGeneration(records) {
40
- let launch;
41
- for (const record of records) {
42
- const candidate = asLaunch(record);
43
- if (candidate)
44
- launch = candidate;
45
- }
46
- if (!launch)
47
- return null;
48
- let adoption;
49
- let killIntent;
50
- let interruptIntent;
51
- let verdict;
52
- for (const record of records) {
53
- if (record.seq <= launch.seq || record.executionId !== launch.executionId)
54
- continue;
55
- adoption ??= asAdoption(record);
56
- killIntent ??= asKillIntent(record);
57
- interruptIntent ??= asInterruptIntent(record);
58
- verdict ??= asVerdict(record);
59
- }
60
- return {
61
- launch,
62
- ...(adoption ? { adoption } : {}),
63
- ...(killIntent ? { killIntent } : {}),
64
- ...(interruptIntent ? { interruptIntent } : {}),
65
- ...(verdict ? { verdict } : {}),
66
- };
67
- }
68
- function verdictState(record) {
69
- const state = record?.facts.state;
70
- return typeof state === "string"
71
- && GENERATION_VERDICT_STATES.includes(state)
72
- ? state
73
- : undefined;
74
- }
75
- function phaseForVerdict(state) {
76
- switch (state) {
77
- case "completed": return "done";
78
- case "dismissed": return "dismissed";
79
- case "interrupted": return "done";
80
- case "killed": return "killed";
81
- case "lost-runner": return "lost";
82
- case "failed":
83
- case "launch-failed":
84
- return "failed";
85
- }
86
- }
87
- export function deriveProjectionLife(input) {
88
- const chain = selectCurrentGeneration(input.records);
89
- if (!chain)
90
- return { phase: "quiescent" };
91
- const executionId = chain.launch.executionId;
92
- if (chain.verdict) {
93
- const state = verdictState(chain.verdict);
94
- return state
95
- ? { phase: phaseForVerdict(state), executionId, verdict: state }
96
- : { phase: "unknown", executionId, diagnostic: "current generation has an invalid verdict" };
97
- }
98
- if (!chain.adoption) {
99
- const createdAt = chain.launch.facts.createdAt;
100
- const launchedAtMs = typeof createdAt === "string" ? Date.parse(createdAt) : Number.NaN;
101
- if (!Number.isFinite(launchedAtMs)) {
102
- return { phase: "unknown", executionId, diagnostic: "current launch has an invalid createdAt fact" };
103
- }
104
- return input.nowMs - launchedAtMs >= input.startupTimeoutMs
105
- ? { phase: "startup-timeout", executionId }
106
- : { phase: "minting", executionId };
107
- }
108
- switch (input.runnerLock) {
109
- case "held": return { phase: "active", executionId };
110
- case "released": return { phase: "lost", executionId };
111
- case "unknown":
112
- case undefined:
113
- return { phase: "unknown", executionId, diagnostic: "runner lock could not be determined" };
114
- }
115
- }