@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
@@ -1,4 +1,4 @@
1
- import { AGENT_ACTIVITY_MAX_CHANGES, AGENT_EVENT_TEXT_MAX_CHARS, AGENT_UNKNOWN_PAYLOAD_MAX_CHARS, } from "./events.js";
1
+ import { AGENT_ACTIVITY_MAX_CHANGES, AGENT_DONE_FINAL_MESSAGE_PERSISTENCE_PREVIEW_MAX_CHARS, AGENT_EVENT_TEXT_MAX_CHARS, AGENT_UNKNOWN_PAYLOAD_MAX_CHARS, } from "./events.js";
2
2
  import { EXEC_EVENT_BUFFER_HARD_CAP } from "./event-channel.js";
3
3
  export function createTruncationMarker(envelope = {
4
4
  seq: EXEC_EVENT_BUFFER_HARD_CAP,
@@ -23,10 +23,10 @@ export function overflowMarkerFromDropped(dropped) {
23
23
  turn: dropped.turn,
24
24
  });
25
25
  }
26
- function truncateText(value) {
26
+ function truncateText(value, limit = AGENT_EVENT_TEXT_MAX_CHARS) {
27
27
  return {
28
- value: value.slice(0, AGENT_EVENT_TEXT_MAX_CHARS),
29
- truncated: value.length > AGENT_EVENT_TEXT_MAX_CHARS,
28
+ value: value.slice(0, limit),
29
+ truncated: value.length > limit,
30
30
  };
31
31
  }
32
32
  /** Bound unknown native payloads before they enter the projection event stream. */
@@ -186,7 +186,9 @@ function normalizeBodyForPersistence(body) {
186
186
  return { body: { ...body, text: text.value }, truncated: text.truncated };
187
187
  }
188
188
  case "done": {
189
- const finalMessage = body.finalMessage === undefined ? undefined : truncateText(body.finalMessage);
189
+ const finalMessage = body.finalMessage === undefined
190
+ ? undefined
191
+ : truncateText(body.finalMessage, AGENT_DONE_FINAL_MESSAGE_PERSISTENCE_PREVIEW_MAX_CHARS);
190
192
  return {
191
193
  body: { ...body, ...(finalMessage ? { finalMessage: finalMessage.value } : {}) },
192
194
  truncated: finalMessage?.truncated ?? false,
@@ -1,10 +1,11 @@
1
1
  import { z } from "zod";
2
- export const AGENT_EVENT_TEXT_MAX_CHARS = 4_096;
2
+ export const AGENT_EVENT_TEXT_MAX_CHARS = 16_384;
3
+ export const AGENT_DONE_FINAL_MESSAGE_PERSISTENCE_PREVIEW_MAX_CHARS = 65_536;
3
4
  export const AGENT_THOUGHT_TEXT_MAX_CHARS = 4_000;
4
5
  export const AGENT_PLAN_MAX_ITEMS = 64;
5
6
  export const AGENT_PLAN_ITEM_TEXT_MAX_CHARS = 200;
6
7
  export const AGENT_ACTIVITY_MAX_CHANGES = 32;
7
- export const AGENT_UNKNOWN_PAYLOAD_MAX_CHARS = 4_096;
8
+ export const AGENT_UNKNOWN_PAYLOAD_MAX_CHARS = AGENT_EVENT_TEXT_MAX_CHARS;
8
9
  export const activityCallSchema = z.discriminatedUnion("kind", [
9
10
  z.object({ kind: z.literal("run"), command: z.string(), description: z.string().optional() }).strict(),
10
11
  z.object({
@@ -86,6 +86,16 @@ export function classifyFailure(shared, state, error, idleTimedOut, transportFai
86
86
  warnings: [...state.warnings],
87
87
  });
88
88
  }
89
+ if (shared.explicitlyAborted && isProviderTerminalError(error) && error.fact.kind === "aborted") {
90
+ return mintOutcome({
91
+ status: "interrupted",
92
+ finalMessage: state.finalMessageCandidate ?? "",
93
+ output: renderCapturedText(state.outputBuffer),
94
+ diagnostics: renderCapturedText(state.diagnosticsBuffer),
95
+ session: state.session ?? fallbackSession(shared),
96
+ warnings: [...state.warnings],
97
+ });
98
+ }
89
99
  if (transportFailure) {
90
100
  const message = transportFailure instanceof Error ? transportFailure.message : `${shared.terms.provider} session transport probe failed`;
91
101
  const timeoutMs = isProviderTerminalError(transportFailure) && transportFailure.fact.kind === "failed"
@@ -243,7 +243,12 @@ export async function startCodexAppServer(prompt, cwd, options = {}) {
243
243
  builderDiagnostic = (text) => builder.emit({ type: "diagnostic", text });
244
244
  emitTurnStarted = () => builder.emit({ type: "turn", phase: "started" });
245
245
  const env = buildSubagentEnv(options.env);
246
- server = new CodexAppServerProcess(executable, env, options.codexHome, options.onEvidence, (text) => builder.emit({ type: "diagnostic", text }));
246
+ server = new CodexAppServerProcess(executable, env, {
247
+ cwd,
248
+ codexHome: options.codexHome,
249
+ onEvidence: options.onEvidence,
250
+ onEvidenceDiagnostic: (text) => builder.emit({ type: "diagnostic", text }),
251
+ });
247
252
  const { turnCompleted } = createCodexAppServerTurnMonitor({
248
253
  server,
249
254
  builder,
@@ -3,8 +3,6 @@ import { emitProviderEvidence, sha256Utf8 } from "../../harness/runtime.js";
3
3
  import { coerceString } from "../../harness/runtime.js";
4
4
  import { CODEX_APP_SERVER_PROVIDER } from "./identity.js";
5
5
  export class CodexAppServerProcess {
6
- onEvidence;
7
- onEvidenceDiagnostic;
8
6
  child;
9
7
  pending = new Map();
10
8
  notificationListeners = new Set();
@@ -17,15 +15,18 @@ export class CodexAppServerProcess {
17
15
  gracefulClose;
18
16
  forceClose;
19
17
  codexHome;
20
- constructor(executable, env, codexHome, onEvidence, onEvidenceDiagnostic) {
21
- this.onEvidence = onEvidence;
22
- this.onEvidenceDiagnostic = onEvidenceDiagnostic;
23
- this.codexHome = codexHome;
18
+ onEvidence;
19
+ onEvidenceDiagnostic;
20
+ constructor(executable, env, options) {
21
+ this.codexHome = options.codexHome;
22
+ this.onEvidence = options.onEvidence;
23
+ this.onEvidenceDiagnostic = options.onEvidenceDiagnostic;
24
24
  const spawnEnv = {
25
25
  ...env,
26
- ...(codexHome ? { CODEX_HOME: codexHome } : {}),
26
+ ...(options.codexHome ? { CODEX_HOME: options.codexHome } : {}),
27
27
  };
28
28
  this.child = spawn(executable, ["app-server", "--listen", "stdio://"], {
29
+ cwd: options.cwd,
29
30
  env: spawnEnv,
30
31
  stdio: ["pipe", "pipe", "pipe"],
31
32
  });
@@ -77,7 +77,18 @@ export async function selectSubagent(agentName, cwd) {
77
77
  throw new FlowError("INVALID_SETTINGS", `Akuma '${selected}' references missing provider instance '${instanceName}'.`);
78
78
  }
79
79
  if (!("instance" in providerCandidate) || providerCandidate.instance === undefined) {
80
- throw new FlowError("INVALID_SETTINGS", `Akuma '${selected}' references invalid provider instance '${instanceName}': ${providerCandidate.reason}`);
80
+ const source = providerCandidate.source === "global" ? "global" : "local";
81
+ throw new FlowError("INVALID_SETTINGS", `Akuma '${selected}' references invalid provider instance '${instanceName}': ${providerCandidate.reason}`, {
82
+ facts: {
83
+ kind: "invalid_settings",
84
+ diseases: [{
85
+ source,
86
+ coordinate: providerCandidate.coordinate,
87
+ knob: "providers",
88
+ reason: providerCandidate.reason,
89
+ }],
90
+ },
91
+ });
81
92
  }
82
93
  const providerInstance = providerCandidate.instance;
83
94
  const adapter = providerInstance.kind;
@@ -2,19 +2,12 @@ import { FlowError } from "../../../../flow-error.js";
2
2
  import { assertSettingsKnobUsable } from "../../../../config/settings/disease.js";
3
3
  import { loadKeiyakuSettings } from "../../../../config/settings/loader.js";
4
4
  import { loadAkumaCatalog } from "../../../../config/akuma-loader.js";
5
- import { readProjectionStatusBoard } from "../../../../core/projection/index.js";
5
+ import { readAkumaNonterminalProjectionStatus } from "../../../../core/projection/index.js";
6
6
  import { isResponseArtifactId } from "../../../../core/response-artifact-id.js";
7
7
  import { textResponse } from "../../../render/shared.js";
8
8
  import { renderProjectionStatusSection } from "../../../render/status.js";
9
9
  import { projectDirectory } from "../../shared.js";
10
10
  import { renderAkumaShow, renderAkumaView } from "../../akuma.js";
11
- const NONTERMINAL_PROJECTION_STATES = new Set([
12
- "minting",
13
- "startup-timeout",
14
- "unknown",
15
- "out",
16
- "lost",
17
- ]);
18
11
  export const handler = async (_stdin, flags, _signal, positional) => {
19
12
  const [name] = positional;
20
13
  if (!name)
@@ -28,8 +21,7 @@ export const handler = async (_stdin, flags, _signal, positional) => {
28
21
  assertSettingsKnobUsable(loaded, "default");
29
22
  assertSettingsKnobUsable(loaded, "reviewer");
30
23
  const profile = renderAkumaShow(await loadAkumaCatalog(cwd), loaded.providerInstances, loaded.knobs, name).trimEnd();
31
- const projectionStatus = await readProjectionStatusBoard(cwd, Date.now(), { showAll: true });
32
- const rows = (projectionStatus?.rows ?? []).filter((row) => row.akuma === name && NONTERMINAL_PROJECTION_STATES.has(row.state));
33
- const projections = renderProjectionStatusSection({ rows }).trimEnd();
24
+ const projectionStatus = await readAkumaNonterminalProjectionStatus(cwd, Date.now(), name);
25
+ const projections = renderProjectionStatusSection(projectionStatus).trimEnd();
34
26
  return textResponse(renderAkumaView(profile, projections));
35
27
  };
@@ -5,7 +5,7 @@ import { buildAmendResponse } from "../../../render/arc.js";
5
5
  import { renderCoreResult } from "../../../render/shared.js";
6
6
  import { effectiveDirectory, repositoryDirectory } from "../../shared.js";
7
7
  export const handler = async (stdin, flags) => {
8
- const parsed = buildAmendInput(stdin, effectiveDirectory(flags), flags.contractId, flags.contractAddressSource);
8
+ const parsed = buildAmendInput(stdin, effectiveDirectory(flags), flags.contractId, flags.contractAddressSource, flags.appendScope);
9
9
  const cwd = await repositoryDirectory(flags, "amend");
10
10
  const address = await resolveExistingContractAddress(cwd, flags.contractId, flags.contractAddressSource, effectiveDirectory(flags));
11
11
  return renderCoreResult(amendKeiyaku({
@@ -2,11 +2,11 @@ export const meta = {
2
2
  command: "amend",
3
3
  summary: "Record an amendment",
4
4
  usage: [
5
- "keiyaku amend [-C|--cwd DIR] [--repo DIR] [--contract ADDR] - < amendment.md",
6
- "keiyaku @addr amend [-C|--cwd DIR] [--repo DIR] - < amendment.md",
5
+ "keiyaku amend [-C|--cwd DIR] [--repo DIR] [--contract ADDR] [--append-scope PATTERN] - < amendment.md",
6
+ "keiyaku @addr amend [-C|--cwd DIR] [--repo DIR] [--append-scope PATTERN] - < amendment.md",
7
7
  ],
8
8
  stdin: { mode: "document", selector: { kind: "positional", index: 0, required: true, rejectBlank: true } },
9
- flags: ["cwd", "repo", "contract"],
10
- purpose: "Append a prose amendment and optional Scope Append gitignore-pattern delta.",
9
+ flags: ["cwd", "repo", "contract", "append-scope"],
10
+ purpose: "Append a prose amendment and one optional ordered scope-pattern delta from --append-scope or Scope Append.",
11
11
  completionRank: 8,
12
12
  };
@@ -1,13 +1,19 @@
1
- import { bindContract } from "../../../../core/bind.js";
1
+ import { bindContract, previewBindContract } from "../../../../core/bind.js";
2
2
  import { defaultActorClock } from "../../../../core/outcome-base.js";
3
3
  import { writeDraftArtifact } from "../../../draft-artifact.js";
4
- import { buildBindDraftResponse, buildBindResponse, textResponse } from "../../../render/shared.js";
4
+ import { buildBindDraftResponse, buildBindPreviewResponse, buildBindResponse } from "../../../render/shared.js";
5
5
  import { repositoryDirectory } from "../../shared.js";
6
6
  export const handler = async (stdin, flags, signal) => {
7
+ const cwd = await repositoryDirectory(flags, "bind");
7
8
  if (flags.dryRun) {
8
- return textResponse(stdin);
9
+ const preview = await previewBindContract({
10
+ cwd, draft: stdin, ...defaultActorClock(), workspace: "worktree",
11
+ after: flags.after ?? [], exclusive: flags.exclusive,
12
+ ...(flags.bindTaskIds === undefined ? {} : { taskIds: flags.bindTaskIds }),
13
+ signal,
14
+ });
15
+ return buildBindPreviewResponse(preview);
9
16
  }
10
- const cwd = await repositoryDirectory(flags, "bind");
11
17
  if (flags.draftOnly) {
12
18
  return buildBindDraftResponse(await writeDraftArtifact({ cwd, title: "bind-draft", content: stdin }));
13
19
  }
@@ -17,10 +17,10 @@ export const meta = {
17
17
  "<what the commission should accomplish>",
18
18
  "",
19
19
  "## Scope",
20
- "```",
20
+ "~~~",
21
21
  "<pattern>",
22
22
  "<another-pattern>",
23
- "```",
23
+ "~~~",
24
24
  "",
25
25
  "## Checks",
26
26
  "- <verification>",
@@ -40,10 +40,10 @@ export const meta = {
40
40
  "<what the commission should accomplish>",
41
41
  "",
42
42
  "## Scope",
43
- "```",
43
+ "~~~",
44
44
  "<pattern>",
45
45
  "<another-pattern>",
46
- "```",
46
+ "~~~",
47
47
  "",
48
48
  "## Checks",
49
49
  "- <verification>",
@@ -52,10 +52,10 @@ export const meta = {
52
52
  [
53
53
  "keiyaku bind --task fix-flaky-pump - <<'KEIYAKU'",
54
54
  "## Scope",
55
- "```",
55
+ "~~~",
56
56
  "<pattern>",
57
57
  "<another-pattern>",
58
- "```",
58
+ "~~~",
59
59
  "",
60
60
  "## Checks",
61
61
  "- <verification>",
@@ -1,23 +1,36 @@
1
1
  import { FlowError } from "../../../../flow-error.js";
2
2
  import { resolveExistingContractAddress, withResolvedAddress } from "../../../../core/addressing.js";
3
3
  import { buildPetitionClaimInput, petitionKeiyaku, previewPetition, } from "../../../../core/settlement/index.js";
4
- import { buildPetitionClaimResponse, buildPetitionPreviewResponse } from "../../../render/petition.js";
4
+ import { renewKeiyaku } from "../../../../core/renew.js";
5
+ import { buildPetitionClaimResponse, buildPetitionPreviewResponse, renderPetitionRenewedConsequence, } from "../../../render/petition.js";
6
+ import { writeCliOutput } from "../../../output-stream-error-policy.js";
5
7
  import { effectiveDirectory, repositoryDirectory } from "../../shared.js";
6
8
  export const handler = async (stdin, flags, signal) => {
7
9
  if (flags.json && !flags.dryRun) {
8
10
  throw new FlowError("EMPTY_PARAM", "petition --json requires --dry-run");
9
11
  }
12
+ if (flags.renewBeforePetition && flags.dryRun) {
13
+ throw new FlowError("EMPTY_PARAM", "petition --renew cannot be used with --dry-run");
14
+ }
10
15
  const cwd = await repositoryDirectory(flags, "petition");
11
16
  const address = await resolveExistingContractAddress(cwd, flags.contractId, flags.contractAddressSource, effectiveDirectory(flags));
12
17
  return await withResolvedAddress(address, async () => {
13
- const input = buildPetitionClaimInput({ cwd, contractId: address.binding.commissionId, stdin, signal, appendScope: flags.appendScope });
14
18
  if (flags.dryRun) {
19
+ const input = buildPetitionClaimInput({ cwd, contractId: address.binding.commissionId, stdin, signal });
15
20
  return buildPetitionPreviewResponse(await previewPetition({
16
21
  cwd,
17
22
  contractId: address.binding.commissionId,
18
23
  oath: input.oath,
19
24
  }), flags.json === true);
20
25
  }
21
- return buildPetitionClaimResponse({ ...await petitionKeiyaku(input), address }, input);
26
+ const renewed = flags.renewBeforePetition
27
+ ? await renewKeiyaku({ cwd, address, appendScope: flags.appendScope })
28
+ : undefined;
29
+ if (renewed && !renewed.alreadyFresh) {
30
+ await writeCliOutput(process.stdout, `${renderPetitionRenewedConsequence(renewed)}\n`);
31
+ }
32
+ const input = buildPetitionClaimInput({ cwd, contractId: address.binding.commissionId, stdin, signal, appendScope: flags.appendScope });
33
+ const petitionInput = renewed ? { ...input, appendScope: undefined } : input;
34
+ return buildPetitionClaimResponse({ ...await petitionKeiyaku(petitionInput), address }, input);
22
35
  });
23
36
  };
@@ -2,12 +2,12 @@ export const meta = {
2
2
  command: "petition",
3
3
  summary: "Run or preview the gated settlement pipeline",
4
4
  usage: [
5
- "keiyaku petition [-C|--cwd DIR] [--repo DIR] [--contract ADDR] [--append-scope PATTERN] [- < petition.md]",
6
- "keiyaku @<id> petition [-C|--cwd DIR] [--repo DIR] [--append-scope PATTERN] [- < petition.md]",
5
+ "keiyaku petition [-C|--cwd DIR] [--repo DIR] [--contract ADDR] [--renew] [--append-scope PATTERN] [- < petition.md]",
6
+ "keiyaku @<id> petition [-C|--cwd DIR] [--repo DIR] [--renew] [--append-scope PATTERN] [- < petition.md]",
7
7
  "keiyaku @<id> petition --dry-run [--json] [- < petition.md]",
8
8
  ],
9
9
  stdin: { mode: "document", selector: { kind: "positional", index: 0, required: false, rejectBlank: false } },
10
- flags: ["cwd", "repo", "contract", "append-scope", "dry-run", "json"],
10
+ flags: ["cwd", "repo", "contract", "renew", "append-scope", "dry-run", "json"],
11
11
  purpose: "Seal the current a0 or open explicit arc, queue the contract, and run verdict and claim processing; dry-run previews deterministic gates without mutation.",
12
12
  input: [
13
13
  "## Oath",
@@ -16,7 +16,7 @@ export const meta = {
16
16
  notes: [
17
17
  "Petition stdin is optional and read only when literal - is supplied; a missing, empty, or blank oath is rejected by the settlement gate with OATH_MISSING.",
18
18
  "The machine checks only that the oath is present. Accepted oath text is persisted in the petition entry and hidden from the reviewer.",
19
- "Base drift requires explicit `keiyaku renew` before petition.",
19
+ "Use --renew to run ordinary renew before petition; renew failures stop the command before settlement.",
20
20
  "Dry-run never runs verification or review and exits 0, 1, or 2.",
21
21
  ],
22
22
  completionRank: 10,
@@ -23,6 +23,7 @@ import { meta as taskMeta } from "./task/task/meta.js";
23
23
  import { meta as taskAddMeta } from "./task/add/meta.js";
24
24
  import { meta as taskShowMeta } from "./task/show/meta.js";
25
25
  import { meta as taskLsMeta } from "./task/ls/meta.js";
26
+ import { meta as taskLogMeta } from "./task/log/meta.js";
26
27
  import { meta as taskStartMeta } from "./task/start/meta.js";
27
28
  import { meta as taskStopMeta } from "./task/stop/meta.js";
28
29
  import { meta as taskDoneMeta } from "./task/done/meta.js";
@@ -34,14 +35,14 @@ export const COMMAND_METADATA_ORDER = [
34
35
  "bind", "arc", "call", "revive", "wait", "tell", "kill", "log",
35
36
  "akuma", "akuma ls", "akuma view", "skills", "skills install", "amend", "audit",
36
37
  "renew", "petition", "forfeit", "guide", "completion", "status", "task",
37
- "task add", "task show", "task ls", "task start", "task stop", "task done",
38
+ "task add", "task show", "task ls", "task log", "task start", "task stop", "task done",
38
39
  "task drop", "task update", "task doctor", "dump-env",
39
40
  ];
40
41
  const COMMAND_METADATA = [
41
42
  bindMeta, arcMeta, callMeta, reviveMeta, waitMeta, tellMeta, killMeta, logMeta,
42
43
  akumaMeta, akumaLsMeta, akumaViewMeta, skillsMeta, skillsInstallMeta, amendMeta, auditMeta,
43
44
  renewMeta, petitionMeta, forfeitMeta, guideMeta, completionMeta, statusMeta,
44
- taskMeta, taskAddMeta, taskShowMeta, taskLsMeta, taskStartMeta, taskStopMeta,
45
+ taskMeta, taskAddMeta, taskShowMeta, taskLsMeta, taskLogMeta, taskStartMeta, taskStopMeta,
45
46
  taskDoneMeta, taskDropMeta, taskUpdateMeta, taskDoctorMeta, dumpEnvMeta,
46
47
  ];
47
48
  export const CLI_COMMAND_METADATA = Object.fromEntries(COMMAND_METADATA.map((meta) => [meta.command, meta]));
@@ -6,6 +6,10 @@ import { renderProjectionFilteredStatus, renderStatusBoard } from "../../../rend
6
6
  import { textResponse } from "../../../render/shared.js";
7
7
  import { projectDirectory, repositoryDirectory } from "../../shared.js";
8
8
  export const handler = async (_stdin, flags, _signal, positional) => {
9
+ const [projectionId] = positional;
10
+ if (projectionId !== undefined && !isProjectionId(projectionId)) {
11
+ throw new FlowError("EMPTY_PARAM", `invalid projection id: ${projectionId}`);
12
+ }
9
13
  // Status is read-only and must open outside Git. projectDirectory still
10
14
  // honors --repo as a repository coordinate when the operator names one.
11
15
  const cwd = flags.target === undefined
@@ -14,12 +18,9 @@ export const handler = async (_stdin, flags, _signal, positional) => {
14
18
  const target = flags.target === undefined ? undefined : normalizeTargetRef(flags.target);
15
19
  const board = await readKanshiBoard(cwd, Date.now(), {
16
20
  ...(target === undefined ? {} : { target }),
17
- showAllProjections: flags.all === true || positional.length === 1,
21
+ ...(projectionId === undefined ? {} : { projectionId }),
18
22
  });
19
- const [projectionId] = positional;
20
23
  if (projectionId === undefined)
21
24
  return textResponse(renderStatusBoard(board));
22
- if (!isProjectionId(projectionId))
23
- throw new FlowError("EMPTY_PARAM", `invalid projection id: ${projectionId}`);
24
25
  return textResponse(renderProjectionFilteredStatus(board, projectionId));
25
26
  };
@@ -1,9 +1,9 @@
1
1
  export const meta = {
2
2
  command: "status",
3
3
  summary: "Show the Kanshi board",
4
- usage: ["keiyaku status [PROJECTION] [-C|--cwd DIR] [--repo DIR] [--target REF] [--all]"],
4
+ usage: ["keiyaku status [PROJECTION] [-C|--cwd DIR] [--repo DIR] [--target REF]"],
5
5
  stdin: { mode: "argument" },
6
- flags: ["cwd", "repo", "target", "all"],
6
+ flags: ["cwd", "repo", "target"],
7
7
  purpose: "Read the Kanshi board or filter it to one projection without mutation.",
8
8
  completionRank: 14,
9
9
  };
@@ -1,5 +1,5 @@
1
1
  import { FlowError } from "../../../../flow-error.js";
2
- import { assertTellEffortSupported, tellProjection, writeInboxTell } from "../../../../core/projection/index.js";
2
+ import { assertTellEffortSupported, interruptProjectionGeneration, tellProjection, writeInboxTell } from "../../../../core/projection/index.js";
3
3
  import { waitForProjection } from "../../../../core/projection/index.js";
4
4
  import { prependAddressReceipt } from "../../../render/address.js";
5
5
  import { buildSilentTellResponse } from "../../../render/tell.js";
@@ -13,7 +13,15 @@ export const handler = async (body, flags, signal, positional) => runProjectionC
13
13
  assertTellEffortSupported(projection.directory, flags.effort);
14
14
  // Address admission and any effort validation precede the durable mailbox write.
15
15
  writeInboxTell(projection.directory, body, Date.now(), { effort: flags.effort });
16
- await tellProjection(projection.directory, projection.id, flags.effort);
16
+ if (flags.interrupt) {
17
+ const interrupted = interruptProjectionGeneration(projection.directory);
18
+ if (interrupted.status === "already-closed") {
19
+ await tellProjection(projection.directory, projection.id, flags.effort);
20
+ }
21
+ }
22
+ else {
23
+ await tellProjection(projection.directory, projection.id, flags.effort);
24
+ }
17
25
  if (flags.timeoutMs === undefined)
18
26
  return buildSilentTellResponse();
19
27
  return prependAddressReceipt(buildWaitResponse(projection.id, await waitForProjection({ projectionDirectory: projection.directory, timeoutMs: flags.timeoutMs, signal }), { alias: projection.alias }), ctx.address, { projectionId: projection.id, projectionAlias: projection.alias });
@@ -1,12 +1,12 @@
1
1
  export const meta = {
2
2
  command: "tell",
3
3
  summary: "Send intent to a projection mailbox",
4
- usage: ["keiyaku tell PROJECTION [--wait DURATION] [--] <BODY|->"],
4
+ usage: ["keiyaku tell PROJECTION [--interrupt] [--wait DURATION] [--] <BODY|->"],
5
5
  stdin: {
6
6
  mode: "argument",
7
7
  selector: { kind: "positional", index: 1, required: true, rejectBlank: true },
8
8
  },
9
- flags: ["cwd", "repo", "contract", "effort", "wait"],
10
- purpose: "Write intent to the mailbox and wake only when eligible.",
9
+ flags: ["cwd", "repo", "contract", "effort", "interrupt", "wait"],
10
+ purpose: "Write intent to the mailbox; --interrupt terminates the active turn before successor wake.",
11
11
  completionRank: 3,
12
12
  };
@@ -1,11 +1,19 @@
1
+ const TASK_ADD_USAGE_PREFIX = "keiyaku task add [-C|--cwd DIR] [--needs ID]... [--parent ID] [--from ID]... [--pri 0-3]";
1
2
  export const meta = {
2
3
  command: "task add",
3
4
  summary: "Add a task",
4
- usage: ["keiyaku task add [-C|--cwd DIR] [--needs ID] [--parent ID] [--from ID] [--pri 0-3] <BODY|->"],
5
+ usage: [
6
+ `${TASK_ADD_USAGE_PREFIX} <BODY>`,
7
+ `${TASK_ADD_USAGE_PREFIX} - < task.md`,
8
+ ],
5
9
  stdin: {
6
10
  mode: "argument",
7
11
  selector: { kind: "positional", index: 0, required: true, rejectBlank: true },
8
12
  },
9
13
  flags: ["cwd", "needs", "parent", "from", "pri"],
10
14
  purpose: "Create a lightweight task from a title and optional body.",
15
+ notes: [
16
+ "<BODY> and literal - are mutually exclusive body selectors. Use literal - with stdin for multiline prose.",
17
+ "Only literal - reads stdin; unselected piped bytes are ignored and never change a literal <BODY>.",
18
+ ],
11
19
  };
@@ -2,6 +2,7 @@ import { meta as taskMeta } from "./task/meta.js";
2
2
  import { meta as addMeta } from "./add/meta.js";
3
3
  import { meta as showMeta } from "./show/meta.js";
4
4
  import { meta as lsMeta } from "./ls/meta.js";
5
+ import { meta as logMeta } from "./log/meta.js";
5
6
  import { meta as startMeta } from "./start/meta.js";
6
7
  import { meta as stopMeta } from "./stop/meta.js";
7
8
  import { meta as doneMeta } from "./done/meta.js";
@@ -13,6 +14,7 @@ export const taskCatalog = [
13
14
  { meta: addMeta, load: () => import("./add/handler.js") },
14
15
  { meta: showMeta, load: () => import("./show/handler.js") },
15
16
  { meta: lsMeta, load: () => import("./ls/handler.js") },
17
+ { meta: logMeta, load: () => import("./log/handler.js") },
16
18
  { meta: startMeta, load: () => import("./start/handler.js") },
17
19
  { meta: stopMeta, load: () => import("./stop/handler.js") },
18
20
  { meta: doneMeta, load: () => import("./done/handler.js") },
@@ -0,0 +1,12 @@
1
+ import { FlowError } from "../../../../flow-error.js";
2
+ import { readTaskBoard, readTaskHistory, resolveTaskReadId, } from "../../../../core/task/index.js";
3
+ import { textResponse } from "../../../render/shared.js";
4
+ import { withTaskCommandRepository } from "../shared.js";
5
+ export const handler = async (_stdin, flags, _signal, positional) => withTaskCommandRepository(flags, async (repository, cwd) => {
6
+ const board = await readTaskBoard(repository);
7
+ const task = board.byId.get(resolveTaskReadId(board, positional[0]));
8
+ if (!task)
9
+ throw new FlowError("INTERNAL_STATE", "resolved task disappeared from the board");
10
+ const rows = await readTaskHistory(cwd, task.id);
11
+ return textResponse(rows.map(({ timestamp, actor, subject }) => `${timestamp}\t${actor}\t${subject}`).join("\n"));
12
+ });
@@ -0,0 +1,8 @@
1
+ export const meta = {
2
+ command: "task log",
3
+ summary: "Show one task's Git history",
4
+ usage: ["keiyaku task log [-C|--cwd DIR] ID"],
5
+ stdin: { mode: "argument" },
6
+ flags: ["cwd"],
7
+ purpose: "Read the current hub branch's task micro-commit history.",
8
+ };
@@ -17,7 +17,8 @@ export function parseTaskAddInput(input) {
17
17
  if (!input.trim())
18
18
  throw new FlowError("EMPTY_PARAM", "task add input cannot be blank");
19
19
  const lines = input.split(/\r?\n/);
20
- const title = lines[0]?.trim() ?? "";
20
+ const firstLine = lines[0]?.startsWith("# ") ? lines[0].slice(2) : lines[0];
21
+ const title = firstLine?.trim() ?? "";
21
22
  if (!title)
22
23
  throw new FlowError("EMPTY_PARAM", "task title cannot be blank");
23
24
  if (lines.length > 1 && lines[1].trim() !== "") {
@@ -19,6 +19,10 @@ function topLevelCommands() {
19
19
  function subcommandsFor(parent) {
20
20
  return [...(getCommandMetadata(parent).subcommands ?? [])];
21
21
  }
22
+ function topLevelOptionsFor(command) {
23
+ const metadata = allCommandMetadata().find((meta) => meta.command === command && !command.includes(" "));
24
+ return metadata ? metadata.flags.map((flag) => `--${flag}`) : [];
25
+ }
22
26
  function filterPrefix(items, word) {
23
27
  return items.filter((item) => item.startsWith(word));
24
28
  }
@@ -50,6 +54,10 @@ export async function renderCompletionCandidates(input) {
50
54
  if (input.previous === "--filter") {
51
55
  return filterPrefix([TASK_FILTER_COMPLETION], word).join("\n");
52
56
  }
57
+ const options = input.previous ? topLevelOptionsFor(input.previous) : [];
58
+ if (options.length > 0) {
59
+ return filterPrefix(options, word).join("\n");
60
+ }
53
61
  return filterPrefix(topLevelCommands(), word).join("\n");
54
62
  }
55
63
  export function renderCompletionScript(shell = "bash") {
@@ -68,6 +68,10 @@ export const FLAG_SPECS = {
68
68
  kind: "boolean",
69
69
  description: "Return after durable launch instead of waiting for completion.",
70
70
  },
71
+ interrupt: {
72
+ kind: "boolean",
73
+ description: "Interrupt the current projection turn after saving the tell.",
74
+ },
71
75
  "draft-only": {
72
76
  kind: "boolean",
73
77
  description: "Write a draft artifact without creating a contract workspace.",
@@ -124,6 +128,10 @@ export const FLAG_SPECS = {
124
128
  placeholder: "PATTERN",
125
129
  description: "Append one ordered scope pattern to the contract before audit. May be repeated.",
126
130
  },
131
+ renew: {
132
+ kind: "boolean",
133
+ description: "Renew onto the current target before petitioning.",
134
+ },
127
135
  resolve: {
128
136
  kind: "boolean",
129
137
  description: "Open a durable Keiyaku-owned renew recovery session when the rewrite conflicts.",
@@ -57,9 +57,6 @@ function prepareBindInput(stdin, flags) {
57
57
  checks: flags.bindChecks,
58
58
  });
59
59
  }
60
- if (flags.dryRun) {
61
- throw new FlowError("EMPTY_PARAM", "--dry-run requires --place, --objective, --scope, and --checks");
62
- }
63
60
  if (!nonblankStdin) {
64
61
  // --task may supply name/objective defaults; core merges and reports missing sections.
65
62
  if (flags.bindTaskIds !== undefined && flags.bindTaskIds.length > 0) {
@@ -188,11 +185,17 @@ function selectedArgument(parsed) {
188
185
  if (stdin.selector === undefined)
189
186
  return undefined;
190
187
  if (stdin.selector.kind === "positional") {
191
- return { token: parsed.positional[stdin.selector.index], replace: () => parsed.flags };
188
+ const token = parsed.positional[stdin.selector.index];
189
+ if (token === undefined && !stdin.selector.required)
190
+ return undefined;
191
+ return { token, replace: () => parsed.flags };
192
192
  }
193
193
  const flagName = stdin.selector.name;
194
+ const token = parsed.flags[flagName];
195
+ if (token === undefined && !stdin.selector.required)
196
+ return undefined;
194
197
  return {
195
- token: parsed.flags[flagName],
198
+ token,
196
199
  replace: (value) => ({ ...parsed.flags, [flagName]: value }),
197
200
  };
198
201
  }
@@ -248,7 +251,8 @@ export async function runCliCommand(tokens) {
248
251
  await printFailure(error, cwd);
249
252
  return 1;
250
253
  }
251
- // Bind dry-run must compile and return before repository/cwd resolution or filesystem effects.
254
+ // Bind input compilation is side-effect free. Dry-run then performs the same
255
+ // repository admission as bind but does not persist an artifact on failure.
252
256
  if (parsed.command === "bind") {
253
257
  try {
254
258
  stdin = prepareBindInput(stdin, parsed.flags);
@@ -54,6 +54,9 @@ export function assignFlag(flags, name, value) {
54
54
  case "detach":
55
55
  flags.detach = Boolean(value);
56
56
  return;
57
+ case "interrupt":
58
+ flags.interrupt = Boolean(value);
59
+ return;
57
60
  case "draft-only":
58
61
  flags.draftOnly = Boolean(value);
59
62
  return;
@@ -102,6 +105,9 @@ export function assignFlag(flags, name, value) {
102
105
  case "append-scope":
103
106
  flags.appendScope = [...(flags.appendScope ?? []), String(value)];
104
107
  return;
108
+ case "renew":
109
+ flags.renewBeforePetition = Boolean(value);
110
+ return;
105
111
  case "resolve":
106
112
  flags.renewResolve = Boolean(value);
107
113
  return;
@@ -83,7 +83,7 @@ export function commandPositionalRange(command) {
83
83
  if (command === "task add") {
84
84
  return { min: 1, max: 1, label: "<BODY|->" };
85
85
  }
86
- if (command === "task show" || command === "task start" || command === "task drop") {
86
+ if (command === "task show" || command === "task log" || command === "task start" || command === "task drop") {
87
87
  return { min: 1, max: 1, label: "<ID>" };
88
88
  }
89
89
  if (command === "task update") {