@astrosheep/keiyaku 2.9.6 → 2.9.8

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 (87) hide show
  1. package/build/.tsbuildinfo +1 -1
  2. package/build/agents/harness/event-persistence.js +7 -5
  3. package/build/agents/harness/events.js +3 -2
  4. package/build/agents/harness/outcome.js +10 -0
  5. package/build/agents/providers/codex-app-server/adapter.js +6 -1
  6. package/build/agents/providers/codex-app-server/session.js +8 -7
  7. package/build/agents/selector.js +12 -1
  8. package/build/cli/commands/akuma/view/handler.js +3 -11
  9. package/build/cli/commands/contract/amend/handler.js +1 -1
  10. package/build/cli/commands/contract/amend/meta.js +4 -4
  11. package/build/cli/commands/contract/bind/handler.js +10 -4
  12. package/build/cli/commands/contract/bind/meta.js +6 -6
  13. package/build/cli/commands/contract/petition/handler.js +16 -3
  14. package/build/cli/commands/contract/petition/meta.js +4 -4
  15. package/build/cli/commands/metadata.js +3 -2
  16. package/build/cli/commands/projection/status/handler.js +5 -4
  17. package/build/cli/commands/projection/status/meta.js +2 -2
  18. package/build/cli/commands/projection/tell/handler.js +10 -2
  19. package/build/cli/commands/projection/tell/meta.js +3 -3
  20. package/build/cli/commands/task/add/meta.js +9 -1
  21. package/build/cli/commands/task/catalog.js +2 -0
  22. package/build/cli/commands/task/log/handler.js +12 -0
  23. package/build/cli/commands/task/log/meta.js +8 -0
  24. package/build/cli/commands/task/shared.js +2 -1
  25. package/build/cli/completion.js +8 -0
  26. package/build/cli/flags.js +8 -0
  27. package/build/cli/index.js +10 -6
  28. package/build/cli/parse-flags.js +6 -0
  29. package/build/cli/parse-metadata.js +1 -1
  30. package/build/cli/render/kanshi.js +2 -2
  31. package/build/cli/render/line-width.js +33 -0
  32. package/build/cli/render/path-prefix-compaction.js +131 -0
  33. package/build/cli/render/petition.js +4 -0
  34. package/build/cli/render/projection-activity.js +103 -21
  35. package/build/cli/render/shared.js +38 -15
  36. package/build/cli/render/status.js +8 -5
  37. package/build/cli/render/success-response.js +2 -0
  38. package/build/cli/render/tool-presentation.js +3 -3
  39. package/build/cli/render/wait.js +68 -48
  40. package/build/cli/subagent-guard.js +3 -0
  41. package/build/cli/types.js +1 -1
  42. package/build/config/settings/disease.js +4 -4
  43. package/build/config/settings/loader.js +44 -21
  44. package/build/core/addressing.js +40 -9
  45. package/build/core/amend.js +21 -5
  46. package/build/core/bind.js +111 -6
  47. package/build/core/call/context.js +19 -3
  48. package/build/core/call/execution.js +43 -20
  49. package/build/core/draft.js +1 -1
  50. package/build/core/ledger-batch.js +194 -0
  51. package/build/core/projection/generation/database.js +43 -2
  52. package/build/core/projection/generation/model.js +24 -3
  53. package/build/core/projection/generation/projection-generation-continuation.js +75 -10
  54. package/build/core/projection/generation/projection-generation-execution.js +46 -37
  55. package/build/core/projection/generation/projection-generation-launcher.js +148 -20
  56. package/build/core/projection/generation/projection-generation-process.js +3 -1
  57. package/build/core/projection/generation/projection-generation-runner.js +145 -66
  58. package/build/core/projection/generation/projection-generation-runtime.js +93 -11
  59. package/build/core/projection/generation/store.js +26 -1
  60. package/build/core/projection/generation/transitions.js +193 -14
  61. package/build/core/projection/index.js +5 -5
  62. package/build/core/projection/projection-core.js +1 -1
  63. package/build/core/projection/projection-kill.js +53 -10
  64. package/build/core/projection/projection-life-protocol.js +10 -0
  65. package/build/core/projection/projection-runner-lock.js +177 -37
  66. package/build/core/projection/projection-status.js +143 -55
  67. package/build/core/projection/projection-wait.js +53 -15
  68. package/build/core/projection/projection-wake.js +177 -46
  69. package/build/core/projection/tell/database.js +18 -0
  70. package/build/core/projection/tell/model.js +1 -0
  71. package/build/core/projection/tell/store.js +102 -55
  72. package/build/core/registry.js +82 -69
  73. package/build/core/scope.js +9 -9
  74. package/build/core/status/board.js +42 -4
  75. package/build/core/status/drift.js +21 -5
  76. package/build/core/status/ledger-batch.js +1 -158
  77. package/build/core/task/index.js +2 -2
  78. package/build/core/task/task-contract.js +18 -0
  79. package/build/core/task/task-git-runtime.js +8 -10
  80. package/build/core/task/task-git-store.js +55 -3
  81. package/build/core/worktree-path.js +39 -25
  82. package/build/flow-error.js +1 -1
  83. package/build/generated/version.js +2 -2
  84. package/build/git/refs.js +47 -1
  85. package/package.json +1 -1
  86. package/skills/keiyaku-akuma/SKILL.md +28 -0
  87. package/skills/keiyaku-workflow/SKILL.md +142 -18
@@ -105,6 +105,29 @@ export async function locateTaskBoard(cwd, runGitProbe = defaultRunGit) {
105
105
  function taskDirectory(board) {
106
106
  return path.join(board.root, ".keiyaku", "tasks");
107
107
  }
108
+ const TASK_HISTORY_FORMAT = "%aI%x00%an%x00%s";
109
+ function taskHistoryPath(taskId) {
110
+ return `.keiyaku/tasks/${taskId}.md`;
111
+ }
112
+ function parseTaskHistory(output) {
113
+ const fields = output.split("\0");
114
+ if (fields.at(-1) !== "") {
115
+ throw new FlowError("INTERNAL_STATE", "task history returned an incomplete record");
116
+ }
117
+ fields.pop();
118
+ if (fields.length % 3 !== 0) {
119
+ throw new FlowError("INTERNAL_STATE", "task history returned an incomplete record");
120
+ }
121
+ const entries = [];
122
+ for (let index = 0; index < fields.length; index += 3) {
123
+ entries.push({
124
+ timestamp: fields[index],
125
+ actor: fields[index + 1],
126
+ subject: fields[index + 2],
127
+ });
128
+ }
129
+ return entries;
130
+ }
108
131
  function assertKeiyakuDirectory(board) {
109
132
  const keiyakuDirectory = path.join(board.root, ".keiyaku");
110
133
  const stat = lstatIfPresent(keiyakuDirectory);
@@ -201,6 +224,33 @@ export async function inspectTaskLock(cwd) {
201
224
  closeQuietly(database);
202
225
  }
203
226
  }
227
+ /** Read the current hub branch's task-specific Git history without acquiring the task lock. */
228
+ export async function readTaskHistory(cwd, taskId, deps = {}) {
229
+ if (!isTaskId(taskId)) {
230
+ throw new FlowError("INVALID_TARGET", `task history requires a canonical task id: ${taskId}`);
231
+ }
232
+ const board = await locateTaskBoard(cwd, deps.runGit);
233
+ if (board.mode !== "git") {
234
+ throw new FlowError("NOT_GIT_REPO", "task history is available only in a Git repository");
235
+ }
236
+ const result = await (deps.runGit ?? defaultRunGit)(board.root, [
237
+ "log",
238
+ "-z",
239
+ "--topo-order",
240
+ "--reverse",
241
+ `--format=${TASK_HISTORY_FORMAT}`,
242
+ "--",
243
+ taskHistoryPath(taskId),
244
+ ]);
245
+ if (result.error) {
246
+ throw new FlowError("INTERNAL_STATE", `cannot read task history: ${result.error.message}`, { cause: result.error });
247
+ }
248
+ if (result.status !== 0) {
249
+ const detail = result.stderr.trim() || `exit ${String(result.status)}`;
250
+ throw new FlowError("INTERNAL_STATE", `cannot read task history: ${detail}`);
251
+ }
252
+ return parseTaskHistory(result.stdout);
253
+ }
204
254
  export function atomicReplaceTaskFile(filePath, text, _deps) {
205
255
  const directory = path.dirname(filePath);
206
256
  fs.mkdirSync(directory, { recursive: true });
@@ -345,8 +395,7 @@ function assertTaskDirectorySafety(directory) {
345
395
  throw new FlowError("INTERNAL_STATE", `task directory is not a regular directory: ${directory}`);
346
396
  }
347
397
  }
348
- export async function openTaskStore(cwd, deps = {}) {
349
- const board = await locateTaskBoard(cwd, deps.runGit);
398
+ export function openTaskStoreAt(board, deps = {}) {
350
399
  const directory = taskDirectory(board);
351
400
  return {
352
401
  mode: board.mode,
@@ -355,7 +404,7 @@ export async function openTaskStore(cwd, deps = {}) {
355
404
  return readTaskSources(directory);
356
405
  },
357
406
  async mutate(apply) {
358
- return withLockHeldTaskBoard(cwd, async (transaction) => {
407
+ return withLockHeldTaskBoard(board.root, async (transaction) => {
359
408
  const previousSources = transaction.sources;
360
409
  const plan = await apply(transaction.sources);
361
410
  validatePlan(plan);
@@ -387,6 +436,9 @@ export async function openTaskStore(cwd, deps = {}) {
387
436
  },
388
437
  };
389
438
  }
439
+ export async function openTaskStore(cwd, deps = {}) {
440
+ return openTaskStoreAt(await locateTaskBoard(cwd, deps.runGit), deps);
441
+ }
390
442
  export async function withLockHeldTaskBoard(cwd, apply, deps = {}, locatedBoard) {
391
443
  const board = locatedBoard ?? await locateTaskBoard(cwd, deps.runGit);
392
444
  const directory = taskDirectory(board);
@@ -3,6 +3,7 @@ import * as fs from "node:fs/promises";
3
3
  import { FlowError } from "../flow-error.js";
4
4
  import { createGit, wrapGitError } from "../git/core.js";
5
5
  import { readLedger } from "./ledger.js";
6
+ import { readLedgerSnapshot } from "./ledger-batch.js";
6
7
  import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
7
8
  import { normalizeFilesystemCoordinate } from "../fs/path-coordinate.js";
8
9
  import { gitCommonRootPath, resolveGitCommonRoot } from "../git/path-coordinate.js";
@@ -73,7 +74,10 @@ export async function stableRepoRoot(cwd) {
73
74
  }
74
75
  }
75
76
  export async function contractWorktreePathFromBind(cwd, contractId, bind) {
76
- return path.join(await stableRepoRoot(cwd), ".keiyaku", "wt", bind?.data.place ?? contractId);
77
+ return contractWorktreePathFromStableRoot(await stableRepoRoot(cwd), contractId, bind);
78
+ }
79
+ function contractWorktreePathFromStableRoot(stableRoot, contractId, bind) {
80
+ return path.join(stableRoot, ".keiyaku", "wt", bind?.data.place ?? contractId);
77
81
  }
78
82
  export async function contractWorktreePath(cwd, contractId) {
79
83
  const ledger = await readLedger(cwd, contractId);
@@ -110,7 +114,7 @@ export async function findRegisteredContractWorktreePath(cwd, contractId) {
110
114
  return undefined;
111
115
  let expected;
112
116
  try {
113
- expected = await fs.realpath(await contractWorktreePathFromBind(stableRoot, contractId, bind));
117
+ expected = await fs.realpath(contractWorktreePathFromStableRoot(stableRoot, contractId, bind));
114
118
  }
115
119
  catch {
116
120
  return undefined;
@@ -139,48 +143,58 @@ export async function resolveContractDeliveryCwd(cwd, contractId) {
139
143
  * terminal state is optional so residual worktrees after cleanup still bind.
140
144
  */
141
145
  async function contractIdForEngineOwnedWorktree(cwd, options) {
142
- const stableRoot = await stableRepoRoot(cwd);
146
+ const stableRoot = options.stableRoot ?? await stableRepoRoot(cwd);
143
147
  const canonicalCwd = await fs.realpath(cwd);
148
+ const candidates = [];
144
149
  for (const worktree of await listWorktrees(stableRoot)) {
145
150
  const branchPrefix = "refs/heads/keiyaku/";
146
151
  if (!worktree.branch?.startsWith(branchPrefix))
147
152
  continue;
148
- const contractId = worktree.branch.slice(branchPrefix.length);
149
- const ledger = await readLedger(stableRoot, contractId);
150
- if (!ledger)
151
- continue;
152
- if (options.requireActive && isTerminalState(deriveContractState(ledger.entries)))
153
- continue;
154
- const bind = findBindEntry(ledger.entries);
155
- if (!bind || bind.data.workspace !== "worktree")
156
- continue;
157
153
  let canonicalWorktree;
158
- let expectedWorktree;
159
154
  try {
160
155
  canonicalWorktree = await fs.realpath(worktree.path);
161
- expectedWorktree = await fs.realpath(await contractWorktreePathFromBind(stableRoot, contractId, bind));
162
156
  }
163
157
  catch {
164
158
  continue;
165
159
  }
166
- if (canonicalWorktree !== expectedWorktree)
160
+ if (!worktreeContainsCoordinate(canonicalWorktree, canonicalCwd))
167
161
  continue;
168
- if (worktreeContainsCoordinate(canonicalWorktree, canonicalCwd))
169
- return contractId;
162
+ candidates.push({
163
+ contractId: worktree.branch.slice(branchPrefix.length),
164
+ path: canonicalWorktree,
165
+ });
170
166
  }
171
- return undefined;
167
+ const candidate = candidates.sort((left, right) => right.path.length - left.path.length)[0];
168
+ if (!candidate)
169
+ return undefined;
170
+ const ledger = await readLedgerSnapshot(stableRoot, candidate.contractId);
171
+ if (!ledger)
172
+ return undefined;
173
+ if (options.requireActive && isTerminalState(deriveContractState(ledger.entries)))
174
+ return undefined;
175
+ const bind = findBindEntry(ledger.entries);
176
+ if (!bind || bind.data.workspace !== "worktree")
177
+ return undefined;
178
+ let expectedWorktree;
179
+ try {
180
+ expectedWorktree = await fs.realpath(contractWorktreePathFromStableRoot(stableRoot, candidate.contractId, bind));
181
+ }
182
+ catch {
183
+ return undefined;
184
+ }
185
+ return candidate.path === expectedWorktree ? candidate.contractId : undefined;
172
186
  }
173
187
  /**
174
188
  * Resolve the contract bound by an engine-owned worktree containing cwd,
175
- * including when the ledger is already terminal. Task CLI contract context
176
- * uses this residual binding; call attachment never infers from cwd.
189
+ * including when the ledger is already terminal. Task CLI context uses this
190
+ * residual binding; call uses the active-only sibling below.
177
191
  */
178
- export async function contractBoundByEngineWorktree(cwd) {
179
- return contractIdForEngineOwnedWorktree(cwd, { requireActive: false });
192
+ export async function contractBoundByEngineWorktree(cwd, stableRoot) {
193
+ return contractIdForEngineOwnedWorktree(cwd, { requireActive: false, stableRoot });
180
194
  }
181
- /** Existing-contract verbs may infer only a verified, active engine binding. */
182
- export async function activeContractBoundByEngineWorktree(cwd) {
183
- return contractIdForEngineOwnedWorktree(cwd, { requireActive: true });
195
+ /** Existing-contract verbs and no-selector call attachment use only this verified active binding. */
196
+ export async function activeContractBoundByEngineWorktree(cwd, stableRoot) {
197
+ return contractIdForEngineOwnedWorktree(cwd, { requireActive: true, stableRoot });
184
198
  }
185
199
  export async function cleanupContractWorkspace(cwd, contractId) {
186
200
  const stableRoot = await stableRepoRoot(cwd);
@@ -20,7 +20,7 @@ export function renderTemplate(template, values) {
20
20
  }
21
21
  function formatInvalidSettingsDiseaseHints(diseases) {
22
22
  return diseases.map((disease) => {
23
- const where = disease.knob ? `${disease.source}:${disease.knob}` : `${disease.source}:(root)`;
23
+ const where = disease.knob ? `${disease.coordinate}:${disease.knob}` : `${disease.coordinate}:(root)`;
24
24
  return `${where}: ${disease.reason}`;
25
25
  });
26
26
  }
@@ -1,3 +1,3 @@
1
1
  // Generated into build output by scripts/build.mjs
2
- export const VERSION = "2.9.6";
3
- export const GIT_HASH = "cb7f9ef";
2
+ export const VERSION = "2.9.8";
3
+ export const GIT_HASH = "c9cfce1";
package/build/git/refs.js CHANGED
@@ -1,5 +1,51 @@
1
- import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, wrapGitError } from "./core.js";
1
+ import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, runGitProcess, wrapGitError, } from "./core.js";
2
2
  const ZERO_OBJECT_ID = "0000000000000000000000000000000000000000";
3
+ /** Normalize a configured branch to the sole local-branch ref spelling. */
4
+ export function normalizeLocalBranchRef(branch) {
5
+ return branch.startsWith("refs/heads/") ? branch : `refs/heads/${branch}`;
6
+ }
7
+ function isMissingExactRef(result) {
8
+ return result.status === 1 && result.stdout === "";
9
+ }
10
+ function parseShowRefRows(output) {
11
+ const lines = output.split(/\r?\n/);
12
+ if (lines.at(-1) === "")
13
+ lines.pop();
14
+ if (lines.length === 0 || lines.some((line) => line.length === 0))
15
+ return null;
16
+ const rows = [];
17
+ for (const line of lines) {
18
+ const match = /^(?<objectId>[0-9a-fA-F]{40}|[0-9a-fA-F]{64}) (?<ref>refs\/\S+)$/.exec(line);
19
+ if (!match?.groups)
20
+ return null;
21
+ rows.push({ objectId: match.groups.objectId, ref: match.groups.ref });
22
+ }
23
+ return rows;
24
+ }
25
+ function throwGitProcessFailure(commandLabel, result, cwd) {
26
+ const failure = result.error ?? Object.assign(new Error(result.stderr || `git ${commandLabel} exited ${result.status}`), {
27
+ stderr: result.stderr,
28
+ stdout: result.stdout,
29
+ });
30
+ throw wrapGitError(commandLabel, failure, cwd);
31
+ }
32
+ /**
33
+ * Resolve only a local branch through the argv-based Git boundary. A configured
34
+ * short name is never allowed to fall through Git's revision search rules.
35
+ */
36
+ export async function resolveLocalBranchHead(cwd, branch, run = runGitProcess) {
37
+ const ref = normalizeLocalBranchRef(branch);
38
+ const result = await run(cwd, ["show-ref", ref]);
39
+ if (isMissingExactRef(result))
40
+ return null;
41
+ if (result.status === 0) {
42
+ const exactRows = parseShowRefRows(result.stdout)?.filter((row) => row.ref === ref);
43
+ if (exactRows?.length === 1) {
44
+ return { ref, head: exactRows[0].objectId };
45
+ }
46
+ }
47
+ return throwGitProcessFailure(`show-ref ${ref}`, result, cwd);
48
+ }
3
49
  async function resolveRefOrNull(cwd, ref) {
4
50
  const git = createGit(cwd);
5
51
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/keiyaku",
3
- "version": "2.9.6",
3
+ "version": "2.9.8",
4
4
  "description": "CLI for running iterative keiyaku workflows with Codex subagents.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -19,6 +19,24 @@ Rule of thumb: continue the same conversation with its projection id. Use an
19
19
  artifact id with `revive` only when deliberately starting a new projection;
20
20
  failed/dead state alone is not a reason to revive.
21
21
 
22
+ ## Projection aliases
23
+
24
+ When a lane will be supervised repeatedly, assign a short stable alias at call
25
+ time and use it as the operator coordination handle:
26
+
27
+ ```bash
28
+ keiyaku call worker-akuma --alias release-lane --detach "implement the brief"
29
+ keiyaku wait release-lane --timeout 10m
30
+ keiyaku tell release-lane "run the focused checks before handoff"
31
+ keiyaku kill release-lane
32
+ ```
33
+
34
+ Aliases are per selected projection ledger, not projection identity. Keep the
35
+ full `name/8hex` ID in durable records and exact recovery. Reusing an alias
36
+ word atomically moves only that per-ledger ref to the new projection; it does
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.
39
+
22
40
  ## Common usage
23
41
 
24
42
  ```bash
@@ -30,6 +48,8 @@ keiyaku call NAME --detach "do the thing" # fire and forget; note the projec
30
48
  keiyaku call NAME - < prompt.md # long prompt: literal `-` reads stdin
31
49
  keiyaku wait <proj-id> # join a running one; prints its activity
32
50
  keiyaku wait <proj-id> --timeout 5m # peek for 5m; timeout = snapshot, not death
51
+ keiyaku wait <proj-a> <proj-b> --any # return when the first member finishes
52
+ keiyaku wait <proj-a> <proj-b> --all # return when every member finishes
33
53
  keiyaku tell <proj-id> [--wait DURATION] <BODY|-> # optionally tell, then bounded-join
34
54
  keiyaku revive rsp_XXXX "continue..." # new run continuing from a record
35
55
  keiyaku kill <proj-id> # stop it; record survives, revive still works
@@ -50,10 +70,18 @@ Use `keiyaku akuma view NAME` to inspect the resolved source and configuration.
50
70
  - Omit tell's bracketed `--wait DURATION` option for silent success: exit 0,
51
71
  zero output. Supply it to perform the ordinary bounded join after acceptance.
52
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.
53
77
  - Body is required for `call`/`tell`. Literal `-` selects stdin; unselected
54
78
  piped bytes are ignored and cannot change an argv body.
55
79
  - A `--wait`/`--timeout` expiry prints a live snapshot and exits 0 — the run continues; rejoin with `wait` anytime.
56
80
  - `wait` is repeatable: on a finished run it shows the final transcript again.
81
+ - Supervise multiple projections with one plural `wait`. Choose `--any` to
82
+ handle the first terminal member and refill its lane; choose `--all` only for
83
+ final batch convergence. Keep foreground waits blocking; if bounded, use one
84
+ operationally meaningful timeout instead of repeated short-timeout polling.
57
85
 
58
86
  ## Occasional flags
59
87
 
@@ -13,7 +13,8 @@ How a contract lives. Running akuma is the `keiyaku-akuma` skill; task planning
13
13
  - A contract is one delivery intent with one ledger, branch, and linked worktree.
14
14
  - Workers edit and test; the host reviews their dirty tree and commits accepted bytes.
15
15
  - `audit` reads one pinned candidate. `petition` is the settlement action.
16
- - Scope is an audit boundary, not a write lock. Amend changed intent before delivery.
16
+ - Scope is the smallest closure of files currently expected to be written. Amend
17
+ newly expected exact paths before delivery.
17
18
  - An arc is a named story chapter for one coherent stretch inside the contract.
18
19
 
19
20
  Lifecycle:
@@ -22,11 +23,86 @@ Lifecycle:
22
23
  bound → active → petitioned → claimed | forfeited
23
24
  ```
24
25
 
26
+ ## Before Bind
27
+
28
+ `bind` begins implementation from a settled executable contract.
29
+
30
+ Before binding, complete the fact and decision work needed to state the delivery:
31
+
32
+ - reproduce or otherwise establish the motivating fact;
33
+ - read the registered authority and locate the current owning modules;
34
+ - settle inputs, outputs, state consequences, failure behavior, invariants, and
35
+ forbidden expansion;
36
+ - identify the smallest expected write set and account for known write overlap
37
+ and delivery dependencies; ownership remains with the authority registry and
38
+ its owning chapter;
39
+ - write checks that prove the critical success path, the highest-risk rejection,
40
+ and the relevant durability, concurrency, or recovery boundary.
41
+
42
+ Use tasks, fact scans, and bare read-only exploration while gathering evidence
43
+ and settling decisions. Task `ready` reports that its dependencies are
44
+ satisfied; the coordinator promotes it after establishing semantic contract
45
+ readiness. Bind when Objective, Scope, and Checks describe one complete
46
+ implementation journey and contain every product or architecture decision the
47
+ worker needs.
48
+
49
+ After bind, the worker implements and verifies that contract. `amend` records
50
+ new evidence or a change of intent that genuinely emerges during implementation
51
+ or review, then implementation continues from the updated contract.
52
+
53
+ ## Choosing Scope
54
+
55
+ Scope names the files this contract is currently expected to write, not a
56
+ territory the contract owns. Choose the narrowest form supported by the
57
+ evidence, in this order:
58
+
59
+ 1. When the write set is known, list exact repository-relative paths, one per
60
+ line. This is the default and needs no justification.
61
+ 2. When filenames are not yet known but changes are scattered within one
62
+ directory, use a one-component wildcard for that component.
63
+ 3. Use the recursive wildcard only for a real whole-subtree rewrite, migration,
64
+ or generated-output operation. The Objective must state why the entire tree
65
+ is expected to change.
66
+
67
+ Known files use exact paths:
68
+
69
+ ```bash
70
+ keiyaku bind --place fix-pump --objective "make the pump test pass" \
71
+ --scope "src/pump.ts" --scope "tests/unit/pump.test.ts" \
72
+ --checks "pump tests green"
73
+ ```
74
+
75
+ Unknown filenames within one component may use a one-component wildcard:
76
+
77
+ ```bash
78
+ keiyaku bind --place repair-pump-fixtures --objective "repair the affected pump fixtures" \
79
+ --scope "tests/fixtures/pump/*" --checks "pump fixture tests green"
80
+ ```
81
+
82
+ A recursive pattern is reserved for an actual whole-tree operation:
83
+
84
+ ```bash
85
+ keiyaku bind --place regenerate-api-docs \
86
+ --objective "regenerate API documentation because the generator changes every file under docs/generated" \
87
+ --scope "docs/generated/**" --checks "generated API docs are current"
88
+ ```
89
+
90
+ If implementation evidence later identifies one more expected file, amend with
91
+ that exact path:
92
+
93
+ ```bash
94
+ keiyaku amend --append-scope "src/pump/metrics.ts" - < amendment.md
95
+ ```
96
+
97
+ Amend is cheap. Broad Scope consumes parallel coordination and weakens audit
98
+ signal, so moving to a broader pattern needs a reason; moving narrower does not.
99
+
25
100
  ## The default flow
26
101
 
27
102
  ```bash
28
103
  keiyaku bind --place fix-pump --objective "make the pump test pass" \
29
- --scope "src/pump/**" --checks "pump tests green"
104
+ --scope "src/pump.ts" --scope "tests/unit/pump.test.ts" \
105
+ --checks "pump tests green"
30
106
  # or the same four fields as Markdown: keiyaku bind - < contract.md
31
107
  # (# <name> / ## Objective / ## Scope / ## Checks)
32
108
  # or promote one or more ready tasks:
@@ -51,8 +127,9 @@ EOF
51
127
  # The oath is required (OATH_MISSING otherwise) and checked for presence, not content.
52
128
  ```
53
129
 
54
- A task is optional planning, not a prerequisite. Bind directly for new work;
55
- use `--task` only when promoting existing tracked planning into this contract.
130
+ A task is optional planning, not a prerequisite. Bind settled new work directly;
131
+ use `--task` when promoting existing tracked planning after the same readiness
132
+ judgment.
56
133
 
57
134
  That's the whole small case: bind → worker dirty-tree handoff → host review/commit → petition.
58
135
 
@@ -62,7 +139,7 @@ contracts/worktrees; it does not replace `needs` or contract `--after` ordering.
62
139
 
63
140
  `audit` is read-only and never invokes an Akuma or settles the contract. Exit 0
64
141
  means it constructed the report, not that the candidate is acceptable. Use
65
- `--diff-budget BYTES` for bounded output and `--json` for complete typed facts.
142
+ `--diff-budget BYTES` when bounded diff evidence is useful.
66
143
 
67
144
  Commissioned workers never write Git metadata. The host alone reviews and
68
145
  commits accepted bytes before `arc`, `renew`, or `petition`.
@@ -73,6 +150,46 @@ worker to read the worktree-root `.keiyaku/KEIYAKU.md` first and follow its
73
150
  objective, scope, and checks. The file is the contract-body carrier; do not
74
151
  copy its rendered body into a dispatch prompt.
75
152
 
153
+ ## Review findings
154
+
155
+ Review findings are evidence for diagnosis, not a queue of local patches. Before
156
+ changing code, classify each finding as a contract hole, missing invariant or
157
+ owner, execution slip, test gap, or provider/capability failure. A second
158
+ finding in the same transition, a race that crosses module owners, or a finding
159
+ that contradicts the contract is a root-cause signal: stop symptom patching,
160
+ record the governing invariant and ownership correction with `amend`, and
161
+ refactor the boundary before sending another tell or accepting another diff.
162
+
163
+ A default review dispatch is:
164
+
165
+ > First read the worktree-root `.keiyaku/KEIYAKU.md`. Review the candidate end
166
+ > to end against the contract and repository law. On every review round, treat
167
+ > the current candidate as a new complete implementation: reassess its overall
168
+ > coherence and regression risks rather than checking only whether prior
169
+ > findings were patched. Report material findings first. For each problem,
170
+ > explain the underlying cause and recommend a root-cause correction direction,
171
+ > not a patch-sized edit. If the evidence is insufficient, state what remains
172
+ > unknown. Do not modify files or Git metadata.
173
+
174
+ Add candidate-specific evidence only when it helps the reviewer judge coverage;
175
+ do not copy the contract body or seed an expected defect. Reviewer suggestions
176
+ inform the coordinator's judgment but do not amend the contract. The
177
+ coordinator owns the final diagnosis; a worker may implement a settled
178
+ correction but may not invent a new state machine or compatibility rule.
179
+
180
+ Do not claim a contract while related findings remain unresolved merely because
181
+ each individual test or patch passes. Re-run the focused interleaving or
182
+ boundary evidence and an independent review against the corrected ownership
183
+ model. Repeated tells that only add guards to the same disputed path are not
184
+ progress; if the contract cannot express the required invariant, amend or
185
+ forfeit and bind a coherent replacement.
186
+
187
+ After a worker fixes review findings, prefer telling the same review projection
188
+ to re-check those findings and the corrected candidate. Start a fresh reviewer
189
+ only when the earlier projection is unavailable or incompatible, its context is
190
+ no longer trustworthy, or a genuinely fresh independent perspective is useful.
191
+ This is an efficiency preference, not an acceptance gate.
192
+
76
193
  ## Mid-flight commands (use when needed, skip otherwise)
77
194
 
78
195
  ```bash
@@ -82,32 +199,39 @@ keiyaku arc - < arc.md # optional iteration boundary: seals the current in
82
199
  keiyaku renew # main moved under you → rebase onto it. Refuses on conflict
83
200
  # instead of guessing; resolve, then renew again.
84
201
  keiyaku amend - < amendment.md # scope/checks changed → update the paper before the code
85
- # Scope terms can be appended durably during a lifecycle operation:
86
- keiyaku @fix-pump arc --append-scope "docs/generated/**" - < arc.md
87
- keiyaku @fix-pump renew --append-scope "docs/generated/**"
88
202
  keiyaku log # this contract's history
89
203
  ```
90
204
 
91
- `amend` accepts prose plus an optional ordered scope-pattern delta:
205
+ `amend` accepts prose plus one optional ordered scope-pattern delta. Use either
206
+ repeated flags:
207
+
208
+ ```sh
209
+ keiyaku amend --append-scope "docs/api-reference.md" --append-scope "!docs/obsolete-api-reference.md" - < amendment.md
210
+ ```
211
+
212
+ or a document section:
92
213
 
93
214
  ````markdown
94
- Clarify the owned documentation surface.
215
+ Clarify the newly expected documentation files.
95
216
 
96
217
  ## Scope Append
97
- ```
98
- docs/**
99
- !docs/generated/**
100
- ```
218
+ ~~~
219
+ docs/api-reference.md
220
+ !docs/obsolete-api-reference.md
221
+ ~~~
101
222
  ````
102
223
 
103
224
  `Scope Append` appends ordered raw gitignore-subset lines in one column-zero
104
- backtick fence; `Scope Add` and Markdown bullets are rejected. A later
105
- `!pattern` can narrow earlier scope. Use `--append-scope PATTERN` when a
106
- lifecycle operation discovers one durable term.
225
+ tilde fence; direct input also accepts a matching backtick fence. `Scope Add`
226
+ and Markdown bullets are rejected. A later
227
+ `!pattern` can narrow earlier scope. Flags and a document Scope Append block
228
+ are mutually exclusive; plain amendment prose plus flags is valid. Use
229
+ `--append-scope PATTERN` when a lifecycle operation discovers one durable term.
107
230
 
108
231
  Gotchas:
109
232
  - `-C DIR` selects the effective working directory; repository discovery starts there.
110
- - Base drift blocks petition — if main moved, `renew` first; petition tells you.
233
+ - Base drift blocks ordinary petition. `petition --renew` runs the same renew
234
+ first and settles only after that renew succeeds; it never auto-opens an arc.
111
235
  - After an explicit `arc` seals, new loose commits need another `arc` before `renew`/`petition` accept them.
112
236
  - Not in the worktree? Address a contract with `--contract ADDR` (place, slug, or full id) or the `keiyaku @addr <verb>` prefix.
113
237