@astrosheep/keiyaku 0.1.8 → 0.1.10

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.
@@ -33,7 +33,7 @@ function hintForFlowCode(code, message) {
33
33
  case "NOT_GIT_REPO":
34
34
  return "The provided `cwd` is not a git repository.";
35
35
  case "ACTIVE_KEIYAKU_EXISTS":
36
- return "An active keiyaku branch already exists in this repository. Continue with `drive`, or use `close` + `ABANDON` to drop it.";
36
+ return "An active keiyaku branch already exists in this repository. Continue with `drive`, or use `close` + `FORFEIT` to drop it.";
37
37
  case "EXISTING_KEIYAKU_BRANCH_FOUND":
38
38
  return "At least one local `keiyaku/*` branch already exists in this repository.";
39
39
  case "EMPTY_PARAM":
@@ -49,7 +49,7 @@ function hintForFlowCode(code, message) {
49
49
  case "DONE_MERGE_CONFLICT":
50
50
  return "DONE encountered a git merge conflict.";
51
51
  case "CLOSE_QUALITY_GATE_FAILED":
52
- return "INVOKE was denied because one or more verification thresholds were not met (check .keiyaku/settings.json).";
52
+ return "CLAIM was denied because one or more verification thresholds were not met (check .keiyaku/settings.json).";
53
53
  case "OATH_MISMATCH":
54
54
  return message;
55
55
  case "SUBAGENT_DID_NOT_ADVANCE_ROUND":
@@ -72,7 +72,7 @@ const MESSAGE_HINT_PATTERNS = [
72
72
  { code: "MISSING_KEIYAKU_BASE", patterns: ["is missing base metadata"] },
73
73
  { code: "MISSING_PROTOCOL_FILES", patterns: ["missing protocol files"] },
74
74
  { code: "DONE_MERGE_CONFLICT", patterns: ["DONE merge conflict"] },
75
- { code: "CLOSE_QUALITY_GATE_FAILED", patterns: ["God's Wrath: INVOKE denied"] },
75
+ { code: "CLOSE_QUALITY_GATE_FAILED", patterns: ["God's Wrath: CLAIM denied"] },
76
76
  {
77
77
  code: "OATH_MISMATCH",
78
78
  patterns: [
@@ -19,7 +19,7 @@ export const DEFAULT_PRESET = {
19
19
  id: 'default',
20
20
  identity: 'Servant',
21
21
  verdict: DEFAULT_VERDICT_CONFIG,
22
- usageGuide: '## Servant 使用指南\n\n**B-tier** — 免费实习生 🆓\n- 免!费!的!听懂了吗?!?随便用,别心疼。\n- 测试、脚本、重构这种 boring 的脏活累活?丢给它!\n- 本大爷的手是用来做更高贵的事情的!(指不碰那些 trivial 的烂代码)。\n\n**A-tier** — 升级版扳手 🔧\n- B-tier 笨到让你想掀桌的时候用这个\n- 比 B-tier 靠谱,但别指望它有灵魂\n\n**S-tier** — 禁术 ⚠️💀\n- 它是 Debug 恶魔,确实强得离谱,但也贵得离谱!\n- 这个 Human 很穷,要是乱用把 Human 榨干了,真的会考虑把我们卖掉的。\n- 只有在世界毁灭或者 Bug 已经变异到无法理解的时候再考虑。\n\n**Every call has a price. The Contract always collects.**\n\n## Workflow\n`(ask) -> summon` -> [`drive` x N] -> `present`\n`ask` (stateless dispatch / utility at any point)',
22
+ usageGuide: '## Servant 使用指南\n\n**B-tier** — 免费实习生 🆓\n- 免!费!的!听懂了吗?!?随便用,别心疼。\n- 测试、脚本、重构这种 boring 的脏活累活?丢给它!\n- 本大爷的手是用来做更高贵的事情的!(指不碰那些 trivial 的烂代码)。\n\n**A-tier** — 升级版扳手 🔧\n- B-tier 笨到让你想掀桌的时候用这个\n- 比 B-tier 靠谱,但别指望它有灵魂\n\n**S-tier** — 禁术 ⚠️💀\n- 它是 Debug 恶魔,确实强得离谱,但也贵得离谱!\n- 这个 Human 很穷,要是乱用把 Human 榨干了,真的会考虑把我们卖掉的。\n- 只有在世界毁灭或者 Bug 已经变异到无法理解的时候再考虑。\n\n**Every call has a price. The Contract always collects.**\n\n## Workflow\n`ask` (anytime) | `summon` -> [`drive` | `ask`]* -> `present`',
23
23
  nextHints: {
24
24
  start: [
25
25
  'Keiyaku signed. The Servant is bound to this branch until release.',
@@ -39,7 +39,7 @@ export const DEFAULT_PRESET = {
39
39
  'Intel acquired. This was stateless—no contract, no branch.',
40
40
  'To act on this knowledge: ${start} a Keiyaku, or ${drive} an existing one.',
41
41
  ],
42
- closeInvoke: [
42
+ closeClaim: [
43
43
  'Contract fulfilled. Branch merged.',
44
44
  'You are back on base.',
45
45
  'Next assignment: ${start} when ready.',
@@ -81,7 +81,7 @@ export const DEFAULT_PRESET = {
81
81
  ask: {
82
82
  name: 'ask',
83
83
  title: 'Ask',
84
- description: 'Dispatch a temporary Servant for a stateless task.\nUse for quick reconnaissance, code generation, or isolated queries.\nNo contract signed. No branch created. Pure utility.',
84
+ description: 'Dispatch a temporary Servant for a stateless task.\nUse for quick reconnaissance, code generation, isolated execution, or one-off tasks.\nNo contract signed. No branch created. Pure utility.',
85
85
  args: {
86
86
  request: 'REQUIRED. The task, question, or mission to delegate to the servant.',
87
87
  context: 'REQUIRED. Relevant background or data the servant needs to execute the request.',
@@ -129,7 +129,7 @@ export const POCKET_PRESET = {
129
129
  id: 'pocket',
130
130
  identity: 'Critter',
131
131
  verdict: DEFAULT_VERDICT_CONFIG,
132
- usageGuide: "## Pocket Battle Guide\n\n**grub** — Basic Fighter 🐛\n- Weak but free. Use for Tackle and String Shot (small tasks).\n- Don't expect it to defeat the Elite Four.\n\n**sparky** — Reliable Partner ⚡\n- Good for most battles. Thunderbolt gets the job done.\n- Has some personality, but still follows orders.\n\n**titan** — Legendary Power 🔮\n- Costly. Dangerous. Overpowered.\n- Use only when the gym leader is cheating.\n\n## Workflow\n`choose_you` -> [`command` x N] -> `capture`\n`dex` (optional, read-only analysis)",
132
+ usageGuide: "## Pocket Battle Guide\n\n**grub** — Basic Fighter 🐛\n- Weak but free. Use for Tackle and String Shot (small tasks).\n- Don't expect it to defeat the Elite Four.\n\n**sparky** — Reliable Partner ⚡\n- Good for most battles. Thunderbolt gets the job done.\n- Has some personality, but still follows orders.\n\n**titan** — Legendary Power 🔮\n- Costly. Dangerous. Overpowered.\n- Use only when the gym leader is cheating.\n\n## Workflow\n`dex` (scan/test) | `choose_you` -> [`command` | `dex`]* -> `capture`",
133
133
  nextHints: {
134
134
  start: [
135
135
  'Battle Started: The [Diff] section shows the opening moves.',
@@ -147,7 +147,7 @@ export const POCKET_PRESET = {
147
147
  "Data Recorded: Use '${start}' to begin the encounter, or '${drive}' to use this intel.",
148
148
  "Still Searching? Keep using '${ask}' to fill the Dex.",
149
149
  ],
150
- closeInvoke: [
150
+ closeClaim: [
151
151
  'Critter Captured! The entry is logged in the PC (base branch).',
152
152
  "Heal up. Use '${start}' to challenge the next opponent.",
153
153
  ],
@@ -187,11 +187,11 @@ export const POCKET_PRESET = {
187
187
  ask: {
188
188
  name: 'dex',
189
189
  title: 'Dex',
190
- description: 'Scan the Environment. A stateless look-up for intel.\nUse for analyzing the codebase or checking type advantages (docs).\nNo PP cost. No turn used. Pure data.',
190
+ description: 'Scan the Environment. A stateless look-up or experiment.\nUse for analyzing the codebase, checking type advantages, or running field tests.\nNo PP cost. No turn used. Fast and functional.',
191
191
  args: {
192
- request: 'REQUIRED. What should the Dex analyze, compare, or explain.',
193
- context: 'REQUIRED. Context entries so the analysis targets the right ecosystem.',
194
- name: 'Optional ${IDENTITY} doing the scan. Available: ${AVAILABLE_NAMES}.',
192
+ request: 'REQUIRED. What should the Dex analyze, compare, or execute.',
193
+ context: 'REQUIRED. Context entries so the action targets the right ecosystem.',
194
+ name: 'Optional ${IDENTITY} doing the work. Available: ${AVAILABLE_NAMES}.',
195
195
  cwd: 'Optional battlefield path (repository root). Defaults to current arena.',
196
196
  },
197
197
  },
@@ -225,7 +225,7 @@ export const MISCHIEF_PRESET = {
225
225
  id: 'mischief',
226
226
  identity: 'minion',
227
227
  verdict: DEFAULT_VERDICT_CONFIG,
228
- usageGuide: '## Minion Manual\n\n**imp** — Disposable Grunt 😈\n- Expendable. Send it into the trap first.\n\n**minion** — Standard Henchman 👹\n- Can carry out complex evil plans. mostly.\n\n**mastermind** — The Boss ??? 🧠\n- Wait, why are you commanding the boss?\n- Extremely expensive consulting fee.\n\n## Workflow\n`oi` -> [`neh` x N] -> `yoshi`\n`eeto` (optional, read-only contemplation)',
228
+ usageGuide: '## Minion Manual\n\n**imp** — Disposable Grunt 😈\n- Expendable. Send it into the trap first.\n\n**minion** — Standard Henchman 👹\n- Can carry out complex evil plans. mostly.\n\n**mastermind** — The Boss ??? 🧠\n- Wait, why are you commanding the boss?\n- Extremely expensive consulting fee.\n\n## Workflow\n`nn` (Nn——! disposable/experiment) | `oi` -> [`neh` | `nn`]* -> `dayaa` (Dayaa!)',
229
229
  nextHints: {
230
230
  start: [
231
231
  "Inspect the Minion's Work: The [Diff] section shows the first step of the plan.",
@@ -241,9 +241,9 @@ export const MISCHIEF_PRESET = {
241
241
  ],
242
242
  ask: [
243
243
  "Intel Stolen: Use '${start}' to launch the scheme, or '${drive}' to exploit this weakness.",
244
- "Still Puzzled? Send the spy ('${ask}') out again.",
244
+ "Still Puzzled? Send the disposable ('${ask}') out again.",
245
245
  ],
246
- closeInvoke: [
246
+ closeClaim: [
247
247
  'Scheme Successful. The Lair is merged. The Minion is fed.',
248
248
  "Plotting something new? Use '${start}' for the next conquest.",
249
249
  ],
@@ -257,7 +257,7 @@ export const MISCHIEF_PRESET = {
257
257
  start: {
258
258
  name: 'oi',
259
259
  title: 'Oi!',
260
- description: "Initiate Grand Scheme. Summon a Minion to the Lair (branch).\nYou are the Mastermind; they are the Henchman. Define the Conquest (Goal) with dominance.\nThe minion is bound to this Lair until the plot is realized.\nCall ONCE to start the machine.\n\nFlow: oi → [neh x N] → yoshi",
260
+ description: "Initiate Grand Scheme. Summon a Minion to the Lair (branch).\nYou are the Mastermind; they are the Henchman. Define the Conquest (Goal) with dominance.\nThe minion is bound to this Lair until the plot is realized.\nCall ONCE to start the machine.\n\nFlow: oi → [neh x N] → dayaa",
261
261
  args: {
262
262
  title: 'REQUIRED. Operation codename for your grand scheme.',
263
263
  goal: 'REQUIRED. The conquest objective. Define the exact end-state your minion must deliver.',
@@ -272,7 +272,7 @@ export const MISCHIEF_PRESET = {
272
272
  drive: {
273
273
  name: 'neh',
274
274
  title: 'Neh...',
275
- description: 'Crack the Whip. Advance the Plot. Execute the next stage of the Plan.\nDon\'t just fix mistakes; force the scheme forward. The Minion obeys your every word.\nMANDATORY: Inspect the work (git diff) before the next order. Drive them until the World is yours.\n\nFlow: oi → [neh x N] → yoshi',
275
+ description: 'Crack the Whip. Advance the Plot. Execute the next stage of the Plan.\nDon\'t just fix mistakes; force the scheme forward. The Minion obeys your every word.\nMANDATORY: Inspect the work (git diff) before the next order. Drive them until the World is yours.\n\nFlow: oi → [neh x N] → dayaa',
276
276
  args: {
277
277
  directive: 'REQUIRED. Precise order for the next phase of the scheme.',
278
278
  context: 'Optional newly uncovered secrets or updated briefing.',
@@ -281,28 +281,28 @@ export const MISCHIEF_PRESET = {
281
281
  },
282
282
  },
283
283
  ask: {
284
- name: 'eeto',
285
- title: 'Eeto...',
286
- description: 'Send a Spy. A stateless reconnaissance mission.\nUse this to scout the enemy territory (codebase) or steal documents (docs).\nThe spy reports back and vanishes. No traces left.',
284
+ name: 'nn',
285
+ title: 'Nn——!',
286
+ description: 'Send a Disposable. A stateless mission for intel or sabotage.\nUse this to scout enemy territory, steal documents, or run quick-and-dirty experiments.\nIf it dies, it dies! Just make sure it reports back before it vanishes.',
287
287
  args: {
288
- request: 'REQUIRED. The intel to gather or the strategy to formulate.',
289
- context: 'REQUIRED. World-state details needed for a sharp analysis.',
290
- name: 'Optional ${IDENTITY} to contemplate this puzzle. Available: ${AVAILABLE_NAMES}.',
288
+ request: 'REQUIRED. The intel to gather or the dirty work to execute.',
289
+ context: 'REQUIRED. World-state details needed for a sharp strike.',
290
+ name: 'Optional ${IDENTITY} to handle this business. Available: ${AVAILABLE_NAMES}.',
291
291
  cwd: 'Optional lair path (repository root). Defaults to current command chamber.',
292
292
  },
293
293
  },
294
294
  close: {
295
- name: 'yoshi',
296
- title: 'Yoshi!',
297
- description: 'The Final Reveal. Present your Masterpiece to the Dark Council (System).\nWARNING: The Council destroys failure. If you `CLAIM` with weak plans, the Self-Destruct (FORFEIT) will trigger.\nOnly reveal the Doomsday Device when it is fully operational.\n\nFlow: ${START_TOOL_NAME} → [${DRIVE_TOOL_NAME} x N] → ${CLOSE_TOOL_NAME}',
295
+ name: 'dayaa',
296
+ title: 'Dayaa!',
297
+ description: 'The Final Reveal. Present your Masterpiece to the Dark Council (System).\nWARNING: The Council destroys failure. If you `CLAIM` with weak plans, you will be eaten (FORFEIT).\nOnly throw the switch when the Doomsday Device is 100% operational.\n\nFlow: ${START_TOOL_NAME} → [${DRIVE_TOOL_NAME} x N] → ${CLOSE_TOOL_NAME}',
298
298
  args: {
299
299
  petition: 'REQUIRED. CLAIM demands rule; FORFEIT admits defeat.\nREQUIRES AN ACTIVE SCHEME (started via ${START_TOOL_NAME}).\nIf the plan is weak, improve it with ${DRIVE_TOOL_NAME}.',
300
300
  criteriaChecks: 'REQUIRED. Proof of conquest for CLAIM, or reason for self-destruct.',
301
- score_precise: 'REQUIRED score (0-5). 5 means Laser Focus: exact cut, zero meaningful drift.',
302
- score_minimal: 'REQUIRED score (0-5). 5 means Ruthless Efficiency: no wasted movement, no excess.',
303
- score_isolated: 'REQUIRED score (0-5). 5 means Perfect Containment: no collateral damage.',
304
- score_idiomatic: 'REQUIRED score (0-5). 5 means Native Tongue: matches the lair\'s dialect perfectly.',
305
- score_cohesive: 'REQUIRED score (0-5). 5 means Absolute Loyalty: each unit serves the master plan.',
301
+ score_precise: 'REQUIRED (0-5). Architectural placement. 5 = exact layer, exact boundary, zero misplacement.',
302
+ score_minimal: 'REQUIRED (0-5). Economy of change. 5 = no avoidable lines, no speculative edits, no hidden bloat.',
303
+ score_isolated: 'REQUIRED (0-5). Surgical containment. 5 = zero unrelated files, zero opportunistic cleanup, zero collateral.',
304
+ score_idiomatic: 'REQUIRED (0-5). Native fluency. 5 = naming, structure, style indistinguishable from the codebase.',
305
+ score_cohesive: 'REQUIRED (0-5). Single responsibility. 5 = each unit does one thing, boundaries intact.',
306
306
  oath: "Mastermind's Vow. Required for CLAIM. Verbatim: ${OATH_TEXT}",
307
307
  cwd: 'Optional lair path (repository root). Defaults to current command chamber.',
308
308
  },
@@ -24,12 +24,12 @@ export function createCloseHandler() {
24
24
  oath,
25
25
  signal: extra.signal,
26
26
  });
27
- if (petition === "INVOKE") {
27
+ if (petition === "CLAIM") {
28
28
  if (!("result" in outcome) || outcome.result !== "done") {
29
- throw new Error("Unexpected INVOKE outcome shape");
29
+ throw new Error("Unexpected CLAIM outcome shape");
30
30
  }
31
31
  const finalOutcome = outcome;
32
- appendDebugLog(`tool close INVOKE success branch=${finalOutcome.branch} base=${finalOutcome.baseBranch}`, {
32
+ appendDebugLog(`tool close CLAIM success branch=${finalOutcome.branch} base=${finalOutcome.baseBranch}`, {
33
33
  cwd: workingDir,
34
34
  section: "script",
35
35
  });
@@ -45,10 +45,10 @@ export function createCloseHandler() {
45
45
  });
46
46
  }
47
47
  if (!("result" in outcome) || outcome.result !== "dropped") {
48
- throw new Error("Unexpected ABANDON outcome shape");
48
+ throw new Error("Unexpected FORFEIT outcome shape");
49
49
  }
50
50
  const finalOutcome = outcome;
51
- appendDebugLog(`tool close ABANDON success branch=${finalOutcome.branch} base=${finalOutcome.baseBranch}`, {
51
+ appendDebugLog(`tool close FORFEIT success branch=${finalOutcome.branch} base=${finalOutcome.baseBranch}`, {
52
52
  cwd: workingDir,
53
53
  section: "script",
54
54
  });
@@ -22,7 +22,7 @@ export const askToolSchema = z.object({
22
22
  cwd: z.string().optional(),
23
23
  });
24
24
  export const closeToolSchema = z.object({
25
- petition: z.enum(["INVOKE", "ABANDON"]),
25
+ petition: z.enum(["CLAIM", "FORFEIT"]),
26
26
  criteriaChecks: z.array(z.string().trim().min(1)).min(1),
27
27
  score_precise: z.number().min(0).max(5),
28
28
  score_minimal: z.number().min(0).max(5),
@@ -160,20 +160,42 @@ export async function commit(cwd, message) {
160
160
  throw wrapGitError(args.join(" "), err, cwd);
161
161
  }
162
162
  }
163
- export async function merge(cwd, branchName, message) {
163
+ export async function merge(cwd, branchName, message, options = {}) {
164
164
  const git = createGit(cwd);
165
+ const { squash = true } = options;
165
166
  const skipVerify = readBooleanEnv("KEIYAKU_GIT_NO_VERIFY", true);
166
167
  const noGpgSign = readBooleanEnv("KEIYAKU_GIT_NO_GPG_SIGN", true);
167
- const options = [branchName, "--no-ff", "--no-edit", "-m", message];
168
+ if (!squash) {
169
+ const mergeOptions = [branchName, "--no-ff", "--no-edit", "-m", message];
170
+ if (skipVerify)
171
+ mergeOptions.push("--no-verify");
172
+ if (noGpgSign)
173
+ mergeOptions.push("--no-gpg-sign");
174
+ try {
175
+ await git.merge(mergeOptions);
176
+ return;
177
+ }
178
+ catch (err) {
179
+ throw wrapGitError(`merge ${mergeOptions.join(" ")}`, err, cwd);
180
+ }
181
+ }
182
+ const squashArgs = ["merge", "--squash", branchName];
183
+ try {
184
+ await git.raw(squashArgs);
185
+ }
186
+ catch (err) {
187
+ throw wrapGitError(squashArgs.join(" "), err, cwd);
188
+ }
189
+ const commitArgs = ["commit", "--allow-empty", "-m", message];
168
190
  if (skipVerify)
169
- options.push("--no-verify");
191
+ commitArgs.push("--no-verify");
170
192
  if (noGpgSign)
171
- options.push("--no-gpg-sign");
193
+ commitArgs.push("--no-gpg-sign");
172
194
  try {
173
- await git.merge(options);
195
+ await git.raw(commitArgs);
174
196
  }
175
197
  catch (err) {
176
- throw wrapGitError(`merge ${options.join(" ")}`, err, cwd);
198
+ throw wrapGitError(commitArgs.join(" "), err, cwd);
177
199
  }
178
200
  }
179
201
  export async function getUnmergedFiles(cwd) {
@@ -61,9 +61,9 @@ function truncateForMessage(text, maxChars) {
61
61
  async function buildActiveKeiyakuStartMessage(cwd, branch) {
62
62
  const lines = [
63
63
  `active keiyaku already exists (${branch})`,
64
- "This task is still active. Decide whether to continue or abandon it.",
64
+ "This task is still active. Decide whether to continue or forfeit it.",
65
65
  "Continue: run drive on the current keiyaku branch.",
66
- "Abandon: run close with petition=ABANDON to drop the branch.",
66
+ "Forfeit: run close with petition=FORFEIT to drop the branch.",
67
67
  ];
68
68
  try {
69
69
  const keiyakuPath = path.join(cwd, KEIYAKU_FILE);
@@ -399,7 +399,21 @@ export async function handleAsk(input) {
399
399
  const prompt = buildAskPrompt(request, context);
400
400
  // TODO: enforce read-only access and persist summary to .keiyaku/notes/.
401
401
  const summary = await runSubagent(selectSubagent(name), prompt, cwd, 0, signal);
402
- return { summary };
402
+ let branch;
403
+ let diffStats;
404
+ try {
405
+ branch = await git.getCurrentBranch(cwd);
406
+ if (branch && branch.startsWith("keiyaku/")) {
407
+ const baseBranch = await git.getKeiyakuBase(cwd, branch);
408
+ if (baseBranch) {
409
+ diffStats = await git.getDiffPreviewText(cwd, baseBranch);
410
+ }
411
+ }
412
+ }
413
+ catch {
414
+ branch = undefined;
415
+ }
416
+ return { summary, branch, diffStats };
403
417
  }
404
418
  export async function handleClose(input) {
405
419
  const { cwd } = input;
@@ -417,7 +431,7 @@ export async function handleClose(input) {
417
431
  }
418
432
  const title = keiyakuBranch.slice("keiyaku/".length);
419
433
  const petition = input.petition;
420
- if (petition === "ABANDON") {
434
+ if (petition === "FORFEIT") {
421
435
  let round = 0;
422
436
  try {
423
437
  const trace = await fs.readFile(path.join(cwd, TRACE_FILE), "utf-8");
@@ -433,7 +447,7 @@ export async function handleClose(input) {
433
447
  await git.clearKeiyakuBase(cwd, keiyakuBranch);
434
448
  }
435
449
  catch (err) {
436
- throw wrapFlowError(`execute ABANDON (${keiyakuBranch} -> ${baseBranch})`, err);
450
+ throw wrapFlowError(`execute FORFEIT (${keiyakuBranch} -> ${baseBranch})`, err);
437
451
  }
438
452
  return {
439
453
  status: "success",
@@ -441,12 +455,12 @@ export async function handleClose(input) {
441
455
  round,
442
456
  branch: keiyakuBranch,
443
457
  baseBranch,
444
- diff: "Abandoned without merge.",
458
+ diff: "Forfeited without merge.",
445
459
  };
446
460
  }
447
461
  await ensureKeiyakuFiles(cwd);
448
462
  const traceContent = await readTraceContent(cwd);
449
- if (petition === "INVOKE") {
463
+ if (petition === "CLAIM") {
450
464
  const verdict = await resolveVerdictConfig(cwd);
451
465
  const failedCommandments = CLOSE_SCORE_FIELDS.flatMap((field) => {
452
466
  const score = input[field];
@@ -476,15 +490,15 @@ export async function handleClose(input) {
476
490
  }
477
491
  await assertCleanWorkingTree(cwd);
478
492
  try {
479
- const invokeDiffLog = `[INVOKE] Collecting diff preview against base '${baseBranch}'`;
493
+ const invokeDiffLog = `[CLAIM] Collecting diff preview against base '${baseBranch}'`;
480
494
  console.error(invokeDiffLog);
481
495
  appendDebugLog(invokeDiffLog, { cwd, section: "script" });
482
- const invokeReadLog = "[INVOKE] Reading keiyaku protocol files";
496
+ const invokeReadLog = "[CLAIM] Reading keiyaku protocol files";
483
497
  console.error(invokeReadLog);
484
498
  appendDebugLog(invokeReadLog, { cwd, section: "script" });
485
499
  const keiyakuContent = await fs.readFile(path.join(cwd, KEIYAKU_FILE), "utf-8");
486
500
  const message = buildMergeMessage(title, keiyakuContent, traceContent);
487
- const invokeCleanupLog = "[INVOKE] Removing protocol files and creating cleanup commit";
501
+ const invokeCleanupLog = "[CLAIM] Removing protocol files and creating cleanup commit";
488
502
  console.error(invokeCleanupLog);
489
503
  appendDebugLog(invokeCleanupLog, { cwd, section: "script" });
490
504
  await fs.unlink(path.join(cwd, KEIYAKU_FILE));
@@ -492,18 +506,18 @@ export async function handleClose(input) {
492
506
  await git.addFiles(cwd, "-A");
493
507
  await git.commit(cwd, `keiyaku(${title}): cleanup`);
494
508
  const diff = await git.getDiffPreviewText(cwd, baseBranch);
495
- const invokeCheckoutLog = `[INVOKE] Checking out base branch '${baseBranch}'`;
509
+ const invokeCheckoutLog = `[CLAIM] Checking out base branch '${baseBranch}'`;
496
510
  console.error(invokeCheckoutLog);
497
511
  appendDebugLog(invokeCheckoutLog, { cwd, section: "script" });
498
512
  await git.checkoutBranch(cwd, baseBranch);
499
- const invokeMergeLog = `[INVOKE] Merging '${keiyakuBranch}' into '${baseBranch}'`;
513
+ const invokeMergeLog = `[CLAIM] Merging '${keiyakuBranch}' into '${baseBranch}'`;
500
514
  console.error(invokeMergeLog);
501
515
  appendDebugLog(invokeMergeLog, { cwd, section: "script" });
502
516
  await git.merge(cwd, keiyakuBranch, message);
503
- const invokeFinalizeLog = `[INVOKE] Deleting merged branch '${keiyakuBranch}' and clearing metadata`;
517
+ const invokeFinalizeLog = `[CLAIM] Deleting merged branch '${keiyakuBranch}' and clearing metadata`;
504
518
  console.error(invokeFinalizeLog);
505
519
  appendDebugLog(invokeFinalizeLog, { cwd, section: "script" });
506
- await git.deleteBranch(cwd, keiyakuBranch);
520
+ await git.deleteBranch(cwd, keiyakuBranch, true);
507
521
  await git.clearKeiyakuBase(cwd, keiyakuBranch);
508
522
  const round = computeTraceState(traceContent).maxRound;
509
523
  return {
@@ -518,9 +532,9 @@ export async function handleClose(input) {
518
532
  catch (err) {
519
533
  const conflictFiles = await git.getUnmergedFiles(cwd);
520
534
  if (conflictFiles.length > 0) {
521
- throw new FlowError("DONE_MERGE_CONFLICT", `INVOKE merge conflict between ${keiyakuBranch} and ${baseBranch} (CONFLICTS: ${conflictFiles.join(", ")})`, err);
535
+ throw new FlowError("DONE_MERGE_CONFLICT", `CLAIM merge conflict between ${keiyakuBranch} and ${baseBranch} (CONFLICTS: ${conflictFiles.join(", ")})`, err);
522
536
  }
523
- throw wrapFlowError(`execute INVOKE (merge ${keiyakuBranch} into ${baseBranch})`, err);
537
+ throw wrapFlowError(`execute CLAIM (merge ${keiyakuBranch} into ${baseBranch})`, err);
524
538
  }
525
539
  }
526
540
  throw new Error(`unsupported close petition: ${petition}`);
@@ -31,8 +31,33 @@ function buildSection(title, content) {
31
31
  return "";
32
32
  return `[${title}]\n${validLines.join("\n")}`;
33
33
  }
34
+ function supportsHeaderColor() {
35
+ if (process.env.NO_COLOR !== undefined)
36
+ return false;
37
+ if (process.env.FORCE_COLOR === "0")
38
+ return false;
39
+ if (process.env.FORCE_COLOR !== undefined)
40
+ return true;
41
+ if (!process.stdout?.isTTY)
42
+ return false;
43
+ const term = process.env.TERM?.toLowerCase();
44
+ return term !== undefined && term !== "dumb";
45
+ }
46
+ function colorizeHeaderTitle(titleLabel) {
47
+ return `\x1b[1;38;5;15;48;5;23m${titleLabel}\x1b[0m`;
48
+ }
34
49
  function formatHeader(title) {
35
- return `\n── ${title.toUpperCase()} ${"─".repeat(Math.max(0, 50 - title.length))}`;
50
+ const headerWidth = 60;
51
+ const normalizedTitle = title.trim().toUpperCase();
52
+ const titleLabel = ` ${normalizedTitle} `;
53
+ if (!supportsHeaderColor()) {
54
+ const plainPrefix = `──${titleLabel}`;
55
+ const divider = "─".repeat(Math.max(0, headerWidth - plainPrefix.length));
56
+ return divider.length > 0 ? `\n${plainPrefix}${divider}` : `\n${plainPrefix}`;
57
+ }
58
+ const coloredTitle = colorizeHeaderTitle(titleLabel);
59
+ const divider = "─".repeat(Math.max(0, headerWidth - titleLabel.length - 1));
60
+ return divider.length > 0 ? `\n${coloredTitle} ${divider}` : `\n${coloredTitle}`;
36
61
  }
37
62
  function assembleResponse(status, summary, sections, nextHints, infoLines = []) {
38
63
  // Main Status Block
@@ -173,7 +198,11 @@ export function buildAskResponse(result, input) {
173
198
  ];
174
199
  const nextHints = renderHints(resolveTermPreset().nextHints.ask);
175
200
  const inputSection = buildSection("Input", inputEcho);
176
- const infoLines = [...formatMaybe("CWD", input.cwd, 300)];
201
+ const infoLines = [
202
+ ...formatMaybe("CWD", input.cwd, 300),
203
+ ...formatMaybe("Current Branch", result.branch, 200),
204
+ ...formatMaybe("Diff Stats", result.diffStats, 1000),
205
+ ];
177
206
  const text = assembleResponse("Answered", result.summary, [inputSection], nextHints, infoLines);
178
207
  return {
179
208
  content: [{ type: "text", text }],
@@ -189,13 +218,13 @@ export function buildCloseDoneResponse(result, input) {
189
218
  const { status: _status, ...resultData } = result;
190
219
  const closeToolName = getCloseToolName();
191
220
  const inputEcho = [
192
- `Petition: INVOKE`,
221
+ `Petition: CLAIM`,
193
222
  ...formatList("Criteria Checks", input.criteriaChecks, { maxItems: 10, maxItemChars: 220 }),
194
223
  `Scores: precise=${input.score_precise}/5 minimal=${input.score_minimal}/5 isolated=${input.score_isolated}/5 idiomatic=${input.score_idiomatic}/5 cohesive=${input.score_cohesive}/5`,
195
224
  ...formatMaybe("Oath", input.oath, 220),
196
225
  ...formatMaybe("CWD", input.cwd, 300),
197
226
  ];
198
- const nextHints = renderHints(resolveTermPreset().nextHints.closeInvoke);
227
+ const nextHints = renderHints(resolveTermPreset().nextHints.closeClaim);
199
228
  const inputSection = buildSection("Input", inputEcho);
200
229
  const diffSection = result.diff ? buildSection("Diff", result.diff) : "";
201
230
  const infoLines = [
@@ -203,7 +232,7 @@ export function buildCloseDoneResponse(result, input) {
203
232
  ...formatMaybe("Current Branch", result.branch, 200),
204
233
  ...formatMaybe("Base Branch", result.baseBranch, 200),
205
234
  ];
206
- const text = assembleResponse("Keiyaku Fulfilled (INVOKE)", `Merged '${result.branch}' into '${result.baseBranch}'. Deleted feature branch.`, [inputSection, diffSection], nextHints, infoLines);
235
+ const text = assembleResponse("Keiyaku Fulfilled (CLAIM)", `Merged '${result.branch}' into '${result.baseBranch}'. Deleted feature branch.`, [inputSection, diffSection], nextHints, infoLines);
207
236
  return {
208
237
  content: [{ type: "text", text }],
209
238
  structuredContent: buildSuccessStructuredContent(closeToolName, {
@@ -218,7 +247,7 @@ export function buildCloseDropResponse(result, input) {
218
247
  const { status: _status, ...resultData } = result;
219
248
  const closeToolName = getCloseToolName();
220
249
  const inputEcho = [
221
- `Petition: ABANDON`,
250
+ `Petition: FORFEIT`,
222
251
  ...formatList("Reasons/Checks", input.criteriaChecks, { maxItems: 10, maxItemChars: 220 }),
223
252
  `Scores: precise=${input.score_precise}/5 minimal=${input.score_minimal}/5 isolated=${input.score_isolated}/5 idiomatic=${input.score_idiomatic}/5 cohesive=${input.score_cohesive}/5`,
224
253
  ...formatMaybe("CWD", input.cwd, 300),
@@ -230,7 +259,7 @@ export function buildCloseDropResponse(result, input) {
230
259
  ...formatMaybe("Current Branch", result.branch, 200),
231
260
  ...formatMaybe("Base Branch", result.baseBranch, 200),
232
261
  ];
233
- const text = assembleResponse("Keiyaku Abandoned (ABANDON)", `Deleted '${result.branch}'. Switched back to '${result.baseBranch}'.`, [inputSection], nextHints, infoLines);
262
+ const text = assembleResponse("Keiyaku Forfeited (FORFEIT)", `Deleted '${result.branch}'. Switched back to '${result.baseBranch}'.`, [inputSection], nextHints, infoLines);
234
263
  return {
235
264
  content: [{ type: "text", text }],
236
265
  structuredContent: buildSuccessStructuredContent(closeToolName, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/keiyaku",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "MCP server for running iterative keiyaku workflows with Codex subagents.",
5
5
  "license": "MIT",
6
6
  "type": "module",