@astrosheep/keiyaku 2.9.4 → 2.9.6

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 (143) hide show
  1. package/build/.tsbuildinfo +1 -1
  2. package/build/agents/call-terms.js +1 -0
  3. package/build/agents/harness/execution-handle.js +1 -0
  4. package/build/agents/harness/outcome.js +31 -5
  5. package/build/agents/harness/projection.js +151 -78
  6. package/build/agents/harness/router.js +2 -1
  7. package/build/agents/providers/claude-agent-sdk/adapter.js +13 -1
  8. package/build/agents/providers/claude-agent-sdk/registration.js +2 -1
  9. package/build/agents/providers/codex-app-server/adapter.js +43 -10
  10. package/build/agents/providers/codex-app-server/events.js +23 -0
  11. package/build/agents/providers/codex-app-server/registration.js +2 -1
  12. package/build/agents/providers/opencode-sdk/adapter.js +7 -0
  13. package/build/agents/providers/opencode-sdk/registration.js +2 -1
  14. package/build/agents/providers/pi/adapter.js +10 -0
  15. package/build/agents/providers/pi/events.js +4 -3
  16. package/build/agents/providers/pi/registration.js +3 -1
  17. package/build/agents/types.js +2 -0
  18. package/build/cli/commands/akuma/akuma/meta.js +1 -1
  19. package/build/cli/commands/akuma/list/meta-ls.js +1 -1
  20. package/build/cli/commands/akuma/view/meta.js +1 -1
  21. package/build/cli/commands/contract/amend/meta.js +4 -4
  22. package/build/cli/commands/contract/arc/handler.js +1 -1
  23. package/build/cli/commands/contract/arc/meta.js +4 -4
  24. package/build/cli/commands/contract/audit/handler.js +16 -0
  25. package/build/cli/commands/contract/audit/meta.js +16 -0
  26. package/build/cli/commands/contract/bind/meta.js +20 -11
  27. package/build/cli/commands/contract/catalog.js +2 -0
  28. package/build/cli/commands/contract/forfeit/meta.js +1 -1
  29. package/build/cli/commands/contract/log/meta.js +1 -1
  30. package/build/cli/commands/contract/petition/handler.js +1 -2
  31. package/build/cli/commands/contract/petition/meta.js +6 -6
  32. package/build/cli/commands/contract/renew/handler.js +2 -2
  33. package/build/cli/commands/contract/renew/meta.js +4 -4
  34. package/build/cli/commands/metadata.js +3 -2
  35. package/build/cli/commands/projection/call/meta.js +4 -1
  36. package/build/cli/commands/projection/kill/meta.js +1 -1
  37. package/build/cli/commands/projection/revive/meta.js +4 -1
  38. package/build/cli/commands/projection/status/meta.js +1 -1
  39. package/build/cli/commands/projection/tell/handler.js +1 -1
  40. package/build/cli/commands/projection/tell/meta.js +4 -1
  41. package/build/cli/commands/projection/wait/meta.js +1 -1
  42. package/build/cli/commands/skills/install/meta.js +1 -1
  43. package/build/cli/commands/skills/skills/meta.js +1 -1
  44. package/build/cli/commands/system/completion/meta.js +1 -1
  45. package/build/cli/commands/system/dump-env/meta.js +1 -1
  46. package/build/cli/commands/system/guide/meta.js +1 -1
  47. package/build/cli/commands/task/add/handler.js +2 -2
  48. package/build/cli/commands/task/add/meta.js +4 -1
  49. package/build/cli/commands/task/doctor/meta.js +1 -1
  50. package/build/cli/commands/task/done/meta.js +1 -1
  51. package/build/cli/commands/task/drop/meta.js +1 -1
  52. package/build/cli/commands/task/ls/handler.js +10 -4
  53. package/build/cli/commands/task/ls/meta.js +3 -3
  54. package/build/cli/commands/task/show/meta.js +1 -1
  55. package/build/cli/commands/task/start/meta.js +1 -1
  56. package/build/cli/commands/task/stop/meta.js +1 -1
  57. package/build/cli/commands/task/task/meta.js +1 -1
  58. package/build/cli/commands/task/update/handler.js +2 -3
  59. package/build/cli/commands/task/update/meta.js +4 -1
  60. package/build/cli/completion.js +9 -2
  61. package/build/cli/flags.js +19 -3
  62. package/build/cli/help.js +3 -3
  63. package/build/cli/index.js +59 -75
  64. package/build/cli/output-stream-error-policy.js +19 -5
  65. package/build/cli/parse-flags.js +19 -2
  66. package/build/cli/parse-metadata.js +1 -7
  67. package/build/cli/parse-selectors.js +1 -0
  68. package/build/cli/parse.js +16 -7
  69. package/build/cli/projection-address.js +6 -1
  70. package/build/cli/render/audit.js +121 -0
  71. package/build/cli/render/errors.js +18 -1
  72. package/build/cli/render/format.js +5 -2
  73. package/build/cli/render/misc.js +3 -1
  74. package/build/cli/render/projection-activity.js +12 -6
  75. package/build/cli/render/status.js +28 -18
  76. package/build/cli/render/success-response.js +6 -5
  77. package/build/cli/render/task.js +12 -43
  78. package/build/cli/render/terminal-failure.js +7 -2
  79. package/build/cli/render/wait.js +36 -8
  80. package/build/config/env-keys.js +2 -0
  81. package/build/config/env.js +3 -0
  82. package/build/core/amend.js +20 -94
  83. package/build/core/arc.js +14 -6
  84. package/build/core/audit/candidate.js +48 -0
  85. package/build/core/audit/coordinates.js +180 -0
  86. package/build/core/audit/evidence.js +158 -0
  87. package/build/core/audit/facade.js +42 -0
  88. package/build/core/audit/readiness.js +4 -0
  89. package/build/core/audit/report.js +38 -0
  90. package/build/core/audit.js +1 -0
  91. package/build/core/bind.js +1 -1
  92. package/build/core/call/execution.js +1 -0
  93. package/build/core/contract-view.js +2 -3
  94. package/build/core/draft.js +33 -20
  95. package/build/core/entry.js +3 -4
  96. package/build/core/markdown/lex.js +2 -1
  97. package/build/core/projection/generation/database.js +41 -16
  98. package/build/core/projection/generation/model.js +22 -2
  99. package/build/core/projection/generation/projection-generation-execution.js +20 -7
  100. package/build/core/projection/generation/projection-generation-launcher.js +4 -4
  101. package/build/core/projection/generation/projection-generation-runner.js +1 -0
  102. package/build/core/projection/generation/store.js +1 -1
  103. package/build/core/projection/generation/transitions.js +19 -3
  104. package/build/core/projection/index.js +2 -2
  105. package/build/core/projection/projection-execution-observer.js +35 -21
  106. package/build/core/projection/projection-life-observer.js +16 -2
  107. package/build/core/projection/projection-status.js +67 -22
  108. package/build/core/projection/projection-terminal-failure.js +2 -0
  109. package/build/core/projection/projection-wait.js +6 -9
  110. package/build/core/projection/projection-wake.js +30 -6
  111. package/build/core/renew.js +41 -22
  112. package/build/core/scope.js +129 -2
  113. package/build/core/seal.js +36 -15
  114. package/build/core/settlement/petition-claim-gates.js +4 -33
  115. package/build/core/settlement/petition-preview.js +1 -1
  116. package/build/core/settlement/petition.js +10 -3
  117. package/build/core/settlement/settlement.js +3 -10
  118. package/build/core/settlement/verification.js +59 -0
  119. package/build/core/status/board.js +48 -28
  120. package/build/core/stored-agent-event.js +3 -2
  121. package/build/core/task/board.js +1 -0
  122. package/build/core/task/commands.js +20 -12
  123. package/build/core/task/document.js +5 -0
  124. package/build/core/task/index.js +1 -1
  125. package/build/core/task/model.js +8 -1
  126. package/build/core/task/query.js +214 -0
  127. package/build/core/task/settlement-git.js +115 -16
  128. package/build/core/task/settlement-policy.js +17 -9
  129. package/build/core/task/source-board.js +1 -0
  130. package/build/core/task/task-contract.js +125 -51
  131. package/build/core/task/task-store-repository.js +1 -0
  132. package/build/core/task/task.js +2 -0
  133. package/build/flow-error.js +2 -4
  134. package/build/generated/version.js +2 -2
  135. package/build/git/diff/parsers.js +23 -13
  136. package/build/git/diff/structured.js +127 -0
  137. package/build/index.js +7 -7
  138. package/build/telemetry/logger.js +4 -2
  139. package/package.json +1 -1
  140. package/skills/keiyaku/SKILL.md +2 -2
  141. package/skills/keiyaku-akuma/SKILL.md +17 -6
  142. package/skills/keiyaku-task/SKILL.md +16 -7
  143. package/skills/keiyaku-workflow/SKILL.md +48 -13
@@ -3,7 +3,7 @@ export const meta = {
3
3
  command: "task",
4
4
  summary: "Manage lightweight repository tasks",
5
5
  usage: ["keiyaku task <subcommand>"],
6
- stdin: "none",
6
+ stdin: { mode: "argument" },
7
7
  flags: [],
8
8
  purpose: "Manage the file-backed task board.",
9
9
  subcommands: TASK_SUBCOMMANDS,
@@ -1,10 +1,9 @@
1
1
  import { updateTask } from "../../../../core/task/index.js";
2
2
  import { runTaskMutationCommand } from "../shared.js";
3
- export const handler = async (stdin, flags, _signal, positional) => {
4
- const body = flags.taskBody === "-" ? stdin : flags.taskBody;
3
+ export const handler = async (_stdin, flags, _signal, positional) => {
5
4
  return runTaskMutationCommand(flags, (repository) => updateTask(repository, positional[0], {
6
5
  ...(flags.taskTitle === undefined ? {} : { title: flags.taskTitle }),
7
- ...(body === undefined ? {} : { body }),
6
+ ...(flags.taskBody === undefined ? {} : { body: flags.taskBody }),
8
7
  ...(flags.taskPri === undefined ? {} : { pri: flags.taskPri }),
9
8
  ...(flags.taskNeeds === undefined ? {} : { addNeeds: flags.taskNeeds }),
10
9
  ...(flags.taskDropNeeds === undefined ? {} : { dropNeeds: flags.taskDropNeeds }),
@@ -2,7 +2,10 @@ export const meta = {
2
2
  command: "task update",
3
3
  summary: "Update task facts",
4
4
  usage: ["keiyaku task update [-C|--cwd DIR] ID [--title TEXT] [--body TEXT|-] [--pri 0-3] [--needs ID] [--drop-needs ID] [--parent ID|--no-parent] [--from ID]"],
5
- stdin: "none",
5
+ stdin: {
6
+ mode: "argument",
7
+ selector: { kind: "flag", name: "taskBody", required: false, rejectBlank: false },
8
+ },
6
9
  flags: ["cwd", "title", "body", "pri", "needs", "drop-needs", "parent", "no-parent", "from"],
7
10
  purpose: "Change mutable task facts.",
8
11
  };
@@ -2,6 +2,7 @@ import { assertSettingsKnobUsable } from "../config/settings/disease.js";
2
2
  import { loadKeiyakuSettings } from "../config/settings/loader.js";
3
3
  import { loadAkumaCatalog } from "../config/akuma-loader.js";
4
4
  import { listContractIds } from "../core/ledger.js";
5
+ import { TASK_FILTER_COMPLETION } from "../core/task/query.js";
5
6
  import { allCommandMetadata, getCommandMetadata } from "./commands/metadata.js";
6
7
  function topLevelCommands() {
7
8
  return allCommandMetadata()
@@ -46,6 +47,9 @@ export async function renderCompletionCandidates(input) {
46
47
  if (input.previous === "task") {
47
48
  return filterPrefix(subcommandsFor("task"), word).join("\n");
48
49
  }
50
+ if (input.previous === "--filter") {
51
+ return filterPrefix([TASK_FILTER_COMPLETION], word).join("\n");
52
+ }
49
53
  return filterPrefix(topLevelCommands(), word).join("\n");
50
54
  }
51
55
  export function renderCompletionScript(shell = "bash") {
@@ -54,7 +58,7 @@ export function renderCompletionScript(shell = "bash") {
54
58
  "#compdef keiyaku",
55
59
  "_keiyaku() {",
56
60
  " local -a candidates",
57
- " candidates=(${(f)\"$(keiyaku completion --complete --word \"${words[CURRENT]}\" --previous \"${words[CURRENT-1]}\" 2>/dev/null)\"})",
61
+ " candidates=(${(f)\"$(keiyaku completion --complete --word=\"${words[CURRENT]}\" --previous=\"${words[CURRENT-1]}\" 2>/dev/null)\"})",
58
62
  " compadd -- $candidates",
59
63
  "}",
60
64
  "_keiyaku \"$@\"",
@@ -65,7 +69,10 @@ export function renderCompletionScript(shell = "bash") {
65
69
  " local cur prev",
66
70
  " cur=\"${COMP_WORDS[COMP_CWORD]}\"",
67
71
  " prev=\"${COMP_WORDS[COMP_CWORD-1]}\"",
68
- " COMPREPLY=( $(compgen -W \"$(keiyaku completion --complete --word \"$cur\" --previous \"$prev\" 2>/dev/null)\" -- \"$cur\") )",
72
+ " COMPREPLY=()",
73
+ " while IFS= read -r candidate; do",
74
+ " COMPREPLY+=(\"$candidate\")",
75
+ " done < <(keiyaku completion --complete --word=\"$cur\" --previous=\"$prev\" 2>/dev/null)",
69
76
  "}",
70
77
  "complete -F _keiyaku_completion keiyaku",
71
78
  ].join("\n");
@@ -1,4 +1,5 @@
1
1
  import { ACTOR_IDENTITY } from "../keiyaku.js";
2
+ import { TASK_FILTER_GRAMMAR } from "../core/task/query.js";
2
3
  export const HELP_FLAGS = new Set(["--help", "-h"]);
3
4
  export const FLAG_SPECS = {
4
5
  cwd: {
@@ -118,10 +119,10 @@ export const FLAG_SPECS = {
118
119
  kind: "boolean",
119
120
  description: "Preview bind input or petition preflight without mutation.",
120
121
  },
121
- waive: {
122
+ "append-scope": {
122
123
  kind: "value",
123
- placeholder: "PATH",
124
- description: "Allow one explicit out-of-scope path for this seal. May be repeated.",
124
+ placeholder: "PATTERN",
125
+ description: "Append one ordered scope pattern to the contract before audit. May be repeated.",
125
126
  },
126
127
  resolve: {
127
128
  kind: "boolean",
@@ -167,10 +168,25 @@ export const FLAG_SPECS = {
167
168
  kind: "boolean",
168
169
  description: "Render task data as JSON.",
169
170
  },
171
+ "diff-budget": {
172
+ kind: "value",
173
+ placeholder: "BYTES",
174
+ description: "Set the audit raw-diff presentation budget in bytes.",
175
+ },
170
176
  done: {
171
177
  kind: "boolean",
172
178
  description: "Show terminal tasks.",
173
179
  },
180
+ filter: {
181
+ kind: "value",
182
+ placeholder: TASK_FILTER_GRAMMAR,
183
+ description: "Filter structured task facts. Repeat --filter for AND; comma values or | terms are OR.",
184
+ },
185
+ sort: {
186
+ kind: "value",
187
+ placeholder: "KEY[,KEY...]",
188
+ description: "Sort by priority ascending, modified descending, or created descending.",
189
+ },
174
190
  pri: {
175
191
  kind: "value",
176
192
  placeholder: "0-3",
package/build/cli/help.js CHANGED
@@ -62,9 +62,9 @@ export function renderCliHelp(version) {
62
62
  "",
63
63
  "Input:",
64
64
  " <> means required; [] means optional.",
65
- " bind, arc, and amend read Markdown from stdin.",
66
- " call and tell require literal BODY or `-`; only literal `-` opens stdin.",
67
- " revive accepts an artifact plus optional BODY or `-`; only literal `-` opens stdin.",
65
+ " bind, arc, amend, and petition read document stdin only when literal `-` selects it.",
66
+ " call and tell require literal BODY or `-`; literal `-` selects stdin.",
67
+ " revive accepts an artifact plus optional BODY or `-`; unselected stdin is ignored.",
68
68
  "",
69
69
  "Configuration:",
70
70
  " Akuma profiles <KEIYAKU_HOME>/akuma/*.md or .keiyaku/akuma/*.md",
@@ -5,16 +5,19 @@ import { compileBindDraftFlags } from "../core/draft.js";
5
5
  import { handleCliError } from "./render/errors.js";
6
6
  import { FLAG_SPECS } from "./flags.js";
7
7
  import { renderCliCommandHelp as renderCliCommandHelpText, renderCliHelp as renderCliHelpText, } from "./help.js";
8
- import { commandUsesPromptInput, isCallCommand, isPetitionDryRunInvocation, parseCliArgs } from "./parse.js";
8
+ import { isPetitionDryRunInvocation, parseCliArgs } from "./parse.js";
9
9
  import { getCommandMetadata } from "./commands/metadata.js";
10
10
  import { loadCommandHandler } from "./commands/registry.js";
11
11
  import { shellQuote } from "./render/format.js";
12
12
  import { writeSuccessResponse } from "./render/success-response.js";
13
+ import { writeCliOutput } from "./output-stream-error-policy.js";
13
14
  function commandRequiresStdin(command) {
14
- return getCommandMetadata(command).stdin === "required";
15
+ const stdin = getCommandMetadata(command).stdin;
16
+ return stdin.mode === "document" && stdin.selector.required;
15
17
  }
16
18
  function commandAcceptsOptionalStdin(command) {
17
- return getCommandMetadata(command).stdin === "optional";
19
+ const stdin = getCommandMetadata(command).stdin;
20
+ return stdin.mode === "document" && !stdin.selector.required;
18
21
  }
19
22
  function commandSavesFailedStdin(command) {
20
23
  return (commandRequiresStdin(command) || commandAcceptsOptionalStdin(command)) && command !== "call";
@@ -67,8 +70,6 @@ function prepareBindInput(stdin, flags) {
67
70
  return stdin;
68
71
  }
69
72
  async function readStdin() {
70
- if (process.stdin.isTTY)
71
- return "";
72
73
  let data = "";
73
74
  process.stdin.setEncoding("utf-8");
74
75
  for await (const chunk of process.stdin)
@@ -129,12 +130,12 @@ function retryHint(input) {
129
130
  if (input.command === "bind") {
130
131
  const flagTokens = bindRetryFlagTokens(input.flags);
131
132
  const flagPart = flagTokens.length > 0 ? ` ${flagTokens.join(" ")}` : "";
132
- return `Retry: keiyaku bind${flagPart} < ${draftArg}`;
133
+ return `Retry: keiyaku bind${flagPart} - < ${draftArg}`;
133
134
  }
134
135
  if (input.flags.cwd === undefined) {
135
- return `Retry: keiyaku ${input.command} < ${shellQuote(input.draftPath)}`;
136
+ return `Retry: keiyaku ${input.command} - < ${shellQuote(input.draftPath)}`;
136
137
  }
137
- return `Retry: keiyaku ${input.command} --cwd ${shellQuote(input.flags.cwd)} < ${draftArg}`;
138
+ return `Retry: keiyaku ${input.command} --cwd ${shellQuote(input.flags.cwd)} - < ${draftArg}`;
138
139
  }
139
140
  async function printFailure(error, cwd) {
140
141
  const response = await handleCliError({
@@ -142,14 +143,14 @@ async function printFailure(error, cwd) {
142
143
  cwd,
143
144
  logLabel: "cli",
144
145
  });
145
- process.stderr.write(`${responseText(response)}\n`);
146
+ await writeCliOutput(process.stderr, `${responseText(response)}\n`);
146
147
  }
147
148
  function failureExitCode(error) {
148
149
  return error instanceof Error && error.name === "AbortError" ? 130 : 1;
149
150
  }
150
151
  async function handleFailureAfterStdin(input) {
151
152
  if (input.response) {
152
- process.stderr.write(`${responseText(input.response)}\n`);
153
+ await writeCliOutput(process.stderr, `${responseText(input.response)}\n`);
153
154
  }
154
155
  else {
155
156
  await printFailure(input.error ?? new Error("Unknown CLI failure"), input.cwd);
@@ -164,7 +165,7 @@ async function handleFailureAfterStdin(input) {
164
165
  content: input.stdin,
165
166
  });
166
167
  if (draftPath) {
167
- process.stderr.write(`\nSaved stdin input to ${draftPath}.\n${retryHint({
168
+ await writeCliOutput(process.stderr, `\nSaved stdin input to ${draftPath}.\n${retryHint({
168
169
  command: input.command,
169
170
  draftPath,
170
171
  cwd: input.cwd,
@@ -173,7 +174,7 @@ async function handleFailureAfterStdin(input) {
173
174
  }
174
175
  }
175
176
  catch (draftError) {
176
- process.stderr.write(`\nFailed to save stdin input: ${asMessage(draftError)}\n`);
177
+ await writeCliOutput(process.stderr, `\nFailed to save stdin input: ${asMessage(draftError)}\n`);
177
178
  }
178
179
  return 1;
179
180
  }
@@ -182,38 +183,42 @@ async function runResponse(command, flags, stdin, positional) {
182
183
  const handler = await loadCommandHandler(command);
183
184
  return await handler(stdin, flags, signal, positional);
184
185
  }
185
- function bodyToken(command, positional) {
186
- if (isCallCommand(command))
187
- return positional[0];
188
- if (command === "tell")
189
- return positional[1];
190
- if (command === "revive")
191
- return positional[1];
192
- return undefined;
193
- }
194
- async function readBodyInput(command, positional, cwd) {
195
- if (!commandUsesPromptInput(command))
196
- return undefined;
197
- const token = bodyToken(command, positional);
198
- if (token === undefined) {
199
- if (command === "revive")
200
- return "";
201
- await printFailure(new FlowError("EMPTY_PARAM", `${command} body cannot be empty`), cwd);
202
- return undefined;
203
- }
204
- if (token !== "-") {
205
- if (!token.trim()) {
206
- await printFailure(new FlowError("EMPTY_PARAM", `${command} body cannot be empty`), cwd);
207
- return undefined;
208
- }
209
- return token;
210
- }
211
- const stdin = await readStdin();
212
- if (!stdin.trim()) {
213
- await printFailure(new FlowError("EMPTY_PARAM", `${command} body cannot be empty`), cwd);
186
+ function selectedArgument(parsed) {
187
+ const stdin = getCommandMetadata(parsed.command).stdin;
188
+ if (stdin.selector === undefined)
214
189
  return undefined;
215
- }
216
- return stdin;
190
+ if (stdin.selector.kind === "positional") {
191
+ return { token: parsed.positional[stdin.selector.index], replace: () => parsed.flags };
192
+ }
193
+ const flagName = stdin.selector.name;
194
+ return {
195
+ token: parsed.flags[flagName],
196
+ replace: (value) => ({ ...parsed.flags, [flagName]: value }),
197
+ };
198
+ }
199
+ async function resolveCliInput(parsed) {
200
+ const stdinMeta = getCommandMetadata(parsed.command).stdin;
201
+ const selected = selectedArgument(parsed);
202
+ const token = selected?.token;
203
+ const selectsStdin = token === "-";
204
+ if (selectsStdin && process.stdin.isTTY) {
205
+ throw new FlowError("EMPTY_PARAM", `${parsed.command} literal '-' requires piped stdin`);
206
+ }
207
+ const piped = selectsStdin ? await readStdin() : "";
208
+ const input = selectsStdin ? piped : token ?? "";
209
+ if (stdinMeta.selector?.required && token === undefined) {
210
+ const message = stdinMeta.mode === "document"
211
+ ? `${parsed.command} requires literal '-' to read Markdown from stdin`
212
+ : `${parsed.command} body cannot be empty`;
213
+ throw new FlowError("EMPTY_PARAM", message);
214
+ }
215
+ if (token !== undefined && stdinMeta.selector?.rejectBlank && !input.trim()) {
216
+ const message = stdinMeta.mode === "document"
217
+ ? `No Markdown input received on stdin.\n\nExample:\n ${usageExample(parsed.command)}`
218
+ : `${parsed.command} body cannot be empty`;
219
+ throw new FlowError("EMPTY_PARAM", message);
220
+ }
221
+ return { stdin: input, flags: selected?.replace(input) ?? parsed.flags };
217
222
  }
218
223
  function usageExample(command) {
219
224
  const meta = getCommandMetadata(command);
@@ -229,40 +234,19 @@ export async function runCliCommand(tokens) {
229
234
  return isPetitionDryRunInvocation(tokens) ? 2 : 1;
230
235
  }
231
236
  if (parsed.helpRequested) {
232
- process.stdout.write(`${renderCliCommandHelp(resolveCliHelpCommand(parsed.command))}\n`);
237
+ await writeCliOutput(process.stdout, `${renderCliCommandHelp(resolveCliHelpCommand(parsed.command))}\n`);
233
238
  return 0;
234
239
  }
235
240
  const cwd = parsed.flags.cwd ?? process.cwd();
236
- let stdin = "";
237
- if (parsed.command === "task add") {
238
- if (parsed.positional[0] === "-")
239
- stdin = await readStdin();
240
- if (!(parsed.positional[0] === "-" ? stdin : parsed.positional[0]).trim()) {
241
- await printFailure(new FlowError("EMPTY_PARAM", "task add input cannot be blank"), cwd);
242
- return 1;
243
- }
244
- }
245
- else if (parsed.command === "task update" && parsed.flags.taskBody === "-") {
246
- stdin = await readStdin();
247
- }
248
- else if (commandRequiresStdin(parsed.command)) {
249
- stdin = await readStdin();
250
- if (!stdin.trim()) {
251
- await printFailure(new FlowError("EMPTY_PARAM", `No Markdown input received on stdin.\n\nExample:\n ${usageExample(parsed.command)}`), cwd);
252
- return 1;
253
- }
254
- }
255
- else if (commandAcceptsOptionalStdin(parsed.command)) {
256
- // Optional stdin: bind prepares domain flags or blank-stdin errors itself;
257
- // petition lets an empty body reach the oath gate as OATH_MISSING.
258
- stdin = await readStdin();
241
+ let stdin;
242
+ try {
243
+ const resolved = await resolveCliInput(parsed);
244
+ stdin = resolved.stdin;
245
+ parsed = { ...parsed, flags: resolved.flags };
259
246
  }
260
- else {
261
- const body = await readBodyInput(parsed.command, parsed.positional, cwd);
262
- if (body === undefined && commandUsesPromptInput(parsed.command)) {
263
- return 1;
264
- }
265
- stdin = body ?? "";
247
+ catch (error) {
248
+ await printFailure(error, cwd);
249
+ return 1;
266
250
  }
267
251
  // Bind dry-run must compile and return before repository/cwd resolution or filesystem effects.
268
252
  if (parsed.command === "bind") {
@@ -296,7 +280,7 @@ export async function runCliCommand(tokens) {
296
280
  }
297
281
  const success = !response.payload || !("error" in response.payload);
298
282
  if (success) {
299
- writeSuccessResponse(parsed.command, parsed.flags, response);
283
+ await writeSuccessResponse(parsed.command, parsed.flags, response);
300
284
  return response.exitCode ?? 0;
301
285
  }
302
286
  if (commandRequiresStdin(parsed.command) || (parsed.command === "bind" && !parsed.flags.dryRun)) {
@@ -309,7 +293,7 @@ export async function runCliCommand(tokens) {
309
293
  saveStdin: !(parsed.command === "bind" && hasBindDomainFlags(parsed.flags)),
310
294
  });
311
295
  }
312
- process.stderr.write(`${responseText(response)}\n`);
296
+ await writeCliOutput(process.stderr, `${responseText(response)}\n`);
313
297
  return 1;
314
298
  }
315
299
  export function renderCliCommandHelp(command) {
@@ -1,3 +1,21 @@
1
+ export function isBrokenPipeError(error) {
2
+ return (typeof error === "object" &&
3
+ error !== null &&
4
+ "code" in error &&
5
+ error.code === "EPIPE");
6
+ }
7
+ /** Resolve only after Node reports that this chunk has been flushed. */
8
+ export function writeCliOutput(stream, chunk) {
9
+ return new Promise((resolve, reject) => {
10
+ stream.write(chunk, (error) => {
11
+ if (error && !isBrokenPipeError(error)) {
12
+ reject(error);
13
+ return;
14
+ }
15
+ resolve();
16
+ });
17
+ });
18
+ }
1
19
  /**
2
20
  * Install a process-level broken-pipe policy on each supplied stream.
3
21
  * EPIPE is silent; every other emitted value is rethrown unchanged.
@@ -5,12 +23,8 @@
5
23
  export function installCliOutputStreamErrorPolicy(...streams) {
6
24
  for (const stream of streams) {
7
25
  stream.on("error", (error) => {
8
- if (typeof error === "object" &&
9
- error !== null &&
10
- "code" in error &&
11
- error.code === "EPIPE") {
26
+ if (isBrokenPipeError(error))
12
27
  return;
13
- }
14
28
  throw error;
15
29
  });
16
30
  }
@@ -99,8 +99,8 @@ export function assignFlag(flags, name, value) {
99
99
  case "dry-run":
100
100
  flags.dryRun = Boolean(value);
101
101
  return;
102
- case "waive":
103
- flags.waivePaths = [...(flags.waivePaths ?? []), String(value)];
102
+ case "append-scope":
103
+ flags.appendScope = [...(flags.appendScope ?? []), String(value)];
104
104
  return;
105
105
  case "resolve":
106
106
  flags.renewResolve = Boolean(value);
@@ -138,9 +138,26 @@ export function assignFlag(flags, name, value) {
138
138
  case "json":
139
139
  flags.json = Boolean(value);
140
140
  return;
141
+ case "diff-budget": {
142
+ const parsed = Number(String(value));
143
+ if (!Number.isSafeInteger(parsed) || parsed < 0) {
144
+ throw new FlowError("EMPTY_PARAM", "option --diff-budget requires a non-negative safe integer byte count");
145
+ }
146
+ flags.auditDiffBudgetBytes = parsed;
147
+ return;
148
+ }
141
149
  case "done":
142
150
  flags.done = Boolean(value);
143
151
  return;
152
+ case "filter":
153
+ flags.taskFilters = [...(flags.taskFilters ?? []), String(value)];
154
+ return;
155
+ case "sort":
156
+ if (flags.taskSort !== undefined) {
157
+ throw new FlowError("EMPTY_PARAM", "option --sort may only be specified once");
158
+ }
159
+ flags.taskSort = String(value);
160
+ return;
144
161
  case "pri": {
145
162
  const raw = String(value);
146
163
  if (!/^[0-3]$/.test(raw)) {
@@ -1,6 +1,6 @@
1
1
  import { FlowError } from "../flow-error.js";
2
2
  import { HELP_FLAGS, SHORT_FLAG_ALIASES, VALUE_FLAGS } from "./flags.js";
3
- import { commandParseOrder, getCommandMetadata } from "./commands/metadata.js";
3
+ import { commandParseOrder } from "./commands/metadata.js";
4
4
  import { TASK_SUBCOMMANDS } from "./types.js";
5
5
  export function normalizeContractAddress(raw) {
6
6
  const value = raw.startsWith("@") ? raw.slice(1) : raw;
@@ -42,9 +42,6 @@ export function isPetitionDryRunInvocation(tokens) {
42
42
  }
43
43
  return false;
44
44
  }
45
- export function commandUsesPromptInput(command) {
46
- return isCallCommand(command) || command === "tell" || command === "revive";
47
- }
48
45
  export function parseWaitTimeout(value) {
49
46
  const matched = /^(?<amount>[1-9][0-9]*)(?<unit>[smh])$/.exec(value);
50
47
  if (!matched?.groups) {
@@ -97,9 +94,6 @@ export function commandPositionalRange(command) {
97
94
  }
98
95
  return { min: 0, max: 0 };
99
96
  }
100
- export function commandRequiresStdin(command) {
101
- return getCommandMetadata(command).stdin === "required";
102
- }
103
97
  export function resolveCommand(tokens) {
104
98
  if (tokens[0] === "petition" && (tokens[1] === "claim" || tokens[1] === "forfeit")) {
105
99
  throw new FlowError("UNKNOWN_COMMAND", `unknown command: petition ${tokens[1]}`);
@@ -12,6 +12,7 @@ const COMMISSION_SELECTOR_COMMANDS = new Set([
12
12
  "renew",
13
13
  "petition",
14
14
  "forfeit",
15
+ "audit",
15
16
  ]);
16
17
  function formatSelectorList(items) {
17
18
  if (items.length <= 1) {
@@ -2,11 +2,12 @@ import { FlowError } from "../flow-error.js";
2
2
  import { agentNameSchema } from "../config/settings/schema.js";
3
3
  import { isResponseArtifactId } from "../core/response-artifact-id.js";
4
4
  import { HELP_FLAGS } from "./flags.js";
5
+ import { getCommandMetadata } from "./commands/metadata.js";
5
6
  import { assertAllowedFlag, assignFlag, BOOLEAN_FLAGS, CONTROL_FLAGS, parseFlagName, readValueFlag, SHORT_FLAG_ALIASES, VALUE_FLAGS, } from "./parse-flags.js";
6
- import { commandPositionalRange, commandRequiresStdin, isCallCommand, resolveCommand, splitAtLiteralBoundary, TASK_SUBCOMMANDS, } from "./parse-metadata.js";
7
+ import { commandPositionalRange, isCallCommand, resolveCommand, splitAtLiteralBoundary, TASK_SUBCOMMANDS, } from "./parse-metadata.js";
7
8
  import { applyCommissionSelector, assertCommissionSelectorAllowed, assertProjectionSelectorPresent, } from "./parse-selectors.js";
8
9
  import { WAIT_SET_SELECTION_ERROR } from "./commands/projection/wait/meta.js";
9
- export { commandPositionalRange, commandUsesPromptInput, isCallCommand, isPetitionDryRunInvocation, normalizeContractAddress, parseWaitTimeout, } from "./parse-metadata.js";
10
+ export { commandPositionalRange, isCallCommand, isPetitionDryRunInvocation, normalizeContractAddress, parseWaitTimeout, } from "./parse-metadata.js";
10
11
  export function parseCliArgs(tokens) {
11
12
  const { head, payload } = splitAtLiteralBoundary(tokens);
12
13
  const flags = {};
@@ -135,13 +136,16 @@ export function parseCliArgs(tokens) {
135
136
  if (command === "petition" && flags.json && !flags.dryRun) {
136
137
  throw new FlowError("EMPTY_PARAM", "petition --json requires --dry-run");
137
138
  }
139
+ if (command === "petition" && flags.dryRun && (flags.appendScope?.length ?? 0) > 0) {
140
+ throw new FlowError("EMPTY_PARAM", "petition --dry-run does not accept --append-scope");
141
+ }
138
142
  if (command === "renew") {
139
143
  const recoveryModes = [flags.renewResolve, flags.renewContinue, flags.renewAbort].filter(Boolean).length;
140
144
  if (recoveryModes > 1) {
141
145
  throw new FlowError("EMPTY_PARAM", "renew accepts only one of --resolve, --continue, or --abort");
142
146
  }
143
- if (flags.renewAbort && (flags.waivePaths?.length ?? 0) > 0) {
144
- throw new FlowError("EMPTY_PARAM", "renew --abort does not accept --waive");
147
+ if (flags.renewAbort && (flags.appendScope?.length ?? 0) > 0) {
148
+ throw new FlowError("EMPTY_PARAM", "renew --abort does not accept --append-scope");
145
149
  }
146
150
  }
147
151
  assertCommissionSelectorAllowed(command, flags);
@@ -184,9 +188,14 @@ export function parseCliArgs(tokens) {
184
188
  if (positional.length < positionalRange.min) {
185
189
  throw new FlowError("EMPTY_PARAM", `${command} requires ${positionalRange.label ?? "an argument"}`);
186
190
  }
187
- if (positional.length > positionalRange.max) {
188
- const message = commandRequiresStdin(command)
189
- ? `${command} reads Markdown from stdin; file path arguments are not supported`
191
+ const stdin = getCommandMetadata(command).stdin;
192
+ const documentStdinSelector = stdin.mode === "document"
193
+ && stdin.selector.kind === "positional"
194
+ && positional.length === stdin.selector.index + 1
195
+ && positional[stdin.selector.index] === "-";
196
+ if (positional.length > positionalRange.max && !documentStdinSelector) {
197
+ const message = stdin.mode === "document"
198
+ ? `${command} reads Markdown only through literal '-'; file path arguments are not supported`
190
199
  : `${command} does not accept positional arguments`;
191
200
  throw new FlowError("EMPTY_PARAM", message);
192
201
  }
@@ -1,7 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { AddressResolutionError, buildObjectResolutionAttempt, } from "../core/addressing.js";
4
- import { resolveProjectionCoordinate } from "../core/projection/index.js";
4
+ import { assertProjectionTellable, resolveProjectionCoordinate } from "../core/projection/index.js";
5
5
  import { isProjectionId, projectionDir } from "../core/projection/index.js";
6
6
  import { isProjectionAlias, readProjectionAlias } from "../core/projection/index.js";
7
7
  import { readProjectionIdentity } from "../core/projection/index.js";
@@ -80,6 +80,11 @@ export async function resolveProjectionAddress(input) {
80
80
  const subject = alias ? `projection alias ${alias} points to missing projection ${id}` : `projection ${id}`;
81
81
  throw new AddressResolutionError("EMPTY_PARAM", `${subject} has no match ${suffix}`, attempt);
82
82
  }
83
+ // Historical projection storage has no current identity to resolve. Tell
84
+ // still reaches its typed no-mutation refusal instead of exposing storage
85
+ // parser details or creating a mailbox row.
86
+ if (input.verb === "tell")
87
+ assertProjectionTellable(directory);
83
88
  let identity;
84
89
  try {
85
90
  identity = readProjectionIdentity(directory);