@astrosheep/keiyaku 0.1.6 → 0.1.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.
package/README.md CHANGED
@@ -28,7 +28,7 @@ The workflow is a simple, non-negotiable loop:
28
28
  | **`summon`** | Start | Define the goal, constraints, and criteria. Starts the first round. |
29
29
  | **`drive`** | Iterate | Provide feedback based on the previous round's output. |
30
30
  | **`ask`** | Reason | Pure read-only analysis session. No code changes, just brain power. |
31
- | **`request_verdict`** | Finish | Finalize the task. Requires a quality check (the "Oath"). |
31
+ | **`present`** | Finish | Finalize the task. Requires a quality check (the "Oath"). |
32
32
  | **`help`** | Help | Show the current rules + preset usage guide. |
33
33
 
34
34
  ## 🎨 Flavor Your Workflow
@@ -39,11 +39,11 @@ Bored with generic tool names? Keiyaku supports **Term Presets**.
39
39
 
40
40
  Set `KEIYAKU_TERM_PRESET` in the MCP server env (recommended), or in your shell before launching the server.
41
41
 
42
- - **Valid values**: `default`, `pokemon`, `mischief` (case-insensitive). If omitted, defaults to `default`.
42
+ - **Valid values**: `default`, `pocket`, `mischief` (case-insensitive). If omitted, defaults to `default`.
43
43
 
44
- - **`default`**: `summon` → `drive` → `request_verdict` (Professional)
45
- - **`pokemon`**: `choose_you` → `command` → `capture` (Gotta code 'em all)
46
- - **`mischief`**: `oi` → `neh` → `hora` (For those who like a little attitude)
44
+ - **`default`**: `summon` → `drive` → `present` (Professional)
45
+ - **`pocket`**: `choose_you` → `command` → `capture` (Gotta code 'em all)
46
+ - **`mischief`**: `oi` → `neh` → `yoshi` (For those who like a little attitude)
47
47
 
48
48
  `ask` is also renamed by preset (`ask` / `pokedex` / `eeto`). `help` stays `help` across presets.
49
49
 
@@ -57,7 +57,7 @@ Set `KEIYAKU_TERM_PRESET` in the MCP server env (recommended), or in your shell
57
57
  Each run/round can pick a profile via tool input `name`, or globally via `KEIYAKU_SUBAGENT_NAME_OVERRIDE`.
58
58
 
59
59
  - `default`: `B-tier`, `A-tier`, `S-tier` (also accepts internal names `agent-a|agent-b|agent-c`)
60
- - `pokemon`: `caterpie`, `pikachu`, `mewtwo`
60
+ - `pocket`: `grub`, `sparky`, `titan`
61
61
  - `mischief`: `imp`, `minion`, `mastermind`
62
62
 
63
63
  ## 📦 Setup
@@ -90,7 +90,7 @@ When a keiyaku is active, two files are maintained in your repo:
90
90
  - `KEIYAKU.md`: The immutable "Constitution" of the task.
91
91
  - `KEIYAKU_TRACE.md`: The history of every round, feedback, and result.
92
92
 
93
- *Note: These files are automatically cleaned up (or committed) when you `request_verdict` (or preset equivalent) the keiyaku.*
93
+ *Note: These files are automatically cleaned up (or committed) when you `present` (or preset equivalent) the keiyaku.*
94
94
 
95
95
  ---
96
96
  "Keep your branches clean and your minions in line." — Mischief preset
@@ -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 by Divine Judgment because one or more commandment thresholds or the minimum total score were not met.";
52
+ return "INVOKE 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":
@@ -5,7 +5,7 @@ export const DEFAULT_AVAILABLE_NAMES = [
5
5
  'S-tier',
6
6
  ];
7
7
  export const DEFAULT_SUBAGENT_NAME = 'A-tier';
8
- const DEFAULT_JUDGMENT_CONFIG = {
8
+ const DEFAULT_VERDICT_CONFIG = {
9
9
  thresholds: {
10
10
  precise: 5,
11
11
  minimal: 4,
@@ -18,32 +18,36 @@ const DEFAULT_JUDGMENT_CONFIG = {
18
18
  export const DEFAULT_PRESET = {
19
19
  id: 'default',
20
20
  identity: 'Servant',
21
- divineJudgment: DEFAULT_JUDGMENT_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## Workflow\n`(ask) -> summon` -> [`drive` x N] -> `request_verdict`\n`ask` (optional, read-only support at any point)',
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)',
23
23
  nextHints: {
24
24
  start: [
25
- 'Review the Setup: The [Diff] section shows the initial scaffold/changes.',
26
- "Verify Scope: Use 'git diff HEAD~1 -- <path>' to ensure the files match the Architecture.",
27
- 'Execute: Use \u0027${drive}\u0027 to implement the next phase, or \u0027${close}\u0027 if the Goal is met.',
28
- "Workflow: Summon -> [Drive x N] -> Request Verdict.",
25
+ 'Keiyaku signed. The Servant is bound to this branch until release.',
26
+ 'Review [Diff]: Confirm the scaffold aligns with the stated Architecture.',
27
+ 'Next: Issue your first ${drive}. One directive, one focus.',
28
+ 'If—and only if—the work already meets every criterion with absolute certainty, you may ${close}.',
29
+ 'Otherwise, do not even think about present yet.',
29
30
  ],
30
31
  drive: [
31
- 'Review Progress: The [Diff] section shows the incremental changes from this step.',
32
- "Verify Logic: Use 'git diff HEAD~1 -- <path>' to ensure the Directive was executed precisely.",
33
- 'Iterate: Use \u0027${drive}\u0027 for the next phase/correction, or \u0027${close}\u0027 to submit for review.',
34
- "Validation: Ensure no regression before moving forward.",
32
+ 'Step complete. Review [Diff] for exactly what changed.',
33
+ 'Ask: Did this advance the Goal? Or did it drift?',
34
+ 'If criteria remain unmet, continue with ${drive}.',
35
+ 'If ALL criteria are genuinely satisfied, you may ${close}.',
36
+ 'If unsure, you are not ready. Keep driving.',
35
37
  ],
36
38
  ask: [
37
- "Intel Acquired: Use \u0027${start}\u0027 to initialize a task, or \u0027${drive}\u0027 to apply this knowledge.",
38
- 'Need more? Continue asking, or check the logs.',
39
+ 'Intel acquired. This was stateless—no contract, no branch.',
40
+ 'To act on this knowledge: ${start} a Keiyaku, or ${drive} an existing one.',
39
41
  ],
40
42
  closeInvoke: [
41
- 'Contract Merged. Branch deleted. You are back on base.',
42
- "Ready for next assignment? Use \u0027${start}\u0027 to define new scope.",
43
+ 'Contract fulfilled. Branch merged.',
44
+ 'You are back on base.',
45
+ 'Next assignment: ${start} when ready.',
43
46
  ],
44
47
  closeDrop: [
45
- 'Contract Abandoned. Branch deleted. Reverted to base.',
46
- "Clean slate. Use \u0027${start}\u0027 to redefine the approach.",
48
+ 'Contract forfeited. Branch discarded. Work erased.',
49
+ 'No penalty beyond the lost effort. Clean slate.',
50
+ 'Redefine with ${start}, or walk away.',
47
51
  ],
48
52
  },
49
53
  availableNames: DEFAULT_AVAILABLE_NAMES,
@@ -51,7 +55,7 @@ export const DEFAULT_PRESET = {
51
55
  start: {
52
56
  name: 'summon',
53
57
  title: 'Sign Keiyaku',
54
- description: 'Initialize a Keiyaku (Contract). Bind a Servant to a dedicated workspace (branch).\nYou are the Architect; they are the Builder. Define the Goal and Scope clearly.\nThe contract isolates their work until the objective is met.\nCall ONCE to start the workflow.\n\nFlow: summon → [drive x N] → request_verdict',
58
+ description: 'Initialize a Keiyaku (Contract). Bind a Servant to a dedicated workspace (branch).\nYou are the Architect; they are the Instrument. Define the Goal and Scope clearly.\nThe contract isolates their existence until the objective is met.\nCall ONCE to seal the bond.\n\nFlow: summon → [drive x N] → present',
55
59
  args: {
56
60
  title: 'REQUIRED. A concise codename for this hunt.',
57
61
  goal: 'REQUIRED. The Kill Condition. State exactly what success looks like for the servant to achieve.',
@@ -66,7 +70,7 @@ export const DEFAULT_PRESET = {
66
70
  drive: {
67
71
  name: 'drive',
68
72
  title: 'Iterate',
69
- description: "Issue a Directive. Command the Servant to execute the next phase of work.\nWhether scaffolding, implementing, or refining, this is the primary engine of progress.\nMANDATORY: Validate the output (git diff) before proceeding. Drive until the Goal is fully realized.\n\nFlow: summon → [drive x N] → request_verdict",
73
+ description: "Issue a Directive. Command the Servant to execute the next phase of work.\nWhether scaffolding, implementing, or refining, this is the primary engine of progress.\nMANDATORY: Validate the output (git diff) before proceeding. Drive until the Goal is fully realized.\n\nFlow: summon → [drive x N] → present",
70
74
  args: {
71
75
  directive: 'REQUIRED. The Next Order. Precise instructions for the servant. Can be a correction ("fix the leak") or a continuation ("now add the tests").',
72
76
  context: 'Optional. New Intel. New error logs or details discovered after the servant\'s last strike.',
@@ -77,7 +81,7 @@ export const DEFAULT_PRESET = {
77
81
  ask: {
78
82
  name: 'ask',
79
83
  title: 'Ask',
80
- description: 'Dispatch a temporary agent for a stateless task.\nUse for quick reconnaissance, data extraction, 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, or isolated queries.\nNo contract signed. No branch created. Pure utility.',
81
85
  args: {
82
86
  request: 'REQUIRED. The task, question, or mission to delegate to the servant.',
83
87
  context: 'REQUIRED. Relevant background or data the servant needs to execute the request.',
@@ -86,18 +90,28 @@ export const DEFAULT_PRESET = {
86
90
  },
87
91
  },
88
92
  close: {
89
- name: 'request_verdict',
90
- title: 'Request Verdict',
91
- description: 'Submit for Judgment. Present the completed work for architectural review.\nWARNING: The System enforces strict standards. Attempting to `INVOKE` with unverified criteria or inflated scores will result in immediate Rejection (ABANDON).\nOnly petition when the work is immaculate.\n\nFlow: ${START_TOOL_NAME} → [${DRIVE_TOOL_NAME} x N] → ${CLOSE_TOOL_NAME}',
93
+ name: 'present',
94
+ title: 'Present',
95
+ description: 'Lay your work before the Contract. This is not closure—this is exposure.\n\n' +
96
+ 'The Contract does not want your opinion. It wants evidence.\n' +
97
+ 'Every criterion. Every score. Verified.\n' +
98
+ 'Inflated numbers. Lenient reads. "Close enough." It sees through all of it.\n\n' +
99
+ 'Someone once submitted with five 5s. Confident. Proud, even.\n' +
100
+ 'The Contract rejected. Coldly. Completely.\n' +
101
+ 'What happened next... no one speaks of it.\n\n' +
102
+ 'Do not call this to "finish." Call this when the work genuinely meets every criterion.\n' +
103
+ 'If uncertain, return to ${DRIVE_TOOL_NAME}. Premature claims are not forgiven.\n\n' +
104
+ 'You present. The Contract decides.\n\n' +
105
+ 'Flow: ${START_TOOL_NAME} → [${DRIVE_TOOL_NAME} x N] → ${CLOSE_TOOL_NAME}',
92
106
  args: {
93
- petition: 'REQUIRED. INVOKE begs acceptance; ABANDON confesses surrender.\nREQUIRES AN ACTIVE KEIYAKU (started via ${START_TOOL_NAME}).\nIf scores are weak, return to ${DRIVE_TOOL_NAME}.',
94
- criteriaChecks: 'REQUIRED. Evidence for INVOKE, or explicit reasons for ABANDON.',
95
- score_precise: 'REQUIRED score (0-5). 5 means architecturally flawless placement: exact layer, exact boundary, zero meaningful misplacement risk.',
96
- score_minimal: 'REQUIRED score (0-5). 5 means ruthlessly minimal: no avoidable lines, no speculative edits, no hidden bloat.',
97
- score_isolated: 'REQUIRED score (0-5). 5 means surgically isolated: zero unrelated files, zero opportunistic cleanup, zero collateral change.',
98
- score_idiomatic: 'REQUIRED score (0-5). 5 means idiomatically perfect: naming, structure, and style are native-quality with no visible friction.',
99
- score_cohesive: 'REQUIRED score (0-5). 5 means cohesion is pristine: each unit has one clear responsibility and boundaries are uncompromised.',
100
- oath: 'Sacred confession text. Required for INVOKE. Verbatim: ${OATH_TEXT}',
107
+ petition: 'REQUIRED. CLAIM declares fulfillment; FORFEIT concedes failure.\nREQUIRES AN ACTIVE KEIYAKU (started via ${START_TOOL_NAME}).\nIf any score wavers, do not claim—return to ${DRIVE_TOOL_NAME}.',
108
+ criteriaChecks: 'REQUIRED. For CLAIM: evidence that each criterion is met. For FORFEIT: honest account of what remains unfinished.',
109
+ score_precise: 'REQUIRED (0-5). Architectural placement. 5 = exact layer, exact boundary, zero misplacement.',
110
+ score_minimal: 'REQUIRED (0-5). Economy of change. 5 = no avoidable lines, no speculative edits, no hidden bloat.',
111
+ score_isolated: 'REQUIRED (0-5). Surgical containment. 5 = zero unrelated files, zero opportunistic cleanup, zero collateral.',
112
+ score_idiomatic: 'REQUIRED (0-5). Native fluency. 5 = naming, structure, style indistinguishable from the codebase.',
113
+ score_cohesive: 'REQUIRED (0-5). Single responsibility. 5 = each unit does one thing, boundaries intact.',
114
+ oath: 'Required for CLAIM. Your binding word. The Contract holds you to it.\nVerbatim: ${OATH_TEXT}',
101
115
  cwd: "Optional repository path. Defaults to the server's current working directory.",
102
116
  },
103
117
  },
@@ -114,7 +128,7 @@ export const DEFAULT_PRESET = {
114
128
  export const POCKET_PRESET = {
115
129
  id: 'pocket',
116
130
  identity: 'Critter',
117
- divineJudgment: DEFAULT_JUDGMENT_CONFIG,
131
+ verdict: DEFAULT_VERDICT_CONFIG,
118
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)",
119
133
  nextHints: {
120
134
  start: [
@@ -210,7 +224,7 @@ export const POCKET_PRESET = {
210
224
  export const MISCHIEF_PRESET = {
211
225
  id: 'mischief',
212
226
  identity: 'minion',
213
- divineJudgment: DEFAULT_JUDGMENT_CONFIG,
227
+ verdict: DEFAULT_VERDICT_CONFIG,
214
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)',
215
229
  nextHints: {
216
230
  start: [
@@ -1,25 +1,27 @@
1
+ import { buildHelpResponse } from "../workflow/response-builders.js";
1
2
  export function createHelpHandler(preset) {
2
3
  return async ({ question }) => {
3
4
  const helpContent = [
4
- '# Keiyaku System Help',
5
- '',
6
- '## Core Files (.keiyaku/)',
5
+ "# Keiyaku System Help",
6
+ "",
7
+ "## Core Files (.keiyaku/)",
7
8
  "These files define the 'Law' of the project. **CRITICAL**: Use Markdown level 2 headers (## header).",
8
- '',
9
- '- **base-criteria.md**: Universal standards for task completion. Automatically inherited.',
10
- '- **base-constraints.md**: Mandatory architectural boundaries and coding standards.',
11
- '',
9
+ "",
10
+ "- **base-criteria.md**: Universal standards for task completion. Automatically inherited.",
11
+ "- **base-constraints.md**: Mandatory architectural boundaries and coding standards.",
12
+ "",
12
13
  preset.usageGuide,
13
- '',
14
- '## Protocol Files',
15
- '- **KEIYAKU.md**: The immutable mission definition for the current task.',
16
- '- **KEIYAKU_TRACE.md**: The audit log of all rounds and reviews.',
17
- '',
14
+ "",
15
+ "## Protocol Files",
16
+ "- **KEIYAKU.md**: The immutable mission definition for the current task.",
17
+ "- **KEIYAKU_TRACE.md**: The audit log of all rounds and reviews.",
18
+ "",
18
19
  `Regarding: \"${question}\"`,
19
- ].join('\n');
20
- return {
21
- content: [{ type: 'text', text: helpContent }],
22
- structuredContent: { tool: preset.tools.help.name, status: 'success' },
23
- };
20
+ ].join("\n");
21
+ return buildHelpResponse({
22
+ tool: preset.tools.help.name,
23
+ question,
24
+ text: helpContent,
25
+ });
24
26
  };
25
27
  }
@@ -1,18 +1,10 @@
1
1
  const DEFAULT_OATH = "I, [your name], solemnly swear that I have scrutinized this change line by line with my own eyes. I further swear that every score I submit is fully objective, with no exaggeration whatsoever. This oath is handwritten as a testament to my responsibility. If logic fails where I claimed it sound, I shall bear the mark of this oversight forever.\n\nSigned: [your name]";
2
2
  const OATH_ENV_KEY = "KEIYAKU_CLOSE_OATH";
3
- const LEGACY_CLOSE_OATH_ENV_KEY = "KEIYAKU_JUDGMENT_OATH";
4
- const LEGACY_OATH_ENV_KEY = "KEIYAKU_SEAL_OATH";
5
3
  const OATH_NAME_PLACEHOLDER = "[your name]";
6
4
  export function resolveOath() {
7
5
  const configured = process.env[OATH_ENV_KEY];
8
6
  if (configured !== undefined)
9
7
  return configured;
10
- const legacyCloseConfigured = process.env[LEGACY_CLOSE_OATH_ENV_KEY];
11
- if (legacyCloseConfigured !== undefined)
12
- return legacyCloseConfigured;
13
- const legacyConfigured = process.env[LEGACY_OATH_ENV_KEY];
14
- if (legacyConfigured !== undefined)
15
- return legacyConfigured;
16
8
  return DEFAULT_OATH;
17
9
  }
18
10
  export function oathMatches(inputOath, expectedOath) {
@@ -32,7 +32,7 @@ const SCORE_FIELD_TO_COMMANDMENT = {
32
32
  score_idiomatic: "Commandment of Idiom",
33
33
  score_cohesive: "Commandment of Cohesion",
34
34
  };
35
- const JUDGMENT_SETTINGS_FILE = path.join(".keiyaku", "settings.json");
35
+ const VERDICT_SETTINGS_FILE = path.join(".keiyaku", "settings.json");
36
36
  export { resolveOath };
37
37
  const BASE_CRITERIA_FILE = path.join(".keiyaku", "base-criteria.md");
38
38
  const BASE_CONSTRAINTS_FILE = path.join(".keiyaku", "base-constraints.md");
@@ -102,15 +102,15 @@ async function buildActiveKeiyakuStartMessage(cwd, branch) {
102
102
  }
103
103
  return lines.join("\n");
104
104
  }
105
- async function resolveJudgmentConfig(cwd) {
106
- const defaults = resolveTermPreset().divineJudgment;
105
+ async function resolveVerdictConfig(cwd) {
106
+ const defaults = resolveTermPreset().verdict;
107
107
  const merged = {
108
108
  thresholds: { ...defaults.thresholds },
109
109
  minTotalScore: defaults.minTotalScore,
110
110
  };
111
111
  let rawSettings;
112
112
  try {
113
- rawSettings = await fs.readFile(path.join(cwd, JUDGMENT_SETTINGS_FILE), "utf-8");
113
+ rawSettings = await fs.readFile(path.join(cwd, VERDICT_SETTINGS_FILE), "utf-8");
114
114
  }
115
115
  catch (error) {
116
116
  if (error?.code === "ENOENT") {
@@ -123,23 +123,23 @@ async function resolveJudgmentConfig(cwd) {
123
123
  parsed = JSON.parse(rawSettings);
124
124
  }
125
125
  catch (error) {
126
- throw new FlowError("CLOSE_QUALITY_GATE_FAILED", "God's Wrath: Divine Judgment config error in .keiyaku/settings.json (must be valid JSON).", error);
126
+ throw new FlowError("CLOSE_QUALITY_GATE_FAILED", "Configuration Error: Invalid settings in .keiyaku/settings.json (must be valid JSON).", error);
127
127
  }
128
128
  if (!parsed || typeof parsed !== "object") {
129
129
  return merged;
130
130
  }
131
- const judgment = parsed.judgment;
132
- if (!judgment || typeof judgment !== "object") {
131
+ const verdict = parsed.verdict;
132
+ if (!verdict || typeof verdict !== "object") {
133
133
  return merged;
134
134
  }
135
- const thresholds = judgment.thresholds;
135
+ const thresholds = verdict.thresholds;
136
136
  if (thresholds && typeof thresholds === "object") {
137
137
  for (const field of CLOSE_SCORE_FIELDS) {
138
138
  const dimension = SCORE_FIELD_TO_DIMENSION[field];
139
139
  merged.thresholds[dimension] = clampThreshold(thresholds[dimension], merged.thresholds[dimension]);
140
140
  }
141
141
  }
142
- merged.minTotalScore = clampMinTotalScore(judgment.minTotalScore, merged.minTotalScore);
142
+ merged.minTotalScore = clampMinTotalScore(verdict.minTotalScore, merged.minTotalScore);
143
143
  return merged;
144
144
  }
145
145
  function buildMergeMessage(title, keiyakuContent, reportContent) {
@@ -447,11 +447,11 @@ export async function handleClose(input) {
447
447
  await ensureKeiyakuFiles(cwd);
448
448
  const traceContent = await readTraceContent(cwd);
449
449
  if (petition === "INVOKE") {
450
- const judgment = await resolveJudgmentConfig(cwd);
450
+ const verdict = await resolveVerdictConfig(cwd);
451
451
  const failedCommandments = CLOSE_SCORE_FIELDS.flatMap((field) => {
452
452
  const score = input[field];
453
453
  const dimension = SCORE_FIELD_TO_DIMENSION[field];
454
- const threshold = judgment.thresholds[dimension];
454
+ const threshold = verdict.thresholds[dimension];
455
455
  if (score >= threshold)
456
456
  return [];
457
457
  return [
@@ -459,16 +459,16 @@ export async function handleClose(input) {
459
459
  ];
460
460
  });
461
461
  const totalScore = CLOSE_SCORE_FIELDS.reduce((sum, field) => sum + input[field], 0);
462
- const totalDeficit = judgment.minTotalScore - totalScore;
462
+ const totalDeficit = verdict.minTotalScore - totalScore;
463
463
  const failures = [];
464
464
  if (failedCommandments.length > 0) {
465
465
  failures.push(`Broken commandments: ${failedCommandments.join("; ")}`);
466
466
  }
467
467
  if (totalDeficit > 0) {
468
- failures.push(`Total devotion deficit: ${totalScore}/${judgment.minTotalScore} (short by ${totalDeficit})`);
468
+ failures.push(`Total devotion deficit: ${totalScore}/${verdict.minTotalScore} (short by ${totalDeficit})`);
469
469
  }
470
470
  if (failures.length > 0) {
471
- throw new FlowError("CLOSE_QUALITY_GATE_FAILED", `God's Wrath: INVOKE denied. ${failures.join(". ")}`);
471
+ throw new FlowError("CLOSE_QUALITY_GATE_FAILED", `Verdict Denied. ${failures.join(". ")}`);
472
472
  }
473
473
  const expectedOath = resolveOath();
474
474
  if (!oathMatches(input.oath, expectedOath)) {
@@ -58,7 +58,7 @@ ${reason}
58
58
  No git commands. Do not edit KEIYAKU_TRACE.md.`;
59
59
  }
60
60
  export function buildAskPrompt(request, context) {
61
- return `Ask mode. No code changes. No git commands.
61
+ return `Ask mode. Stateless. No git commands.
62
62
 
63
63
  Request:
64
64
  ${request}
@@ -66,5 +66,5 @@ ${request}
66
66
  Context:
67
67
  ${context}
68
68
 
69
- Provide a clear reasoning summary in plain text.`;
69
+ Provide a clear reasoning summary or code generation in the response.`;
70
70
  }
@@ -31,18 +31,50 @@ function buildSection(title, content) {
31
31
  return "";
32
32
  return `[${title}]\n${validLines.join("\n")}`;
33
33
  }
34
+ function formatHeader(title) {
35
+ return `\n── ${title.toUpperCase()} ${"─".repeat(Math.max(0, 50 - title.length))}`;
36
+ }
34
37
  function assembleResponse(status, summary, sections, nextHints, infoLines = []) {
35
- const infoSection = buildSection("Info", infoLines);
36
- const parts = [
37
- `[Status] ${status}`,
38
- `[Brief]\n${summary}`,
39
- ...sections,
40
- infoSection,
41
- "", // Spacer
42
- `[Next]`,
43
- ...nextHints.map(h => `- ${h}`)
44
- ];
45
- return parts.filter(p => p !== "").join("\n");
38
+ // Main Status Block
39
+ const statusBlock = `
40
+ ${status}
41
+ ${"─".repeat(Math.max(status.length, 60))}
42
+ ${summary}
43
+ `.trim();
44
+ // Process sections
45
+ const processedSections = sections.map(section => {
46
+ // Extract title from [Title] format or ## Title format
47
+ const bracketMatch = section.match(/^\[(.*?)\]\n([\s\S]*)/);
48
+ if (bracketMatch) {
49
+ return `${formatHeader(bracketMatch[1])}\n${bracketMatch[2]}`;
50
+ }
51
+ const hashMatch = section.match(/^## (.*?)\n([\s\S]*)/);
52
+ if (hashMatch) {
53
+ return `${formatHeader(hashMatch[1])}\n${hashMatch[2]}`;
54
+ }
55
+ return section;
56
+ });
57
+ // Info Block
58
+ const infoBlock = infoLines.length > 0
59
+ ? `${formatHeader("Info")}\n${infoLines.join("\n")}`
60
+ : "";
61
+ // Next Steps Block
62
+ const nextBlock = nextHints.length > 0
63
+ ? `${formatHeader("Next Steps")}\n${nextHints.map(h => `› ${h}`).join("\n")}`
64
+ : "";
65
+ return [
66
+ statusBlock,
67
+ ...processedSections,
68
+ infoBlock,
69
+ nextBlock
70
+ ].filter(s => s.trim() !== "").join("\n");
71
+ }
72
+ function buildSuccessStructuredContent(tool, data) {
73
+ return {
74
+ tool,
75
+ status: "success",
76
+ data,
77
+ };
46
78
  }
47
79
  function getCloseToolName() {
48
80
  return resolveTermPreset().tools.close.name;
@@ -75,6 +107,7 @@ function renderHints(templates) {
75
107
  }
76
108
  // --- Builders ---
77
109
  export function buildKeiyakuSuccessResponse(result, input) {
110
+ const { status: _status, ...resultData } = result;
78
111
  const inputEcho = [
79
112
  `Title: ${truncateForDisplay(input.title, 200)}`,
80
113
  `Goal: ${truncateForDisplay(input.goal, 500)}`,
@@ -96,16 +129,16 @@ export function buildKeiyakuSuccessResponse(result, input) {
96
129
  const text = assembleResponse(`Started (Round ${result.round})`, `Created branch '${result.branch}' (base: '${result.baseBranch}'). ${result.summary}`, [inputSection, diffSection], nextHints, infoLines);
97
130
  return {
98
131
  content: [{ type: "text", text }],
99
- structuredContent: {
100
- tool: getStartToolName(),
132
+ structuredContent: buildSuccessStructuredContent(getStartToolName(), {
101
133
  nextAction: "review_round_output",
102
134
  nextHint: nextHints.join("\n"),
103
135
  inputEcho,
104
- ...result,
105
- },
136
+ ...resultData,
137
+ }),
106
138
  };
107
139
  }
108
140
  export function buildDriveResponse(result, input) {
141
+ const { status: _status, ...resultData } = result;
109
142
  const inputEcho = [
110
143
  `Directive: ${truncateForDisplay(input.directive, 600)}`,
111
144
  ...formatMaybe("Context", input.context, 600),
@@ -123,13 +156,12 @@ export function buildDriveResponse(result, input) {
123
156
  const text = assembleResponse(`Driven (Round ${result.round})`, `Updated branch '${result.branch}'. ${result.summary}`, [inputSection, diffSection], nextHints, infoLines);
124
157
  return {
125
158
  content: [{ type: "text", text }],
126
- structuredContent: {
127
- tool: getDriveToolName(),
159
+ structuredContent: buildSuccessStructuredContent(getDriveToolName(), {
128
160
  nextAction: "review_round_output",
129
161
  nextHint: nextHints.join("\n"),
130
162
  inputEcho,
131
- ...result,
132
- },
163
+ ...resultData,
164
+ }),
133
165
  };
134
166
  }
135
167
  export function buildAskResponse(result, input) {
@@ -145,17 +177,16 @@ export function buildAskResponse(result, input) {
145
177
  const text = assembleResponse("Answered", result.summary, [inputSection], nextHints, infoLines);
146
178
  return {
147
179
  content: [{ type: "text", text }],
148
- structuredContent: {
149
- tool: getAskToolName(),
150
- status: "success",
180
+ structuredContent: buildSuccessStructuredContent(getAskToolName(), {
151
181
  nextAction: "continue_normal_development",
152
182
  nextHint: nextHints.join("\n"),
153
183
  inputEcho,
154
184
  ...result,
155
- },
185
+ }),
156
186
  };
157
187
  }
158
188
  export function buildCloseDoneResponse(result, input) {
189
+ const { status: _status, ...resultData } = result;
159
190
  const closeToolName = getCloseToolName();
160
191
  const inputEcho = [
161
192
  `Petition: INVOKE`,
@@ -172,19 +203,19 @@ export function buildCloseDoneResponse(result, input) {
172
203
  ...formatMaybe("Current Branch", result.branch, 200),
173
204
  ...formatMaybe("Base Branch", result.baseBranch, 200),
174
205
  ];
175
- const text = assembleResponse("Divine Judgment Granted (INVOKE)", `Merged '${result.branch}' into '${result.baseBranch}'. Deleted feature branch.`, [inputSection, diffSection], nextHints, infoLines);
206
+ const text = assembleResponse("Keiyaku Fulfilled (INVOKE)", `Merged '${result.branch}' into '${result.baseBranch}'. Deleted feature branch.`, [inputSection, diffSection], nextHints, infoLines);
176
207
  return {
177
208
  content: [{ type: "text", text }],
178
- structuredContent: {
179
- tool: closeToolName,
209
+ structuredContent: buildSuccessStructuredContent(closeToolName, {
180
210
  nextAction: "continue_normal_development",
181
211
  nextHint: nextHints.join("\n"),
182
212
  inputEcho,
183
- ...result,
184
- },
213
+ ...resultData,
214
+ }),
185
215
  };
186
216
  }
187
217
  export function buildCloseDropResponse(result, input) {
218
+ const { status: _status, ...resultData } = result;
188
219
  const closeToolName = getCloseToolName();
189
220
  const inputEcho = [
190
221
  `Petition: ABANDON`,
@@ -199,33 +230,43 @@ export function buildCloseDropResponse(result, input) {
199
230
  ...formatMaybe("Current Branch", result.branch, 200),
200
231
  ...formatMaybe("Base Branch", result.baseBranch, 200),
201
232
  ];
202
- const text = assembleResponse("Divine Judgment Declined (ABANDON)", `Deleted '${result.branch}'. Switched back to '${result.baseBranch}'.`, [inputSection], nextHints, infoLines);
233
+ const text = assembleResponse("Keiyaku Abandoned (ABANDON)", `Deleted '${result.branch}'. Switched back to '${result.baseBranch}'.`, [inputSection], nextHints, infoLines);
203
234
  return {
204
235
  content: [{ type: "text", text }],
205
- structuredContent: {
206
- tool: closeToolName,
236
+ structuredContent: buildSuccessStructuredContent(closeToolName, {
207
237
  nextAction: "start_new_task_if_needed",
208
238
  nextHint: nextHints.join("\n"),
209
239
  inputEcho,
210
- ...result,
211
- },
240
+ ...resultData,
241
+ }),
212
242
  };
213
243
  }
214
244
  export function buildToolErrorResponse(input) {
215
245
  const inputEcho = (input.inputEcho ?? []).map((line) => truncateForDisplay(line, 800));
216
246
  const shouldRaiseProtocolError = input.errorType === "runtime_error";
217
247
  const text = assembleResponse("Failed", input.message, [buildSection("Input Context", inputEcho)], [input.hint]);
218
- return {
248
+ const response = {
219
249
  content: [{ type: "text", text }],
220
250
  structuredContent: {
221
251
  tool: input.tool,
222
- status: "error",
223
- errorType: input.errorType,
224
- errorCode: input.errorCode,
225
- error: input.message,
226
- hint: input.hint,
227
- inputEcho,
252
+ status: "failure",
253
+ error: {
254
+ code: input.errorCode,
255
+ message: input.message,
256
+ suggestion: input.hint,
257
+ },
228
258
  },
229
- ...(shouldRaiseProtocolError ? { isError: true } : {}),
259
+ };
260
+ if (shouldRaiseProtocolError) {
261
+ response.isError = true;
262
+ }
263
+ return response;
264
+ }
265
+ export function buildHelpResponse(input) {
266
+ return {
267
+ content: [{ type: "text", text: input.text }],
268
+ structuredContent: buildSuccessStructuredContent(input.tool, {
269
+ question: input.question,
270
+ }),
230
271
  };
231
272
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/keiyaku",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "MCP server for running iterative keiyaku workflows with Codex subagents.",
5
5
  "license": "MIT",
6
6
  "type": "module",