@astrosheep/keiyaku 2.9.5 → 2.9.7

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 (75) hide show
  1. package/build/.tsbuildinfo +1 -1
  2. package/build/agents/harness/outcome.js +20 -4
  3. package/build/agents/harness/projection.js +8 -7
  4. package/build/agents/harness/router.js +2 -1
  5. package/build/agents/providers/claude-agent-sdk/adapter.js +13 -1
  6. package/build/agents/providers/claude-agent-sdk/registration.js +2 -1
  7. package/build/agents/providers/codex-app-server/adapter.js +11 -10
  8. package/build/agents/providers/codex-app-server/registration.js +2 -1
  9. package/build/agents/providers/opencode-sdk/adapter.js +7 -0
  10. package/build/agents/providers/opencode-sdk/registration.js +2 -1
  11. package/build/agents/providers/pi/adapter.js +10 -0
  12. package/build/agents/providers/pi/registration.js +3 -1
  13. package/build/cli/commands/contract/bind/handler.js +10 -4
  14. package/build/cli/commands/contract/bind/meta.js +6 -6
  15. package/build/cli/commands/contract/petition/handler.js +16 -3
  16. package/build/cli/commands/contract/petition/meta.js +4 -4
  17. package/build/cli/commands/metadata.js +3 -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/catalog.js +2 -0
  21. package/build/cli/commands/task/log/handler.js +12 -0
  22. package/build/cli/commands/task/log/meta.js +8 -0
  23. package/build/cli/completion.js +9 -2
  24. package/build/cli/flags.js +11 -2
  25. package/build/cli/index.js +10 -6
  26. package/build/cli/parse-flags.js +6 -0
  27. package/build/cli/parse-metadata.js +1 -1
  28. package/build/cli/render/errors.js +18 -1
  29. package/build/cli/render/format.js +5 -2
  30. package/build/cli/render/line-width.js +33 -0
  31. package/build/cli/render/path-prefix-compaction.js +88 -0
  32. package/build/cli/render/petition.js +4 -0
  33. package/build/cli/render/projection-activity.js +93 -20
  34. package/build/cli/render/shared.js +34 -12
  35. package/build/cli/render/success-response.js +2 -0
  36. package/build/cli/render/tool-presentation.js +3 -3
  37. package/build/cli/render/wait.js +68 -48
  38. package/build/cli/subagent-guard.js +3 -0
  39. package/build/cli/types.js +1 -1
  40. package/build/core/audit/candidate.js +48 -0
  41. package/build/core/audit/coordinates.js +180 -0
  42. package/build/core/audit/evidence.js +158 -0
  43. package/build/core/audit/facade.js +42 -0
  44. package/build/core/audit/readiness.js +4 -0
  45. package/build/core/audit/report.js +38 -0
  46. package/build/core/audit.js +1 -650
  47. package/build/core/bind.js +111 -6
  48. package/build/core/draft.js +1 -1
  49. package/build/core/projection/generation/database.js +21 -2
  50. package/build/core/projection/generation/model.js +24 -3
  51. package/build/core/projection/generation/projection-generation-continuation.js +75 -10
  52. package/build/core/projection/generation/projection-generation-execution.js +4 -3
  53. package/build/core/projection/generation/projection-generation-runner.js +96 -53
  54. package/build/core/projection/generation/projection-generation-runtime.js +19 -0
  55. package/build/core/projection/generation/store.js +9 -0
  56. package/build/core/projection/generation/transitions.js +105 -3
  57. package/build/core/projection/index.js +2 -2
  58. package/build/core/projection/projection-core.js +1 -1
  59. package/build/core/projection/projection-kill.js +31 -0
  60. package/build/core/projection/projection-life-protocol.js +10 -0
  61. package/build/core/projection/projection-wait.js +53 -15
  62. package/build/core/projection/projection-wake.js +35 -3
  63. package/build/core/projection/tell/database.js +18 -0
  64. package/build/core/projection/tell/model.js +1 -0
  65. package/build/core/projection/tell/store.js +102 -55
  66. package/build/core/registry.js +82 -69
  67. package/build/core/scope.js +9 -9
  68. package/build/core/task/index.js +2 -2
  69. package/build/core/task/query.js +65 -23
  70. package/build/core/task/task-contract.js +18 -0
  71. package/build/core/task/task-git-store.js +50 -0
  72. package/build/generated/version.js +2 -2
  73. package/package.json +1 -1
  74. package/skills/keiyaku-akuma/SKILL.md +10 -0
  75. package/skills/keiyaku-workflow/SKILL.md +82 -7
@@ -3,13 +3,17 @@ import { SUBAGENT_SESSION_VERSION, } from "../session.js";
3
3
  import { OUTCOME_ERROR_VERSION, SubagentFailure, } from "../types.js";
4
4
  import { isProviderTerminalError, } from "./provider-adapter.js";
5
5
  import { renderCapturedText } from "./runtime.js";
6
- export function createOutcomeState(maxCaptureChars) {
6
+ export function createOutcomeState(maxCaptureChars, admittedSession) {
7
7
  return {
8
8
  outputBuffer: new StringTailBuffer(maxCaptureChars),
9
9
  diagnosticsBuffer: new StringTailBuffer(maxCaptureChars),
10
10
  warnings: [],
11
+ ...(admittedSession ? { session: admittedSession } : {}),
11
12
  };
12
13
  }
14
+ export function recordSessionAdmission(state, session) {
15
+ state.session = session;
16
+ }
13
17
  function mintOutcome(draft) {
14
18
  return draft;
15
19
  }
@@ -29,7 +33,6 @@ export function processEvent(state, event) {
29
33
  return;
30
34
  case "done":
31
35
  state.doneEvent = event;
32
- state.session = event.session;
33
36
  if (event.finalMessage !== undefined) {
34
37
  state.finalMessageCandidate = event.finalMessage;
35
38
  }
@@ -83,6 +86,16 @@ export function classifyFailure(shared, state, error, idleTimedOut, transportFai
83
86
  warnings: [...state.warnings],
84
87
  });
85
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
+ }
86
99
  if (transportFailure) {
87
100
  const message = transportFailure instanceof Error ? transportFailure.message : `${shared.terms.provider} session transport probe failed`;
88
101
  const timeoutMs = isProviderTerminalError(transportFailure) && transportFailure.fact.kind === "failed"
@@ -173,13 +186,16 @@ export function failWithError(shared, state, error, finalMessage, diagnostics =
173
186
  warnings: [...state.warnings],
174
187
  });
175
188
  }
176
- export function completeOutcome(shared, state, finalMessage, session) {
189
+ export function completeOutcome(state, finalMessage) {
190
+ if (!state.session) {
191
+ throw new Error("Cannot complete an outcome before session admission");
192
+ }
177
193
  return mintOutcome({
178
194
  status: "completed",
179
195
  finalMessage,
180
196
  output: renderCapturedText(state.outputBuffer),
181
197
  diagnostics: renderCapturedText(state.diagnosticsBuffer),
182
- session,
198
+ session: state.session,
183
199
  warnings: [...state.warnings],
184
200
  });
185
201
  }
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import { FlowError } from "../../flow-error.js";
3
3
  import { createEventChannel } from "./event-channel.js";
4
4
  import { overflowMarkerFromDropped, prepareAgentEventForPersistence, } from "./execution-handle.js";
5
- import { classifyFailure, classifySettlementFailure, completeOutcome, createOutcomeState, failFromFailEvent, failWithError, processEvent, renderOutcomeCapturedText, } from "./outcome.js";
5
+ import { classifyFailure, classifySettlementFailure, completeOutcome, createOutcomeState, failFromFailEvent, failWithError, processEvent, recordSessionAdmission, renderOutcomeCapturedText, } from "./outcome.js";
6
6
  import { startLiveProjection } from "./router.js";
7
7
  import { createAbortError } from "./runtime.js";
8
8
  const POLICY_BUDGET_CLEANUP_GRACE_MS = 5_000;
@@ -198,7 +198,7 @@ function deliverDriverEvent(shared, state, live, event, refreshIdleTimeout, know
198
198
  processEvent(state, event);
199
199
  }
200
200
  async function settleProjection(shared) {
201
- const state = createOutcomeState(shared.terms.maxCaptureChars);
201
+ const state = createOutcomeState(shared.terms.maxCaptureChars, shared.reviveTerms?.session);
202
202
  let idleTimedOut = false;
203
203
  let transportFailure;
204
204
  let policyBudgetExceeded = false;
@@ -245,7 +245,6 @@ async function settleProjection(shared) {
245
245
  resolvePolicyBudget();
246
246
  }, shared.terms.executionTimeoutMs);
247
247
  executionTimeout.unref?.();
248
- refreshIdleTimeout();
249
248
  const requestLiveAbort = () => {
250
249
  if (shared.explicitlyAborted)
251
250
  return;
@@ -257,7 +256,10 @@ async function settleProjection(shared) {
257
256
  shared.lifecycleAbort.signal.addEventListener("abort", requestLiveAbort, { once: true });
258
257
  const providerSettlement = (async () => {
259
258
  try {
260
- const live = await startLiveProjection(shared.terms, shared.lifecycleAbort.signal, shared.reviveTerms, refreshIdleTimeout, shared.observer);
259
+ const live = await startLiveProjection(shared.terms, shared.lifecycleAbort.signal, shared.reviveTerms, refreshIdleTimeout, shared.observer, (admission) => {
260
+ recordSessionAdmission(state, admission.session);
261
+ refreshIdleTimeout();
262
+ });
261
263
  liveDriver = live;
262
264
  shared.onLive(live);
263
265
  resolveForceWaiters(shared, live);
@@ -324,8 +326,7 @@ async function settleProjection(shared) {
324
326
  completionOutcome?.finalMessage ??
325
327
  state.finalMessageCandidate ??
326
328
  renderOutcomeCapturedText(state.outputBuffer).trim();
327
- const session = state.doneEvent?.session ?? completionOutcome?.session;
328
- if (!session) {
329
+ if (!state.session) {
329
330
  return failWithError(shared, state, {
330
331
  code: "SUBAGENT_EXEC_ERROR",
331
332
  message: `${shared.terms.provider} exec ended without a session handle`,
@@ -333,7 +334,7 @@ async function settleProjection(shared) {
333
334
  exitCode: null,
334
335
  }, finalMessage);
335
336
  }
336
- return completeOutcome(shared, state, finalMessage, session);
337
+ return completeOutcome(state, finalMessage);
337
338
  }
338
339
  catch (error) {
339
340
  if (error instanceof FlowError) {
@@ -1,5 +1,5 @@
1
1
  import { getProviderRegistration } from "../providers/registry.js";
2
- export async function startLiveProjection(terms, signal, reviveTerms, onIngress, observer) {
2
+ export async function startLiveProjection(terms, signal, reviveTerms, onIngress, observer, onSessionAdmission) {
3
3
  const onSyncCheckpoint = observer
4
4
  ? (control, checkpoint) => {
5
5
  observer.onCheckpoint(control, checkpoint);
@@ -19,6 +19,7 @@ export async function startLiveProjection(terms, signal, reviveTerms, onIngress,
19
19
  reviveTerms,
20
20
  onSyncCheckpoint,
21
21
  onDeliveryOpportunity,
22
+ onSessionAdmission,
22
23
  onIngress,
23
24
  observer,
24
25
  });
@@ -198,6 +198,7 @@ export async function startClaudeAgentSdk(prompt, cwd, options) {
198
198
  queryActive = true;
199
199
  notifyActive();
200
200
  let resumedSessionId = sessionId;
201
+ let admittedSessionId;
201
202
  let finalMessage;
202
203
  let latestAssistantMessageModel;
203
204
  // Provider acceptance / pre-terminal retry evidence for no-turn classification.
@@ -207,7 +208,18 @@ export async function startClaudeAgentSdk(prompt, cwd, options) {
207
208
  const seenToolUses = new Map();
208
209
  for await (const event of query) {
209
210
  builder.ingress(CLAUDE_AGENT_SDK_PROVIDER, event.type, event, claudeIngressDisposition(event.type), () => {
210
- resumedSessionId = extractResumeToken(event) ?? resumedSessionId;
211
+ const providerSessionId = extractResumeToken(event);
212
+ resumedSessionId = providerSessionId ?? resumedSessionId;
213
+ if (providerSessionId && providerSessionId !== admittedSessionId) {
214
+ admittedSessionId = providerSessionId;
215
+ options.onSessionAdmission?.({
216
+ session: {
217
+ provider: CLAUDE_AGENT_SDK_PROVIDER,
218
+ version: SUBAGENT_SESSION_VERSION,
219
+ handle: { sessionId: providerSessionId },
220
+ },
221
+ });
222
+ }
211
223
  if (isClaudeApiRetryEvent(event)) {
212
224
  lastPreTerminalEvidenceWasRetry = true;
213
225
  builder.emit({ type: "warning", warning: mapClaudeApiRetryWarning(event) });
@@ -179,7 +179,7 @@ export const claudeAgentSdkRegistration = {
179
179
  return loader ? { ...terms, loader } : terms;
180
180
  },
181
181
  nativePolicyOwnership: () => null,
182
- startLive: async ({ terms, signal, reviveTerms, onSyncCheckpoint, onIngress }) => {
182
+ startLive: async ({ terms, signal, reviveTerms, onSyncCheckpoint, onSessionAdmission, onIngress }) => {
183
183
  if (terms.provider !== CLAUDE_AGENT_SDK_PROVIDER) {
184
184
  throw new Error(`startLive expected ${CLAUDE_AGENT_SDK_PROVIDER}`);
185
185
  }
@@ -197,6 +197,7 @@ export const claudeAgentSdkRegistration = {
197
197
  systemPrompt: claudeTerms.systemPrompt,
198
198
  onSyncCheckpoint,
199
199
  onEvidence: claudeTerms.onEvidence,
200
+ onSessionAdmission,
200
201
  onIngress,
201
202
  initialTurn: claudeTerms.initialTurn,
202
203
  loader: claudeTerms.loader,
@@ -304,6 +304,15 @@ export async function startCodexAppServer(prompt, cwd, options = {}) {
304
304
  builder.emit({ type: "fail", message });
305
305
  throw createProviderFailure(message);
306
306
  }
307
+ const admittedSession = {
308
+ provider: "codex-app-server",
309
+ version: SUBAGENT_SESSION_VERSION,
310
+ handle: {
311
+ threadId: activeThreadId,
312
+ ...(serverCodexHome ? { codexHome: serverCodexHome } : {}),
313
+ },
314
+ };
315
+ options.onSessionAdmission?.({ session: admittedSession });
307
316
  if (options.onEvidence)
308
317
  emitProviderEvidence(options.onEvidence, { type: "session", provider: CODEX_APP_SERVER_PROVIDER, threadId: activeThreadId }, builderDiagnostic);
309
318
  // Policy A defaults, but omit when config.* already owns that surface (thread/start config wins).
@@ -351,17 +360,9 @@ export async function startCodexAppServer(prompt, cwd, options = {}) {
351
360
  throw createProviderFailure(message);
352
361
  }
353
362
  // Successful turn/completed is consumption evidence; interrupt ack alone is not terminal proof.
354
- const session = {
355
- provider: "codex-app-server",
356
- version: SUBAGENT_SESSION_VERSION,
357
- handle: {
358
- threadId: activeThreadId,
359
- ...(serverCodexHome ? { codexHome: serverCodexHome } : {}),
360
- },
361
- };
362
363
  const finalMessage = turnState.lastAgentMessage;
363
- builder.emit({ type: "done", finalMessage, session });
364
- return { finalMessage, session };
364
+ builder.emit({ type: "done", finalMessage, session: admittedSession });
365
+ return { finalMessage, session: admittedSession };
365
366
  }
366
367
  finally {
367
368
  activeTurnCompletion = undefined;
@@ -212,7 +212,7 @@ export const codexAppServerRegistration = {
212
212
  webSearch: Object.freeze(webSearch),
213
213
  });
214
214
  },
215
- startLive: async ({ terms, signal, reviveTerms, onSyncCheckpoint, onDeliveryOpportunity, onIngress, }) => {
215
+ startLive: async ({ terms, signal, reviveTerms, onSyncCheckpoint, onDeliveryOpportunity, onSessionAdmission, onIngress, }) => {
216
216
  if (terms.provider !== CODEX_APP_SERVER_PROVIDER) {
217
217
  throw new Error(`startLive expected ${CODEX_APP_SERVER_PROVIDER}`);
218
218
  }
@@ -231,6 +231,7 @@ export const codexAppServerRegistration = {
231
231
  onSyncCheckpoint,
232
232
  onDeliveryOpportunity,
233
233
  onEvidence: codexTerms.onEvidence,
234
+ onSessionAdmission,
234
235
  onIngress,
235
236
  initialTurn: codexTerms.initialTurn,
236
237
  };
@@ -264,6 +264,13 @@ export async function startOpencode(prompt, cwd, options = {}) {
264
264
  builder.emit({ type: "fail", message });
265
265
  throw createProviderFailure(message);
266
266
  }
267
+ options.onSessionAdmission?.({
268
+ session: {
269
+ provider: OPENCODE_SDK_PROVIDER,
270
+ version: SUBAGENT_SESSION_VERSION,
271
+ handle: { sessionId: activeSessionId },
272
+ },
273
+ });
267
274
  idleMonitor = new OpencodeIdleMonitor(sdk.client.v2.session, activeSessionId, combined.signal);
268
275
  eventController = createOpencodeEventController({
269
276
  builder,
@@ -104,7 +104,7 @@ export const opencodeSdkRegistration = {
104
104
  return loader ? { ...terms, loader } : terms;
105
105
  },
106
106
  nativePolicyOwnership: () => null,
107
- startLive: async ({ terms, signal, reviveTerms, onSyncCheckpoint, onDeliveryOpportunity, onIngress, }) => {
107
+ startLive: async ({ terms, signal, reviveTerms, onSyncCheckpoint, onDeliveryOpportunity, onSessionAdmission, onIngress, }) => {
108
108
  if (terms.provider !== OPENCODE_SDK_PROVIDER) {
109
109
  throw new Error(`startLive expected ${OPENCODE_SDK_PROVIDER}`);
110
110
  }
@@ -118,6 +118,7 @@ export const opencodeSdkRegistration = {
118
118
  onSyncCheckpoint,
119
119
  onDeliveryOpportunity,
120
120
  onEvidence: opencodeTerms.onEvidence,
121
+ onSessionAdmission,
121
122
  onIngress,
122
123
  initialTurn: opencodeTerms.initialTurn,
123
124
  loader: opencodeTerms.loader,
@@ -1,5 +1,6 @@
1
1
  import { FlowError } from "../../../flow-error.js";
2
2
  import { appendDebugLog } from "../../../telemetry/debug-log.js";
3
+ import { SUBAGENT_SESSION_VERSION, } from "../../session.js";
3
4
  import { createProjectionDriver } from "../../harness/execution-handle.js";
4
5
  import { createProviderFailure, toProviderTerminalError, } from "../../harness/provider-adapter.js";
5
6
  import { createAbortError, emitProviderEvidence, sha256Utf8 } from "../../harness/runtime.js";
@@ -103,6 +104,15 @@ async function openPiProjectionDriver(prompt, cwd, options) {
103
104
  ...(resourceLoader ? { resourceLoader } : {}),
104
105
  });
105
106
  const session = created.session;
107
+ if (session.sessionFile) {
108
+ options.onSessionAdmission?.({
109
+ session: {
110
+ provider: PI_PROVIDER,
111
+ version: SUBAGENT_SESSION_VERSION,
112
+ handle: { sessionId: session.sessionId, sessionFile: session.sessionFile },
113
+ },
114
+ });
115
+ }
106
116
  if (options.onEvidence)
107
117
  emitProviderEvidence(options.onEvidence, { type: "session", provider: PI_PROVIDER, sessionId: session.sessionId, ...(session.sessionFile ? { sessionFile: session.sessionFile } : {}) }, reportEvidenceDiagnostic);
108
118
  session.setSteeringMode("one-at-a-time");
@@ -109,7 +109,7 @@ export const piRegistration = {
109
109
  return loader ? { ...terms, loader } : terms;
110
110
  },
111
111
  nativePolicyOwnership: () => null,
112
- startLive: async ({ terms, signal, reviveTerms, onSyncCheckpoint, onIngress }) => {
112
+ startLive: async ({ terms, signal, reviveTerms, onSyncCheckpoint, onSessionAdmission, onIngress }) => {
113
113
  if (terms.provider !== PI_PROVIDER) {
114
114
  throw new Error(`startLive expected ${PI_PROVIDER}`);
115
115
  }
@@ -125,6 +125,7 @@ export const piRegistration = {
125
125
  appendSystemPromptOverride: piTerms.appendSystemPromptOverride,
126
126
  onSyncCheckpoint,
127
127
  onEvidence: piTerms.onEvidence,
128
+ onSessionAdmission,
128
129
  onIngress,
129
130
  initialTurn: piTerms.initialTurn,
130
131
  loader: piTerms.loader,
@@ -137,6 +138,7 @@ export const piRegistration = {
137
138
  appendSystemPromptOverride: piTerms.appendSystemPromptOverride,
138
139
  onSyncCheckpoint,
139
140
  onEvidence: piTerms.onEvidence,
141
+ onSessionAdmission,
140
142
  onIngress,
141
143
  initialTurn: piTerms.initialTurn,
142
144
  loader: piTerms.loader,
@@ -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]));
@@ -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
  };
@@ -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
+ };
@@ -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: {
@@ -67,6 +68,10 @@ export const FLAG_SPECS = {
67
68
  kind: "boolean",
68
69
  description: "Return after durable launch instead of waiting for completion.",
69
70
  },
71
+ interrupt: {
72
+ kind: "boolean",
73
+ description: "Interrupt the current projection turn after saving the tell.",
74
+ },
70
75
  "draft-only": {
71
76
  kind: "boolean",
72
77
  description: "Write a draft artifact without creating a contract workspace.",
@@ -123,6 +128,10 @@ export const FLAG_SPECS = {
123
128
  placeholder: "PATTERN",
124
129
  description: "Append one ordered scope pattern to the contract before audit. May be repeated.",
125
130
  },
131
+ renew: {
132
+ kind: "boolean",
133
+ description: "Renew onto the current target before petitioning.",
134
+ },
126
135
  resolve: {
127
136
  kind: "boolean",
128
137
  description: "Open a durable Keiyaku-owned renew recovery session when the rewrite conflicts.",
@@ -178,8 +187,8 @@ export const FLAG_SPECS = {
178
187
  },
179
188
  filter: {
180
189
  kind: "value",
181
- placeholder: "FIELD OP VALUE",
182
- description: "Filter structured task facts. Repeat for AND; comma-separated values are OR.",
190
+ placeholder: TASK_FILTER_GRAMMAR,
191
+ description: "Filter structured task facts. Repeat --filter for AND; comma values or | terms are OR.",
183
192
  },
184
193
  sort: {
185
194
  kind: "value",