@astrosheep/keiyaku 0.1.9 → 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.
@@ -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.',
@@ -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.',
@@ -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,7 +241,7 @@ 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
246
  closeClaim: [
247
247
  'Scheme Successful. The Lair is merged. The Minion is fed.',
@@ -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
  },
@@ -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) {
@@ -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;
@@ -503,7 +517,7 @@ export async function handleClose(input) {
503
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 {
@@ -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 }],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/keiyaku",
3
- "version": "0.1.9",
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",