@astrosheep/keiyaku 0.1.85 → 0.1.87

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.
@@ -2,7 +2,7 @@ import path from "node:path";
2
2
  import { ACTOR_IDENTITY, ACTOR_IDENTITY_PLURAL, BASE_RULES_FILE, KEIYAKU_ARTIFACT_DIRS, KEIYAKU_BRANCH_PREFIX, KEIYAKU_DRAFT_DIR, RESPONSE_HISTORY_DIR, SETTINGS_FILE, TOOL_NAMES, formatArtifactDirectories, } from "../keiyaku.js";
3
3
  import { renderTemplate } from "./render-template.js";
4
4
  const STATUS_BRANCH_CANDIDATE_LIMIT = 3;
5
- const STATUS_DIRTY_FILE_DISPLAY_LIMIT = 5;
5
+ const STATUS_DIRTY_FILE_DISPLAY_LIMIT = 2;
6
6
  const GUIDANCE_BRANCH_LIST_LIMIT = 3;
7
7
  const WORKFLOW_HINT_TEMPLATES = {
8
8
  bind: [
@@ -32,22 +32,23 @@ const WORKFLOW_HINT_TEMPLATES = {
32
32
  const STATUS_TEXT = {
33
33
  blockers: {
34
34
  dirtyWorktree: "Working directory has uncommitted changes; commit or stash before continuing.",
35
+ dirtyWorktreeSummary: "Working directory has uncommitted changes.",
35
36
  bindProtocolOutsideActive: "KEIYAKU.md exists outside an active keiyaku branch; remove or relocate before starting.",
36
37
  missingBaseMetadata: "Active keiyaku branch is missing base metadata (branch.<keiyaku-branch>.keiyakuBase).",
37
38
  missingProtocolFiles: "Active keiyaku branch is missing KEIYAKU.md; restore it before continuing.",
38
39
  },
39
40
  hints: {
40
- missingKeiyaku: "KEIYAKU.md is missing; restore or recreate it before running more rounds.",
41
- missingBaseMetadata: "Base branch metadata is missing; set branch.<keiyaku-branch>.keiyakuBase to avoid close-time failures.",
41
+ missingKeiyaku: "No Keiyaku is active here; bind starts a tracked contract when you want one.",
42
+ missingBaseMetadata: "This branch is missing base metadata; set branch.<keiyaku-branch>.keiyakuBase before round or petition commands.",
42
43
  },
43
44
  };
44
45
  const HELP_BODY_TEMPLATE = [
45
46
  "## Workflow",
46
- "`bind -> round open -> [summon | amend | delivery work]* -> delivery commits -> round close -> [round open | amend | petition claim | petition forfeit]`",
47
+ "`bind -> round open -> [summon/ask | amend | delivery work]* -> delivery commits -> round close -> [round open | amend | petition claim | petition forfeit]`",
47
48
  "",
48
49
  `- **\${bind}**: Create the active Keiyaku branch and protocol files from Markdown stdin. The worktree must be clean; call it once per Keiyaku.`,
49
50
  `- **\${roundOpen}**: Record the delivery round title, objective, and brief before work starts. It does not run agents or mutate delivery code.`,
50
- `- **\${summon}**: Summon a ${ACTOR_IDENTITY} for bounded work. If a round is open, the round scope is injected automatically; the caller still owns review, integration, and commits.`,
51
+ `- **\${summon}** / **ask**: Summon a ${ACTOR_IDENTITY} for bounded work. If a round is open, the round scope is injected automatically; the caller still owns review, integration, and commits.`,
51
52
  `- **zako list / zako show**: Inspect available ${ACTOR_IDENTITY_PLURAL} and their resolved provider/executable before choosing \`keiyaku summon NAME\`.`,
52
53
  "- **delivery commits**: Commit the actual code or documentation changes in git before closing the round.",
53
54
  `- **\${roundClose}**: Record the report and non-empty delivery commit list after the commits already exist. It does not create delivery commits.`,
@@ -58,7 +59,7 @@ const HELP_BODY_TEMPLATE = [
58
59
  "",
59
60
  "## Command Boundaries",
60
61
  `- \`\${roundOpen}\` and \`\${roundClose}\` are ledger operations; they do not summon agents and do not edit delivery code.`,
61
- `- \`\${summon}\` can run outside a Keiyaku, but an open round gives the ${ACTOR_IDENTITY} better context.`,
62
+ `- \`\${summon}\` and \`ask\` can run outside a Keiyaku, but an open round gives the ${ACTOR_IDENTITY} better context.`,
62
63
  `- \`\${summon}\` starts a new session. Use \`keiyaku resume RESPONSE_PATH\` to continue a prior response artifact.`,
63
64
  "- `zako list` and `zako show <name>` are read-only inspection commands; they do not start a helper.",
64
65
  "- `keiyaku resume` restores the agent from the response artifact; `--incognito` summons cannot be resumed later.",
@@ -109,6 +110,7 @@ export const STATUS_OPERATION_NAMES = {
109
110
  bind: TOOL_NAMES.bind,
110
111
  roundOpen: TOOL_NAMES.roundOpen,
111
112
  roundClose: TOOL_NAMES.roundClose,
113
+ summon: TOOL_NAMES.summon,
112
114
  close: TOOL_NAMES.close,
113
115
  };
114
116
  function renderArchitectText(template) {
@@ -136,15 +138,15 @@ const STATUS_HINT_RULES = [
136
138
  },
137
139
  {
138
140
  when: (input) => input.active && input.openRoundExists === false,
139
- render: (input) => renderWithOperationNames("Open a round with ${roundOpen} before starting delivery work; then use ${summon} to run tests and verify, or ${amend} to rewrite the terms.", input.operationNames),
141
+ render: (input) => renderWithOperationNames("You can ask/summon for investigation now. Open a round with ${roundOpen} before recording delivery work.", input.operationNames),
140
142
  },
141
143
  {
142
144
  when: (input) => !input.active && input.localKeiyakuBranches.length > 0,
143
- render: (input) => `No active keiyaku branch; checkout one to resume: ${formatBranchCandidates(input.localKeiyakuBranches, STATUS_BRANCH_CANDIDATE_LIMIT)}.`,
145
+ render: (input) => `ask and summon are available here. To resume a contract, checkout: ${formatBranchCandidates(input.localKeiyakuBranches, STATUS_BRANCH_CANDIDATE_LIMIT)}.`,
144
146
  },
145
147
  {
146
148
  when: (input) => !input.active && input.localKeiyakuBranches.length === 0,
147
- render: (input) => renderWithOperationNames("No active keiyaku branch; ${bind} a new keiyaku when ready.", input.operationNames),
149
+ render: (input) => "ask and summon are available for investigation outside a contract.",
148
150
  },
149
151
  {
150
152
  when: (input) => input.protocolFiles.draft.present,
@@ -172,7 +174,7 @@ export function buildStatusDirtyWorktreeBlocker(dirtyStatusLines) {
172
174
  : [];
173
175
  return [STATUS_TEXT.blockers.dirtyWorktree, ...shownLines, ...overflowLine].join("\n");
174
176
  }
175
- export function evaluateStatusBlockedByOperation(input) {
177
+ export function evaluateStatusUnavailableByCommand(input) {
176
178
  const bindBlockers = [];
177
179
  const roundOpenBlockers = [];
178
180
  const roundCloseBlockers = [];
@@ -181,10 +183,10 @@ export function evaluateStatusBlockedByOperation(input) {
181
183
  const hasBlockingDirtyForRoundOps = input.blockingDirtyStatusLines.length > 0;
182
184
  const missingProtocolFiles = !input.protocolFiles.keiyaku.present;
183
185
  if (hasDirtyWorktree) {
184
- bindBlockers.push(buildStatusDirtyWorktreeBlocker(input.dirtyStatusLines));
186
+ bindBlockers.push(STATUS_TEXT.blockers.dirtyWorktreeSummary);
185
187
  }
186
188
  if (hasBlockingDirtyForRoundOps) {
187
- const roundOpsDirtyBlocker = buildStatusDirtyWorktreeBlocker(input.blockingDirtyStatusLines);
189
+ const roundOpsDirtyBlocker = STATUS_TEXT.blockers.dirtyWorktreeSummary;
188
190
  roundOpenBlockers.push(roundOpsDirtyBlocker);
189
191
  roundCloseBlockers.push(roundOpsDirtyBlocker);
190
192
  }
@@ -209,6 +211,7 @@ export function evaluateStatusBlockedByOperation(input) {
209
211
  const missingActiveBranch = renderWithOperationNames("No active keiyaku branch; checkout a keiyaku/* branch or ${bind} a new keiyaku first.", input.operationNames);
210
212
  roundOpenBlockers.push(missingActiveBranch);
211
213
  roundCloseBlockers.push(missingActiveBranch);
214
+ closeBlockers.push(missingActiveBranch);
212
215
  if (input.protocolFiles.keiyaku.present) {
213
216
  bindBlockers.push(STATUS_TEXT.blockers.bindProtocolOutsideActive);
214
217
  }
@@ -13,6 +13,7 @@ export const ENV_KEYS = {
13
13
  FAKE_SUBAGENT: "KEIYAKU_FAKE_SUBAGENT",
14
14
  SUBAGENT_EXEC_TIMEOUT_MS: "KEIYAKU_SUBAGENT_EXEC_TIMEOUT_MS",
15
15
  SUBAGENT_EXEC_IDLE_TIMEOUT_MS: "KEIYAKU_SUBAGENT_EXEC_IDLE_TIMEOUT_MS",
16
+ SUBAGENT_PROGRESS_INTERVAL_MIN: "KEIYAKU_SUBAGENT_PROGRESS_INTERVAL_MIN",
16
17
  CODEX_EXEC_MAX_CAPTURE_CHARS: "KEIYAKU_CODEX_EXEC_MAX_CAPTURE_CHARS",
17
18
  CLAUDE_EXEC_MAX_CAPTURE_CHARS: "KEIYAKU_CLAUDE_EXEC_MAX_CAPTURE_CHARS",
18
19
  GEMINI_EXEC_MAX_CAPTURE_CHARS: "KEIYAKU_GEMINI_EXEC_MAX_CAPTURE_CHARS",
@@ -33,6 +34,7 @@ const ENV_KEY_ENTRIES = [
33
34
  { key: ENV_KEYS.DISABLE_SUBAGENT_SPAWN, defaultValue: "0", description: "Disable subprocess subagent spawning when set to 1", category: "subagent" },
34
35
  { key: ENV_KEYS.SUBAGENT_EXEC_TIMEOUT_MS, defaultValue: "2700000", description: "Subagent execution timeout in milliseconds", category: "subagent" },
35
36
  { key: ENV_KEYS.SUBAGENT_EXEC_IDLE_TIMEOUT_MS, defaultValue: "600000", description: "Subagent idle timeout in milliseconds", category: "subagent" },
37
+ { key: ENV_KEYS.SUBAGENT_PROGRESS_INTERVAL_MIN, defaultValue: "0", description: "Print subagent tool/activity summaries every N minutes when greater than 0", category: "subagent" },
36
38
  { key: ENV_KEYS.CODEX_EXEC_MAX_CAPTURE_CHARS, defaultValue: "200000", description: "Codex runtime output/diagnostics capture cap", category: "subagent" },
37
39
  { key: ENV_KEYS.CLAUDE_EXEC_MAX_CAPTURE_CHARS, defaultValue: "200000", description: "Claude runtime output/diagnostics capture cap", category: "subagent" },
38
40
  { key: ENV_KEYS.GEMINI_EXEC_MAX_CAPTURE_CHARS, defaultValue: "200000", description: "Gemini runtime output/diagnostics capture cap", category: "subagent" },
@@ -73,6 +73,7 @@ const subagentSchema = z.object({
73
73
  disableSpawn: booleanFlagEnv(ENV_KEYS.DISABLE_SUBAGENT_SPAWN),
74
74
  execTimeoutMs: positiveIntEnv(ENV_KEYS.SUBAGENT_EXEC_TIMEOUT_MS),
75
75
  execIdleTimeoutMs: positiveIntEnv(ENV_KEYS.SUBAGENT_EXEC_IDLE_TIMEOUT_MS),
76
+ progressIntervalMin: nonNegativeIntEnv(ENV_KEYS.SUBAGENT_PROGRESS_INTERVAL_MIN),
76
77
  fakeMode: z
77
78
  .string()
78
79
  .default(getEnvDefaultValue(ENV_KEYS.FAKE_SUBAGENT))
@@ -120,6 +121,7 @@ export const envSchema = z
120
121
  [ENV_KEYS.FAKE_SUBAGENT]: z.string().optional(),
121
122
  [ENV_KEYS.SUBAGENT_EXEC_TIMEOUT_MS]: z.string().optional(),
122
123
  [ENV_KEYS.SUBAGENT_EXEC_IDLE_TIMEOUT_MS]: z.string().optional(),
124
+ [ENV_KEYS.SUBAGENT_PROGRESS_INTERVAL_MIN]: z.string().optional(),
123
125
  [ENV_KEYS.CODEX_EXEC_MAX_CAPTURE_CHARS]: z.string().optional(),
124
126
  [ENV_KEYS.CLAUDE_EXEC_MAX_CAPTURE_CHARS]: z.string().optional(),
125
127
  [ENV_KEYS.GEMINI_EXEC_MAX_CAPTURE_CHARS]: z.string().optional(),
@@ -143,6 +145,7 @@ export const envSchema = z
143
145
  disableSpawn: rawEnv[ENV_KEYS.DISABLE_SUBAGENT_SPAWN],
144
146
  execTimeoutMs: rawEnv[ENV_KEYS.SUBAGENT_EXEC_TIMEOUT_MS],
145
147
  execIdleTimeoutMs: rawEnv[ENV_KEYS.SUBAGENT_EXEC_IDLE_TIMEOUT_MS],
148
+ progressIntervalMin: rawEnv[ENV_KEYS.SUBAGENT_PROGRESS_INTERVAL_MIN],
146
149
  fakeMode: rawEnv[ENV_KEYS.FAKE_SUBAGENT],
147
150
  }),
148
151
  codex: codexSchema.parse({
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by scripts/generate-version.mjs
2
- export const VERSION = "0.1.85";
2
+ export const VERSION = "0.1.87";
package/build/git/core.js CHANGED
@@ -56,7 +56,6 @@ export function createGit(cwd) {
56
56
  });
57
57
  git.env("GIT_TERMINAL_PROMPT", "0");
58
58
  git.env("GCM_INTERACTIVE", "Never");
59
- git.env("GIT_EDITOR", "true");
60
59
  git.env("GIT_MERGE_AUTOEDIT", "no");
61
60
  git.env("LC_ALL", "C");
62
61
  return git;
@@ -11,7 +11,8 @@ const INFO_SECTION_TITLE = "Info";
11
11
  const HINTS_SECTION_TITLE = "Hints";
12
12
  const REVIEW_SECTION_TITLE = "Review";
13
13
  const STATUS_KEIYAKU_SECTION_TITLE = "Keiyaku";
14
- const STATUS_BLOCKED_SECTION_TITLE = "Blocked";
14
+ const STATUS_WORKTREE_SECTION_TITLE = "Worktree";
15
+ const STATUS_COMMANDS_SECTION_TITLE = "Commands";
15
16
  const CONTENT_SECTION_TITLE = "Content";
16
17
  const STATUS_ACTIVE_SUMMARY_PREFIX = "● Active";
17
18
  const STATUS_INACTIVE_SUMMARY = "○ Inactive";
@@ -26,8 +27,6 @@ const INFO_COMMIT_MAX_CHARS = 100;
26
27
  const INFO_PLEA_MAX_CHARS = 220;
27
28
  const DROPPED_CHANGES_MAX_ITEMS = FORMAT_LIST_MAX_ITEMS;
28
29
  const HINTS_LIST_MAX_ITEMS = 12;
29
- const BLOCKER_LIST_MAX_ITEMS = FORMAT_LIST_MAX_ITEMS;
30
- const BLOCKER_LIST_MAX_ITEM_CHARS = 300;
31
30
  const STATUS_PREVIEW_MAX_CHARS = 400;
32
31
  const BIND_PROTOCOL_FILES_CREATED = [KEIYAKU_FILE];
33
32
  const BIND_PROTOCOL_FILES_LABEL = "Protocol Files";
@@ -36,7 +35,6 @@ export const STRUCTURED_SUMMARY_MAX_CHARS = 20_000;
36
35
  export const STRUCTURED_DIFF_MAX_CHARS = 8_000;
37
36
  export const STRUCTURED_RULES_MAX_CHARS = 5_000;
38
37
  export const STRUCTURED_TRUNCATED_SUFFIX = "...[truncated]";
39
- const BLOCKED_ITEMS_FIELD = "blockedItems";
40
38
  const HEADER_MAX_WIDTH = 40;
41
39
  const HEADER_DASH = "┈";
42
40
  function supportsHeaderColor() {
@@ -168,16 +166,8 @@ function buildSection(title, content) {
168
166
  return null;
169
167
  return { title, body };
170
168
  }
171
- function formatBlockedOperation(label, blockers) {
172
- if (blockers.length === 0)
173
- return [];
174
- return [
175
- `${label}:`,
176
- ...formatList(`${label} Blockers`, blockers, {
177
- maxItems: BLOCKER_LIST_MAX_ITEMS,
178
- maxItemChars: BLOCKER_LIST_MAX_ITEM_CHARS,
179
- }),
180
- ];
169
+ function formatUnavailableReasons(reasons) {
170
+ return reasons.map((reason) => reason.trim().replace(/[.。]+$/, "")).join("; ");
181
171
  }
182
172
  function resolveStructuredStringLimit(key, parentKey) {
183
173
  if (key === "summary")
@@ -200,8 +190,6 @@ function resolveStructuredStringLimit(key, parentKey) {
200
190
  if (key === "keiyakuPreview" || key === "openRoundPreview" || key === "lastClosedRoundPreview") {
201
191
  return STATUS_PREVIEW_MAX_CHARS;
202
192
  }
203
- if (parentKey === BLOCKED_ITEMS_FIELD)
204
- return BLOCKER_LIST_MAX_ITEM_CHARS;
205
193
  if (key === "hints" || key === "existingBranches" || key === "droppedChanges") {
206
194
  return FORMAT_LIST_MAX_ITEM_CHARS;
207
195
  }
@@ -480,19 +468,14 @@ export function buildHelpResponse(input) {
480
468
  };
481
469
  }
482
470
  export function buildStatusResponse(result, labels) {
483
- const blockedItems = {};
484
- if (result.blockedByOperation.bind.length > 0) {
485
- blockedItems[labels.bind] = result.blockedByOperation.bind;
486
- }
487
- if (result.blockedByOperation.roundOpen.length > 0) {
488
- blockedItems[labels.roundOpen] = result.blockedByOperation.roundOpen;
489
- }
490
- if (result.blockedByOperation.roundClose.length > 0) {
491
- blockedItems[labels.roundClose] = result.blockedByOperation.roundClose;
492
- }
493
- if (result.blockedByOperation.close.length > 0) {
494
- blockedItems[labels.close] = result.blockedByOperation.close;
495
- }
471
+ const commandReasons = {
472
+ [labels.bind]: result.unavailableByCommand.bind,
473
+ [labels.roundOpen]: result.unavailableByCommand.roundOpen,
474
+ [labels.roundClose]: result.unavailableByCommand.roundClose,
475
+ [labels.close]: result.unavailableByCommand.close,
476
+ };
477
+ const availableCommands = ["ask", labels.summon, ...Object.entries(commandReasons).filter(([, reasons]) => reasons.length === 0).map(([label]) => label)];
478
+ const unavailableCommands = Object.fromEntries(Object.entries(commandReasons).filter(([, reasons]) => reasons.length > 0));
496
479
  const summaryLine = result.branch
497
480
  ? `${STATUS_ACTIVE_SUMMARY_PREFIX} — ${result.branch} (round ${result.round})`
498
481
  : STATUS_INACTIVE_SUMMARY;
@@ -501,16 +484,17 @@ export function buildStatusResponse(result, labels) {
501
484
  ...formatMaybe("Open Round Preview", result.openRoundPreview, STATUS_PREVIEW_MAX_CHARS),
502
485
  ...formatMaybe("Last Closed Round Preview", result.lastClosedRoundPreview, STATUS_PREVIEW_MAX_CHARS),
503
486
  ];
504
- const readinessLines = [
505
- ...formatBlockedOperation(labels.bind, result.blockedByOperation.bind),
506
- ...formatBlockedOperation(labels.roundOpen, result.blockedByOperation.roundOpen),
507
- ...formatBlockedOperation(labels.roundClose, result.blockedByOperation.roundClose),
508
- ...formatBlockedOperation(labels.close, result.blockedByOperation.close),
487
+ const unavailableCommandLines = Object.entries(unavailableCommands).map(([label, reasons]) => `• ${label}: ${formatUnavailableReasons(reasons)}`);
488
+ const commandLines = [
489
+ "Available Commands:",
490
+ ...availableCommands.map((label) => `• ${label}`),
491
+ ...(unavailableCommandLines.length > 0 ? ["", "Unavailable Commands:", ...unavailableCommandLines] : []),
509
492
  ];
510
493
  const hintLines = formatHints(result.hints, { maxItems: HINTS_LIST_MAX_ITEMS, maxItemChars: FORMAT_LIST_MAX_ITEM_CHARS });
511
494
  const text = assembleResponse(`${RESPONSE_MARKERS.round} Status`, summaryLine, [
512
495
  buildSection(STATUS_KEIYAKU_SECTION_TITLE, stateLines),
513
- buildSection(STATUS_BLOCKED_SECTION_TITLE, readinessLines),
496
+ buildSection(STATUS_WORKTREE_SECTION_TITLE, result.worktreeStatus ?? "Clean"),
497
+ buildSection(STATUS_COMMANDS_SECTION_TITLE, commandLines),
514
498
  buildSection(HINTS_SECTION_TITLE, hintLines),
515
499
  ].filter((section) => section !== null));
516
500
  return {
@@ -524,7 +508,8 @@ export function buildStatusResponse(result, labels) {
524
508
  keiyakuPreview: result.keiyakuPreview ?? null,
525
509
  openRoundPreview: result.openRoundPreview ?? null,
526
510
  lastClosedRoundPreview: result.lastClosedRoundPreview ?? null,
527
- blockedItems,
511
+ availableCommands,
512
+ unavailableCommands,
528
513
  }),
529
514
  };
530
515
  }
@@ -26,6 +26,7 @@ export const summonToolSchemaBase = z.object({
26
26
  task: z.string(),
27
27
  context: z.string().optional(),
28
28
  agent: z.string().optional(),
29
+ model: z.string().optional(),
29
30
  resumePath: z.string().optional(),
30
31
  incognito: z.boolean().optional(),
31
32
  cwd: z.string().optional(),
@@ -3,7 +3,7 @@ import * as path from "node:path";
3
3
  import { KEIYAKU_ARTIFACT_DIRS, KEIYAKU_BRANCH_PREFIX, KEIYAKU_DRAFT_DIR, KEIYAKU_FILE, } from "../../keiyaku.js";
4
4
  import { isErrnoException } from "../../errno.js";
5
5
  import { FlowError } from "../../flow-error.js";
6
- import { evaluateStatusBlockedByOperation, evaluateStatusHints, STATUS_OPERATION_NAMES, } from "../../config/architect-hints.js";
6
+ import { evaluateStatusUnavailableByCommand, evaluateStatusHints, buildStatusDirtyWorktreeBlocker, STATUS_OPERATION_NAMES, } from "../../config/architect-hints.js";
7
7
  import { getCurrentBranch, isGitRepo, listLocalKeiyakuBranches } from "../../git/branches.js";
8
8
  import { getKeiyakuBase } from "../../git/keiyaku-state.js";
9
9
  import { getDirtyFiles, getUntrackedFiles, isPathTracked, renderDirtyFileStatusLine } from "../../git/worktree.js";
@@ -45,8 +45,8 @@ function isPathInDirectory(filePath, directoryPath) {
45
45
  const normalizedDirectory = normalizeStatusPath(directoryPath).replace(/\/+$/, "");
46
46
  return normalizedPath === normalizedDirectory || normalizedPath.startsWith(`${normalizedDirectory}/`);
47
47
  }
48
- function buildBlockedByOperation(input) {
49
- return evaluateStatusBlockedByOperation(input);
48
+ function buildUnavailableByCommand(input) {
49
+ return evaluateStatusUnavailableByCommand(input);
50
50
  }
51
51
  function buildStatusHints(input) {
52
52
  return evaluateStatusHints(input);
@@ -71,6 +71,7 @@ export async function readKeiyakuStatus(input) {
71
71
  const roundBlockingDirtyFiles = filterDirtyFilesByAllowlist(cwd, bindBlockingDirtyFiles, []).dirtyFiles;
72
72
  const dirtyStatusLines = bindBlockingDirtyFiles.map((file) => renderDirtyFileStatusLine(file));
73
73
  const blockingDirtyStatusLines = roundBlockingDirtyFiles.map((file) => renderDirtyFileStatusLine(file));
74
+ const worktreeStatus = dirtyStatusLines.length > 0 ? buildStatusDirtyWorktreeBlocker(dirtyStatusLines) : undefined;
74
75
  const draftPath = latestDraft?.path ?? KEIYAKU_DRAFT_DIR;
75
76
  const draftDirty = draftPresent ? rawDirtyPaths.includes(draftPath) : false;
76
77
  const draftTracked = draftPresent ? await isPathTracked(cwd, draftPath) : undefined;
@@ -90,7 +91,7 @@ export async function readKeiyakuStatus(input) {
90
91
  if (!currentBranch.startsWith(KEIYAKU_BRANCH_PREFIX)) {
91
92
  const keiyakuContent = await readOptionalFile(cwd, KEIYAKU_FILE);
92
93
  const localKeiyakuBranches = await listLocalKeiyakuBranches(cwd);
93
- const blockedByOperation = buildBlockedByOperation({
94
+ const unavailableByCommand = buildUnavailableByCommand({
94
95
  active: false,
95
96
  dirtyStatusLines,
96
97
  blockingDirtyStatusLines,
@@ -100,8 +101,9 @@ export async function readKeiyakuStatus(input) {
100
101
  return {
101
102
  active: false,
102
103
  round: 0,
104
+ worktreeStatus,
103
105
  keiyakuPreview: toStatusPreview(keiyakuContent ? extractDocumentPreviewBySections(keiyakuContent, false) : undefined),
104
- blockedByOperation,
106
+ unavailableByCommand,
105
107
  hints: buildStatusHints({
106
108
  active: false,
107
109
  protocolFiles,
@@ -121,7 +123,7 @@ export async function readKeiyakuStatus(input) {
121
123
  const keiyakuPreview = toStatusPreview(keiyakuContent ? extractDocumentPreviewBySections(keiyakuContent, false) : undefined);
122
124
  const openRoundPreview = renderRoundLedgerPreview(openRound);
123
125
  const lastClosedRoundPreview = renderRoundLedgerPreview(latestClosedRound);
124
- const blockedByOperation = buildBlockedByOperation({
126
+ const unavailableByCommand = buildUnavailableByCommand({
125
127
  active: true,
126
128
  dirtyStatusLines,
127
129
  blockingDirtyStatusLines,
@@ -135,10 +137,11 @@ export async function readKeiyakuStatus(input) {
135
137
  branch: currentBranch,
136
138
  baseBranch: baseBranch ?? undefined,
137
139
  round: getMaxRound(ledgerEntries),
140
+ worktreeStatus,
138
141
  keiyakuPreview,
139
142
  openRoundPreview,
140
143
  lastClosedRoundPreview,
141
- blockedByOperation,
144
+ unavailableByCommand,
142
145
  hints: buildStatusHints({
143
146
  active: true,
144
147
  baseBranch: baseBranch ?? undefined,
@@ -70,6 +70,7 @@ export async function runSummon(input) {
70
70
  const subagentResult = await runSubagentWithResult(selectedSubagent, prompt, cwd, keiyakuContext.openRound?.round ?? 0, {
71
71
  signal,
72
72
  resume,
73
+ model: input.model,
73
74
  });
74
75
  return {
75
76
  summary: subagentResult.summary,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/keiyaku",
3
- "version": "0.1.85",
3
+ "version": "0.1.87",
4
4
  "description": "CLI for running iterative keiyaku workflows with Codex subagents.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -44,7 +44,7 @@
44
44
  "@google/gemini-cli-core": "^0.42.0",
45
45
  "@modelcontextprotocol/sdk": "1.26.0",
46
46
  "@openai/codex-sdk": "^0.113.0",
47
- "simple-git": "^3.21.0",
47
+ "simple-git": "^3.36.0",
48
48
  "zod": "4.3.6"
49
49
  },
50
50
  "devDependencies": {
@@ -21,12 +21,12 @@ keiyaku <command> --help
21
21
  Use this shape for committed delivery work:
22
22
 
23
23
  ```text
24
- bind -> round open -> [summon | amend | delivery work]* -> delivery commits -> round close -> [round open | amend | petition claim | petition forfeit]
24
+ bind -> round open -> [summon/ask | amend | delivery work]* -> delivery commits -> round close -> [round open | amend | petition claim | petition forfeit]
25
25
  ```
26
26
 
27
27
  - `bind`: create the active Keiyaku branch and `KEIYAKU.md` from Markdown stdin.
28
28
  - `round open`: record the scope/objective/brief before delivery work starts.
29
- - `summon`: summon a Zako helper for bounded work with `keiyaku summon NAME [prompt|-]`.
29
+ - `summon` / `ask`: summon a Zako helper for bounded work with `keiyaku summon NAME [prompt|-]` or `keiyaku ask NAME [prompt|-]`.
30
30
  - `resume`: continue a prior summon session with `keiyaku resume RESPONSE_PATH [prompt|-]`.
31
31
  - `zako list` / `zako show <name>`: inspect available Zako profiles before choosing `keiyaku summon NAME`.
32
32
  - `round close`: after delivery commits exist, record the report and non-empty commit list.
@@ -37,7 +37,7 @@ bind -> round open -> [summon | amend | delivery work]* -> delivery commits -> r
37
37
 
38
38
  ## Temporary Work
39
39
 
40
- For temporary tasks, quick inspection, or bounded helper work, use `summon` directly. It does not require an active Keiyaku. If a round is open, the Zako receives the round scope automatically; otherwise it receives only the task, context, and reference files.
40
+ For temporary tasks, quick inspection, or bounded helper work, use `summon` or `ask` directly. It does not require an active Keiyaku. If a round is open, the Zako receives the round scope automatically; otherwise it receives only the task, context, and reference files.
41
41
 
42
42
  Recommended summon prompts use this shape, but the CLI accepts plain text and does not require Markdown sections:
43
43
 
@@ -60,6 +60,7 @@ keiyaku guide
60
60
  keiyaku zako list
61
61
  keiyaku round --help
62
62
  keiyaku summon --help
63
+ keiyaku ask --help
63
64
  keiyaku resume --help
64
65
  keiyaku petition claim --help
65
66
  ```