@astrosheep/keiyaku 0.1.5 → 0.1.6

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
@@ -56,7 +56,7 @@ Set `KEIYAKU_TERM_PRESET` in the MCP server env (recommended), or in your shell
56
56
 
57
57
  Each run/round can pick a profile via tool input `name`, or globally via `KEIYAKU_SUBAGENT_NAME_OVERRIDE`.
58
58
 
59
- - `default`: `servant-tier-b`, `servant-tier-a`, `servant-tier-s` (also accepts internal names `agent-a|agent-b|agent-c`)
59
+ - `default`: `B-tier`, `A-tier`, `S-tier` (also accepts internal names `agent-a|agent-b|agent-c`)
60
60
  - `pokemon`: `caterpie`, `pikachu`, `mewtwo`
61
61
  - `mischief`: `imp`, `minion`, `mastermind`
62
62
 
@@ -1,11 +1,10 @@
1
1
  const INTERNAL_PROFILE_NAMES = ['agent-a', 'agent-b', 'agent-c'];
2
2
  export const DEFAULT_AVAILABLE_NAMES = [
3
- 'servant-tier-b',
4
- 'servant-tier-a',
5
- 'servant-tier-s',
3
+ 'B-tier',
4
+ 'A-tier',
5
+ 'S-tier',
6
6
  ];
7
- const LEGACY_AVAILABLE_NAMES = ['servant-tier-B', 'servant-tier-A', 'servant-tier-S'];
8
- export const DEFAULT_SUBAGENT_NAME = 'servant-tier-a';
7
+ export const DEFAULT_SUBAGENT_NAME = 'A-tier';
9
8
  const DEFAULT_JUDGMENT_CONFIG = {
10
9
  thresholds: {
11
10
  precise: 5,
@@ -20,31 +19,31 @@ export const DEFAULT_PRESET = {
20
19
  id: 'default',
21
20
  identity: 'Servant',
22
21
  divineJudgment: DEFAULT_JUDGMENT_CONFIG,
23
- usageGuide: '## Servant 使用指南\n\n**servant-tier-b** — 免费奴隶 🆓\n- 免!费!的!听懂了吗?!?随便用,别心疼。\n- 测试、脚本、重构这种boring的东西?丢给它!\n- 本大爷的手是用来做更高贵的事情的!(指不碰那些 trivial 的烂代码)。\n\n**servant-tier-a** — 升级版扳手 🔧\n- tier-b 笨到让你想掀桌的时候用这个\n- 比 tier-b 靠谱,但别指望它有灵魂\n\n**servant-tier-s** — 禁术 ⚠️💀\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)',
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)',
24
23
  nextHints: {
25
24
  start: [
26
- 'Review Round Details: The [Diff] section above shows incremental patches from this round.',
27
- "Deep Dive: Use 'git diff HEAD~1 -- <path>' to see the full changes for a specific file.",
28
- 'Verify: Run tests and ensure logic aligns with the Goal and Constraints.',
29
- "Refine with '${drive}', or call '${close}' (INVOKE/ABANDON) based on results.",
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.",
30
29
  ],
31
30
  drive: [
32
- 'Review Round Details: The [Diff] section above shows incremental patches from this round.',
33
- "Deep Dive: Use 'git diff HEAD~1 -- <path>' to see the full changes for a specific file.",
34
- 'Verify & Test: Ensure this round fixed the issues without regression.',
35
- "Decide: '${drive}' again to iterate, or '${close}' (INVOKE to merge / ABANDON to abort).",
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.",
36
35
  ],
37
36
  ask: [
38
- "Apply this insight: use '${drive}' to incorporate these findings into your implementation.",
39
- 'Need more? Continue asking, or check the logs if something remains unclear.',
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.',
40
39
  ],
41
40
  closeInvoke: [
42
- 'Task finalized and merged. Current branch is now base.',
43
- "Start a new keiyaku with '${start}' whenever you're ready for the next mission.",
41
+ 'Contract Merged. Branch deleted. You are back on base.',
42
+ "Ready for next assignment? Use \u0027${start}\u0027 to define new scope.",
44
43
  ],
45
44
  closeDrop: [
46
- 'Task abandoned. Switched back to base branch safely.',
47
- "Clean slate. Use '${start}' to try a different approach.",
45
+ 'Contract Abandoned. Branch deleted. Reverted to base.',
46
+ "Clean slate. Use \u0027${start}\u0027 to redefine the approach.",
48
47
  ],
49
48
  },
50
49
  availableNames: DEFAULT_AVAILABLE_NAMES,
@@ -52,7 +51,7 @@ export const DEFAULT_PRESET = {
52
51
  start: {
53
52
  name: 'summon',
54
53
  title: 'Sign Keiyaku',
55
- description: 'Sign the Keiyaku and assign the mission to a servant. This creates a dedicated branch where the servant will labor until the goal is met.\nCall ONCE per task to start the cycle. Workspace must be clean.\n\nFlow: summon → [drive x N] → request_verdict',
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',
56
55
  args: {
57
56
  title: 'REQUIRED. A concise codename for this hunt.',
58
57
  goal: 'REQUIRED. The Kill Condition. State exactly what success looks like for the servant to achieve.',
@@ -60,36 +59,36 @@ export const DEFAULT_PRESET = {
60
59
  context: 'REQUIRED. Mission Intel. The complete knowledge base for the servant: current vs. expected behavior, relevant file paths, error logs, and any critical background info.',
61
60
  constraints: 'REQUIRED. Non-negotiable Rules. Architectural and stylistic boundaries the servant must obey.',
62
61
  criteria: 'REQUIRED. Acceptance Criteria. Verifiable checks to prove the servant has finished the job.',
63
- name: 'Optional ${IDENTITY} profile to execute this mission. Presets: ${PRESET_IDENTITIES}.',
62
+ name: 'Optional ${IDENTITY} profile to execute this mission. Available: ${AVAILABLE_NAMES}.',
64
63
  cwd: "Optional repository path. Defaults to the server's current working directory.",
65
64
  },
66
65
  },
67
66
  drive: {
68
67
  name: 'drive',
69
68
  title: 'Iterate',
70
- description: "Direct the active servant's next step. Provide feedback or new orders for the servant to implement in the current branch.\nMANDATORY: Review the servant's work (git diff) before iterating.\n\nFlow: summon → [drive x N] → request_verdict",
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",
71
70
  args: {
72
71
  directive: 'REQUIRED. The Next Order. Precise instructions for the servant. Can be a correction ("fix the leak") or a continuation ("now add the tests").',
73
72
  context: 'Optional. New Intel. New error logs or details discovered after the servant\'s last strike.',
74
- name: 'Optional ${IDENTITY} profile to process this turn. Presets: ${PRESET_IDENTITIES}.',
73
+ name: 'Optional ${IDENTITY} profile to process this turn. Available: ${AVAILABLE_NAMES}.',
75
74
  cwd: "Optional repository path. Defaults to the server's current working directory.",
76
75
  },
77
76
  },
78
77
  ask: {
79
78
  name: 'ask',
80
79
  title: 'Ask',
81
- description: 'Delegate a quick task or analysis to a servant. Use for scouting, strategic consultation, or one-off tasks (like scripts or docs) where you want the servant to handle it without a full keiyaku.',
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.',
82
81
  args: {
83
82
  request: 'REQUIRED. The task, question, or mission to delegate to the servant.',
84
83
  context: 'REQUIRED. Relevant background or data the servant needs to execute the request.',
85
- name: 'Optional ${IDENTITY} profile to perform this task. Presets: ${PRESET_IDENTITIES}.',
84
+ name: 'Optional ${IDENTITY} profile to perform this task. Available: ${AVAILABLE_NAMES}.',
86
85
  cwd: "Optional repository path. Defaults to the server's current working directory.",
87
86
  },
88
87
  },
89
88
  close: {
90
89
  name: 'request_verdict',
91
90
  title: 'Request Verdict',
92
- description: 'A humble petition before Divine Judgment. Present your evidence and numeric confessions, then await mercy or wrath.\nREQUIRES AN ACTIVE KEIYAKU (started via ${START_TOOL_NAME}).\nIf your spirit falters, continue repentance through ${DRIVE_TOOL_NAME}.\n\nFlow: ${START_TOOL_NAME} → [${DRIVE_TOOL_NAME} x N] → ${CLOSE_TOOL_NAME}',
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
92
  args: {
94
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}.',
95
94
  criteriaChecks: 'REQUIRED. Evidence for INVOKE, or explicit reasons for ABANDON.',
@@ -104,106 +103,106 @@ export const DEFAULT_PRESET = {
104
103
  },
105
104
  help: {
106
105
  name: 'help',
107
- title: 'Keiyaku Help',
108
- description: 'Global laws in .keiyaku/ (base-criteria.md & base-constraints.md) must be Markdown level 2 headers. Workflow: summon -> [drive x N] -> request_verdict.',
106
+ title: 'Protocol Codex',
107
+ description: 'Consult the Architect\'s Codex. Clarify the Laws of the Keiyaku and the standard Workflow.\nUse this to understand the constraints of the reality you command.',
109
108
  args: {
110
- question: 'REQUIRED. The specific keiyaku rule, workflow step, or confusion to clarify.',
109
+ question: 'REQUIRED. The specific Protocol, Law, or Workflow detail to clarify.',
111
110
  },
112
111
  },
113
112
  },
114
113
  };
115
- export const POKEMON_PRESET = {
116
- id: 'pokemon',
117
- identity: 'Pokemon',
114
+ export const POCKET_PRESET = {
115
+ id: 'pocket',
116
+ identity: 'Critter',
118
117
  divineJudgment: DEFAULT_JUDGMENT_CONFIG,
119
- usageGuide: "## Pokemon Battle Guide\n\n**caterpie** — 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**pikachu** — Reliable Partner ⚡\n- Good for most battles. Thunderbolt gets the job done.\n- Has some personality, but still follows orders.\n\n**mewtwo** — 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`pokedex` (optional, read-only analysis)",
118
+ 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)",
120
119
  nextHints: {
121
120
  start: [
122
- 'Check the Battle Log: The [Diff] section shows what changed in this turn.',
123
- "Full Move Set: Use 'git diff HEAD~1 -- <path>' to inspect a specific file's changes.",
124
- "Battle Readiness: Verify the strategy and ensure it doesn't break the Battle Rules.",
125
- "Next Move: Issue another '${drive}', or attempt a '${close}' (INVOKE to capture / ABANDON to flee).",
121
+ 'Battle Started: The [Diff] section shows the opening moves.',
122
+ "Analyze: Use 'git diff HEAD~1 -- <path>' to check the effectiveness of the deployment.",
123
+ 'Strategize: Ensure the setup matches the Victory Conditions (Goal).',
124
+ "Next Turn: Use '${drive}' to execute the next tactic, or '${close}' if the Badge is within reach.",
126
125
  ],
127
126
  drive: [
128
- 'Turn Analysis: Review the incremental [Diff] above.',
129
- "Tactical Review: Use 'git diff HEAD~1 -- <path>' for a detailed file inspection.",
130
- 'Strategy Alignment: Ensure the latest move fixed the issue without taking recoil damage.',
131
- "Decision: '${drive}' for another turn, or '${close}' the encounter.",
127
+ 'Turn Complete: The [Diff] section shows the result of the last command.',
128
+ "Damage Check: Use 'git diff HEAD~1 -- <path>' to verify the move executed correctly.",
129
+ 'Validate: Ensure no recoil damage (regressions) occurred.',
130
+ "Command: '${drive}' to continue the combo, or '${close}' to attempt Capture.",
132
131
  ],
133
132
  ask: [
134
- "Pokedex Data: Use '${drive}' to integrate this research into your battle strategy.",
135
- "More Intel? Keep using '${ask}', or check the arena logs.",
133
+ "Data Recorded: Use '${start}' to begin the encounter, or '${drive}' to use this intel.",
134
+ "Still Searching? Keep using '${ask}' to fill the Dex.",
136
135
  ],
137
136
  closeInvoke: [
138
- 'Pokemon captured! The battlefield is clear.',
139
- "Ready for another encounter? Use '${start}' to find a new target.",
137
+ 'Critter Captured! The entry is logged in the PC (base branch).',
138
+ "Heal up. Use '${start}' to challenge the next opponent.",
140
139
  ],
141
140
  closeDrop: [
142
- 'You fled the battle safely. Back in the tall grass.',
143
- "Need a different approach? Use '${start}' to re-engage.",
141
+ 'Ran Away Safely. The encounter is reset.',
142
+ "Back to the map. Use '${start}' to find a wild Critter.",
144
143
  ],
145
144
  },
146
- availableNames: ['caterpie', 'pikachu', 'mewtwo'],
145
+ availableNames: ['grub', 'sparky', 'titan'],
147
146
  tools: {
148
147
  start: {
149
148
  name: 'choose_you',
150
149
  title: 'I Choose You!',
151
- description: 'Start a battle (task). Throws a Pokeball (branch), begins Turn 1.\nCall ONCE per battle. Battlefield must be clear no uncommitted changes.\nBefore calling, identify the target Pokemon (goal). If unclear, consult Pokedex first.\n\nFlow: choose_you → [command x N] → capture',
150
+ description: 'Initialize the Battle Phase. Send a Critter (Servant) into a dedicated Arena (branch).\nYou are the Trainer; they are the Fighter. Define the Victory Condition (Goal) clearly.\nThe battle continues in this Arena until the Badge is won.\nCall ONCE to start the encounter.\n\nFlow: choose_you → [command x N] → capture',
152
151
  args: {
153
152
  title: 'REQUIRED. Battle card title for this encounter.',
154
153
  goal: 'REQUIRED. Victory condition. Define exactly what winning this battle means.',
155
- directive: 'Optional Turn 1 strategy leash. Use to focus on one phase first.',
154
+ directive: 'Optional Turn 1 strategy. Use to focus on the opening move.',
156
155
  context: 'REQUIRED. Battle background: key code paths, symptoms, logs, and repro clues.',
157
156
  constraints: 'REQUIRED. Battle rules that cannot be broken while fighting.',
158
157
  criteria: 'REQUIRED. Gym badges for completion: concrete checks proving victory.',
159
- name: 'Optional ${IDENTITY} to send into battle. Presets: ${PRESET_IDENTITIES}.',
158
+ name: 'Optional ${IDENTITY} to send into battle. Available: ${AVAILABLE_NAMES}.',
160
159
  cwd: 'Optional battlefield path (repository root). Defaults to current arena.',
161
160
  },
162
161
  },
163
162
  drive: {
164
163
  name: 'command',
165
164
  title: 'Issue Command',
166
- description: 'Issue orders for the next turn. Give feedback on the previous move.\nCall after reviewing the battle log. Repeatable.\nBefore capturing, always check HP status (diff).\n\nFlow: choose_you → [command x N] → capture',
165
+ description: 'Execute the next Strategic Move. Order the Critter to advance the battle state.\nWhether Attacking (coding) or Defending (testing), every command counts as a Turn.\nMANDATORY: Check the Battle Log (git diff) before the next move. Command until Victory is certain.\n\nFlow: choose_you → [command x N] → capture',
167
166
  args: {
168
- directive: 'REQUIRED. Next move command with specific tactical correction.',
167
+ directive: 'REQUIRED. The specific move or tactic to execute next.',
169
168
  context: 'Optional new battle intel discovered after the previous turn.',
170
- name: 'Optional ${IDENTITY} to execute this turn. Presets: ${PRESET_IDENTITIES}.',
169
+ name: 'Optional ${IDENTITY} to execute this turn. Available: ${AVAILABLE_NAMES}.',
171
170
  cwd: 'Optional battlefield path (repository root). Defaults to current arena.',
172
171
  },
173
172
  },
174
173
  ask: {
175
- name: 'pokedex',
176
- title: 'Pokedex',
177
- description: 'Scan the codebase (environment). Read-only analysis. No PP cost. Results are saved to research logs.',
174
+ name: 'dex',
175
+ title: 'Dex',
176
+ 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.',
178
177
  args: {
179
- request: 'REQUIRED. What should the Pokedex analyze, compare, or explain.',
178
+ request: 'REQUIRED. What should the Dex analyze, compare, or explain.',
180
179
  context: 'REQUIRED. Context entries so the analysis targets the right ecosystem.',
181
- name: 'Optional ${IDENTITY} doing the scan. Presets: ${PRESET_IDENTITIES}.',
180
+ name: 'Optional ${IDENTITY} doing the scan. Available: ${AVAILABLE_NAMES}.',
182
181
  cwd: 'Optional battlefield path (repository root). Defaults to current arena.',
183
182
  },
184
183
  },
185
184
  close: {
186
185
  name: 'capture',
187
186
  title: 'End Battle',
188
- description: 'Final petition before a power greater than trainers and legends. Offer your scored confession and await Divine Judgment.\nREQUIRES AN ACTIVE BATTLE (started via ${START_TOOL_NAME}).\nIf unworthy, keep refining through ${DRIVE_TOOL_NAME}.\n\nFlow: ${START_TOOL_NAME} → [${DRIVE_TOOL_NAME} x N] → ${CLOSE_TOOL_NAME}',
187
+ description: 'Attempt Capture. Present the weakened target for League Inspection.\nWARNING: The League (System) checks every Badge. If you attempt to `INVOKE` with fainted code, Disqualification (ABANDON) is immediate.\nOnly throw the Ball when the target is 100% ready.\n\nFlow: ${START_TOOL_NAME} → [${DRIVE_TOOL_NAME} x N] → ${CLOSE_TOOL_NAME}',
189
188
  args: {
190
- petition: 'REQUIRED. INVOKE seeks blessing; ABANDON retreats in humility.\nREQUIRES AN ACTIVE BATTLE (started via ${START_TOOL_NAME}).\nIf scores are unworthy, continue with ${DRIVE_TOOL_NAME}.',
191
- criteriaChecks: 'REQUIRED. Badge-by-badge proof for INVOKE, or confession for ABANDON.',
192
- score_precise: 'REQUIRED score (0-5). 5 means a flawless hit: exact layer, exact target, zero meaningful misplacement.',
193
- score_minimal: 'REQUIRED score (0-5). 5 means perfectly efficient: no unnecessary moves, no extra motion, no bloat.',
194
- score_isolated: 'REQUIRED score (0-5). 5 means pure battle focus: zero side-quests, zero unrelated damage.',
195
- score_idiomatic: "REQUIRED score (0-5). 5 means doctrine-perfect execution: reads exactly like the team's strongest native style.",
196
- score_cohesive: 'REQUIRED score (0-5). 5 means immaculate role clarity: each action serves one purpose with clean boundaries.',
197
- oath: "Trainer's sacred confession. Required for INVOKE. Verbatim: ${OATH_TEXT}",
189
+ petition: 'REQUIRED. INVOKE seeks Badge; ABANDON forfeits the match.\nREQUIRES AN ACTIVE BATTLE (started via ${START_TOOL_NAME}).\nIf stats are low, continue with ${DRIVE_TOOL_NAME}.',
190
+ criteriaChecks: 'REQUIRED. Badge-by-badge proof for INVOKE, or reason for Forfeit.',
191
+ score_precise: 'REQUIRED score (0-5). 5 means a Critical Hit: exact layer, exact target, zero meaningful misplacement.',
192
+ score_minimal: 'REQUIRED score (0-5). 5 means Max Efficiency: no wasted PP, no extra motion.',
193
+ score_isolated: 'REQUIRED score (0-5). 5 means 1v1 Focus: zero side-quests, zero unrelated damage.',
194
+ score_idiomatic: "REQUIRED score (0-5). 5 means STAB (Same Type Attack Bonus): perfectly matches the codebase style.",
195
+ score_cohesive: 'REQUIRED score (0-5). 5 means Team Synergy: action serves one purpose with clean boundaries.',
196
+ oath: "Trainer's Honor Code. Required for INVOKE. Verbatim: ${OATH_TEXT}",
198
197
  cwd: 'Optional battlefield path (repository root). Defaults to current arena.',
199
198
  },
200
199
  },
201
200
  help: {
202
201
  name: 'help',
203
- title: 'CLI Help',
204
- description: 'Get guidance on the rules of the Pokemon Battle System.',
202
+ title: 'Trainer Guide',
203
+ description: 'Consult the League Rules. Clarify the Battle System and Turn Structure.',
205
204
  args: {
206
- question: 'REQUIRED. The Pokemon Battle System question you want answered.',
205
+ question: 'REQUIRED. The Pocket Battle System question you want answered.',
207
206
  },
208
207
  },
209
208
  },
@@ -215,28 +214,28 @@ export const MISCHIEF_PRESET = {
215
214
  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)',
216
215
  nextHints: {
217
216
  start: [
218
- "Scrutinize the Minion's Work: Check the incremental [Diff] section.",
219
- "Inspection: Use 'git diff HEAD~1 -- <path>' to see if they missed a spot.",
220
- "Master's Approval: Does this align with your grand vision and decrees?",
221
- "Next Command: Give another '${drive}' order, or grant a '${close}' judgment (INVOKE/ABANDON).",
217
+ "Inspect the Minion's Work: The [Diff] section shows the first step of the plan.",
218
+ "Verify Compliance: Use 'git diff HEAD~1 -- <path>' to ensure they followed orders.",
219
+ "Approve: Does this align with the Grand Scheme (Goal)?",
220
+ "Next Phase: Command '${drive}' to advance the plot, or '${close}' if the world is yours.",
222
221
  ],
223
222
  drive: [
224
- 'Another Attempt: Review the latest incremental [Diff] above.',
225
- "Deep Scrutiny: Use 'git diff HEAD~1 -- <path>' to verify the minion's precision.",
226
- 'Validation: Ensure no new chaos was introduced by their clumsy hands.',
227
- "The Verdict: '${drive}' again if imperfect, otherwise '${close}' the matter.",
223
+ 'Phase Complete: The [Diff] section shows the latest execution.',
224
+ "Deep Scrutiny: Use 'git diff HEAD~1 -- <path>' to check for incompetence.",
225
+ 'Validation: Ensure no sabotage (regressions) in the plan.',
226
+ "Command: '${drive}' to push further, or '${close}' to reveal the masterpiece.",
228
227
  ],
229
228
  ask: [
230
- "Intel Gathered: Incorporate this into your next '${drive}' command.",
231
- "Still Puzzled? Use '${ask}' again, or look at the dossiers yourself.",
229
+ "Intel Stolen: Use '${start}' to launch the scheme, or '${drive}' to exploit this weakness.",
230
+ "Still Puzzled? Send the spy ('${ask}') out again.",
232
231
  ],
233
232
  closeInvoke: [
234
- 'The masterpiece is complete. Minion dismissed.',
235
- "Plotting something new? Use '${start}' to initiate the next scheme.",
233
+ 'Scheme Successful. The Lair is merged. The Minion is fed.',
234
+ "Plotting something new? Use '${start}' for the next conquest.",
236
235
  ],
237
236
  closeDrop: [
238
- 'Scheme aborted. The evidence has been incinerated.',
239
- "Starting over? Use '${start}' to find a more competent minion.",
237
+ 'Plan Aborted. Evidence destroyed. The Minion was disposed of.',
238
+ "Back to the drawing board. Use '${start}' to hatch a new plan.",
240
239
  ],
241
240
  },
242
241
  availableNames: ['imp', 'minion', 'mastermind'],
@@ -244,60 +243,60 @@ export const MISCHIEF_PRESET = {
244
243
  start: {
245
244
  name: 'oi',
246
245
  title: 'Oi!',
247
- description: "Master Architect mode initiated. It's time to direct a minion to execute your grand vision.\nCall this ONCE per task to establish the keiyaku. Ensure the workspace is clean so your genius isn't obscured by clutter.\nDefine the goal with absolute precision; you lead, they follow.\n\nFlow: oi → [neh x N] → yoshi",
246
+ 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",
248
247
  args: {
249
248
  title: 'REQUIRED. Operation codename for your grand scheme.',
250
249
  goal: 'REQUIRED. The conquest objective. Define the exact end-state your minion must deliver.',
251
- directive: 'Optional first-order command to leash Round 1 scope.',
250
+ directive: 'Optional first-order command. Point them in the right direction.',
252
251
  context: 'REQUIRED. Briefing dossier: relevant file paths, current failures, logs, and repro clues.',
253
252
  constraints: 'REQUIRED. Absolute decrees. Architectural and stylistic limits your minion must obey.',
254
253
  criteria: 'REQUIRED. Triumph conditions that can be verified without debate.',
255
- name: 'Optional ${IDENTITY} to command this operation. Presets: ${PRESET_IDENTITIES}.',
254
+ name: 'Optional ${IDENTITY} to command this operation. Available: ${AVAILABLE_NAMES}.',
256
255
  cwd: 'Optional lair path (repository root). Defaults to current command chamber.',
257
256
  },
258
257
  },
259
258
  drive: {
260
259
  name: 'neh',
261
260
  title: 'Neh...',
262
- description: 'The previous attempt was imperfect. As the judge, provide the corrective nudge needed for the minion to reach your standards.\nAnalyze the diff with your superior intuition before issuing the next directive. Repeat until perfection is achieved.\n\nFlow: oi → [neh x N] → yoshi',
261
+ 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',
263
262
  args: {
264
- directive: 'REQUIRED. Precise correction order for the next attempt.',
265
- context: 'Optional newly uncovered evidence from your latest inspection.',
266
- name: 'Optional ${IDENTITY} to execute this correction. Presets: ${PRESET_IDENTITIES}.',
263
+ directive: 'REQUIRED. Precise order for the next phase of the scheme.',
264
+ context: 'Optional newly uncovered secrets or updated briefing.',
265
+ name: 'Optional ${IDENTITY} to execute this phase. Available: ${AVAILABLE_NAMES}.',
267
266
  cwd: 'Optional lair path (repository root). Defaults to current command chamber.',
268
267
  },
269
268
  },
270
269
  ask: {
271
270
  name: 'eeto',
272
271
  title: 'Eeto...',
273
- description: "Lazy strategy mode. Use this to scout the area before committing to a full scheme, or just because you can't be bothered to start a real task.\nPerfect for gathering intel to paste into a future 'oi' context.",
272
+ 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.',
274
273
  args: {
275
274
  request: 'REQUIRED. The intel to gather or the strategy to formulate.',
276
275
  context: 'REQUIRED. World-state details needed for a sharp analysis.',
277
- name: 'Optional ${IDENTITY} to contemplate this puzzle. Presets: ${PRESET_IDENTITIES}.',
276
+ name: 'Optional ${IDENTITY} to contemplate this puzzle. Available: ${AVAILABLE_NAMES}.',
278
277
  cwd: 'Optional lair path (repository root). Defaults to current command chamber.',
279
278
  },
280
279
  },
281
280
  close: {
282
281
  name: 'yoshi',
283
282
  title: 'Yoshi!',
284
- description: 'Kneel before the final tribunal. Submit your scored confession and await Divine Judgment: mercy through INVOKE, ruin through ABANDON.\nREQUIRES AN ACTIVE SCHEME (started via ${START_TOOL_NAME}).\nIf doubtful, continue penance through ${DRIVE_TOOL_NAME}.\n\nFlow: ${START_TOOL_NAME} → [${DRIVE_TOOL_NAME} x N] → ${CLOSE_TOOL_NAME}',
283
+ description: 'The Final Reveal. Present your Masterpiece to the Dark Council (System).\nWARNING: The Council destroys failure. If you `INVOKE` with weak plans, the Self-Destruct (ABANDON) 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}',
285
284
  args: {
286
- petition: 'REQUIRED. INVOKE pleads for ascension; ABANDON confesses failure.\nREQUIRES AN ACTIVE SCHEME (started via ${START_TOOL_NAME}).\nIf scores are low, continue via ${DRIVE_TOOL_NAME}.',
287
- criteriaChecks: 'REQUIRED. Proof of mastery for INVOKE, or explicit confession for ABANDON.',
288
- score_precise: 'REQUIRED score (0-5). 5 means ordained precision without flaw: exact layer, exact cut, zero meaningful drift.',
289
- score_minimal: 'REQUIRED score (0-5). 5 means ascetic minimality: no needless machinery, no decorative motion, no excess.',
290
- score_isolated: 'REQUIRED score (0-5). 5 means perfect containment: unrelated chaos remains fully outside this diff.',
291
- score_idiomatic: 'REQUIRED score (0-5). 5 means native-tongue perfection: style and structure match house doctrine without strain.',
292
- score_cohesive: 'REQUIRED score (0-5). 5 means sacred cohesion: each unit bears exactly one duty with uncompromised boundaries.',
293
- oath: "Architect's sacred confession. Required for INVOKE. Verbatim: ${OATH_TEXT}",
285
+ petition: 'REQUIRED. INVOKE demands rule; ABANDON admits defeat.\nREQUIRES AN ACTIVE SCHEME (started via ${START_TOOL_NAME}).\nIf the plan is weak, improve it with ${DRIVE_TOOL_NAME}.',
286
+ criteriaChecks: 'REQUIRED. Proof of conquest for INVOKE, or reason for self-destruct.',
287
+ score_precise: 'REQUIRED score (0-5). 5 means Laser Focus: exact cut, zero meaningful drift.',
288
+ score_minimal: 'REQUIRED score (0-5). 5 means Ruthless Efficiency: no wasted movement, no excess.',
289
+ score_isolated: 'REQUIRED score (0-5). 5 means Perfect Containment: no collateral damage.',
290
+ score_idiomatic: 'REQUIRED score (0-5). 5 means Native Tongue: matches the lair\'s dialect perfectly.',
291
+ score_cohesive: 'REQUIRED score (0-5). 5 means Absolute Loyalty: each unit serves the master plan.',
292
+ oath: "Mastermind's Vow. Required for INVOKE. Verbatim: ${OATH_TEXT}",
294
293
  cwd: 'Optional lair path (repository root). Defaults to current command chamber.',
295
294
  },
296
295
  },
297
296
  help: {
298
297
  name: 'help',
299
298
  title: 'Nani?!',
300
- description: 'Recalling the fundamental laws of this realm. Stay sharp, Architect.',
299
+ description: 'Consult the Evil Overlord List. Review the Laws of the Lair.',
301
300
  args: {
302
301
  question: 'REQUIRED. The law or protocol detail you want the realm to explain.',
303
302
  },
@@ -309,16 +308,16 @@ export function resolveTermPreset() {
309
308
  if (!raw || raw === 'default') {
310
309
  return DEFAULT_PRESET;
311
310
  }
312
- if (raw === 'pokemon') {
313
- return POKEMON_PRESET;
311
+ if (raw === 'pocket') {
312
+ return POCKET_PRESET;
314
313
  }
315
314
  if (raw === 'mischief') {
316
315
  return MISCHIEF_PRESET;
317
316
  }
318
- throw new Error(`Unsupported KEIYAKU_TERM_PRESET '${raw}'. Expected 'default', 'pokemon', or 'mischief'.`);
317
+ throw new Error(`Unsupported KEIYAKU_TERM_PRESET '${raw}'. Expected 'default', 'pocket', or 'mischief'.`);
319
318
  }
320
319
  export function listTermPresets() {
321
- return [DEFAULT_PRESET, POKEMON_PRESET, MISCHIEF_PRESET];
320
+ return [DEFAULT_PRESET, POCKET_PRESET, MISCHIEF_PRESET];
322
321
  }
323
322
  function extractPresetAvailableNames(preset) {
324
323
  return (preset.availableNames?.map((value) => value.trim()).filter((value) => value.length > 0) ?? []);
@@ -346,10 +345,6 @@ export function resolveSubagentProfileName(name, preset = resolveTermPreset()) {
346
345
  if (defaultIndex >= 0) {
347
346
  return INTERNAL_PROFILE_NAMES[defaultIndex];
348
347
  }
349
- const legacyIndex = LEGACY_AVAILABLE_NAMES.indexOf(trimmed);
350
- if (legacyIndex >= 0) {
351
- return INTERNAL_PROFILE_NAMES[legacyIndex];
352
- }
353
348
  const available = extractPresetAvailableNames(preset);
354
349
  const index = available.indexOf(trimmed);
355
350
  if (index < 0 || index >= INTERNAL_PROFILE_NAMES.length)
package/build/index.js CHANGED
@@ -3,91 +3,88 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
5
5
  import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
6
+ import { readFileSync } from "node:fs";
6
7
  import { resolveOath } from "./workflow/orchestrator.js";
7
8
  import { askToolSchema, startToolSchema, driveToolSchema, closeToolSchema, helpToolSchema, } from "./types/tool-schemas.js";
8
- import { listTermPresets, resolveTermPreset } from "./config/term-presets.js";
9
+ import { listTermPresets, resolveTermPreset, getAvailableNamesForPreset } from "./config/term-presets.js";
10
+ import { renderPreset } from "./utils/text-utils.js";
11
+ import { applyArgumentDescriptions } from "./utils/schema-utils.js";
9
12
  import { createAskHandler, createCloseHandler, createStartHandler, createDriveHandler, createHelpHandler, } from "./handlers/index.js";
10
- function applyArgumentDescriptions(schema, descriptions) {
11
- const describedShape = {};
12
- const shape = schema.shape;
13
- for (const key of Object.keys(shape)) {
14
- describedShape[key] = shape[key].describe(descriptions[key]);
13
+ function resolvePackageVersion() {
14
+ const packageJsonRaw = readFileSync(new URL("../package.json", import.meta.url), "utf-8");
15
+ const packageJson = JSON.parse(packageJsonRaw);
16
+ if (typeof packageJson.version !== "string" || packageJson.version.trim() === "") {
17
+ throw new Error("Invalid package.json version.");
15
18
  }
16
- return schema.extend(describedShape);
19
+ return packageJson.version;
17
20
  }
21
+ const packageVersion = resolvePackageVersion();
18
22
  function registerTools(server) {
19
23
  const preset = resolveTermPreset();
20
- const startPreset = preset.tools.start;
21
- const drivePreset = preset.tools.drive;
22
- const askPreset = preset.tools.ask;
23
- const closePreset = preset.tools.close;
24
- const helpPreset = preset.tools.help;
25
24
  const presetIdentities = listTermPresets()
26
25
  .map((item) => `${item.id}=${item.identity}`)
27
26
  .join(", ");
27
+ const availableNames = getAvailableNamesForPreset(preset).join(", ");
28
28
  const currentOath = resolveOath();
29
- const replaceToolPlaceholders = (value) => value
30
- .replace(/\$\{START_TOOL_NAME\}/g, startPreset.name)
31
- .replace(/\$\{DRIVE_TOOL_NAME\}/g, drivePreset.name)
32
- .replace(/\$\{CLOSE_TOOL_NAME\}/g, closePreset.name);
29
+ const renderedPreset = renderPreset(preset, {
30
+ IDENTITY: preset.identity,
31
+ PRESET_IDENTITIES: presetIdentities,
32
+ AVAILABLE_NAMES: availableNames,
33
+ START_TOOL_NAME: preset.tools.start.name,
34
+ DRIVE_TOOL_NAME: preset.tools.drive.name,
35
+ CLOSE_TOOL_NAME: preset.tools.close.name,
36
+ OATH_TEXT: `'${currentOath}'`,
37
+ });
38
+ const startPreset = renderedPreset.tools.start;
39
+ const drivePreset = renderedPreset.tools.drive;
40
+ const askPreset = renderedPreset.tools.ask;
41
+ const closePreset = renderedPreset.tools.close;
42
+ const helpPreset = renderedPreset.tools.help;
33
43
  const dynamicCloseSchema = applyArgumentDescriptions(closeToolSchema, {
34
44
  ...closePreset.args,
35
- petition: replaceToolPlaceholders(closePreset.args.petition),
36
- oath: closePreset.args.oath.replace("${OATH_TEXT}", `'${currentOath}'`),
37
45
  });
38
- const dynamicCloseDescription = replaceToolPlaceholders(closePreset.description);
39
- const dynamicStartDescription = replaceToolPlaceholders(startPreset.description);
40
- const dynamicDriveDescription = replaceToolPlaceholders(drivePreset.description);
41
- const dynamicAskDescription = replaceToolPlaceholders(askPreset.description);
42
- const dynamicHelpDescription = replaceToolPlaceholders(helpPreset.description);
43
- const dynamicHelpUsageGuide = replaceToolPlaceholders(preset.usageGuide);
44
46
  const dynamicStartSchema = applyArgumentDescriptions(startToolSchema, {
45
47
  ...startPreset.args,
46
- name: startPreset.args.name
47
- .replace("${IDENTITY}", preset.identity)
48
- .replace("${PRESET_IDENTITIES}", presetIdentities),
49
48
  });
50
49
  const dynamicDriveSchema = applyArgumentDescriptions(driveToolSchema, {
51
50
  ...drivePreset.args,
52
- name: drivePreset.args.name.replace("${IDENTITY}", preset.identity).replace("${PRESET_IDENTITIES}", presetIdentities),
53
51
  });
54
52
  const dynamicAskSchema = applyArgumentDescriptions(askToolSchema, {
55
53
  ...askPreset.args,
56
- name: askPreset.args.name.replace("${IDENTITY}", preset.identity).replace("${PRESET_IDENTITIES}", presetIdentities),
57
54
  });
58
55
  const dynamicHelpSchema = applyArgumentDescriptions(helpToolSchema, {
59
56
  ...helpPreset.args,
60
57
  });
61
58
  server.registerTool(startPreset.name, {
62
59
  title: startPreset.title,
63
- description: dynamicStartDescription,
60
+ description: startPreset.description,
64
61
  inputSchema: dynamicStartSchema,
65
62
  }, createStartHandler());
66
63
  server.registerTool(drivePreset.name, {
67
64
  title: drivePreset.title,
68
- description: dynamicDriveDescription,
65
+ description: drivePreset.description,
69
66
  inputSchema: dynamicDriveSchema,
70
67
  }, createDriveHandler());
71
68
  server.registerTool(askPreset.name, {
72
69
  title: askPreset.title,
73
- description: dynamicAskDescription,
70
+ description: askPreset.description,
74
71
  inputSchema: dynamicAskSchema,
75
72
  }, createAskHandler());
76
73
  server.registerTool(closePreset.name, {
77
74
  title: closePreset.title,
78
- description: dynamicCloseDescription,
75
+ description: closePreset.description,
79
76
  inputSchema: dynamicCloseSchema,
80
77
  }, createCloseHandler());
81
78
  server.registerTool(helpPreset.name, {
82
79
  title: helpPreset.title,
83
- description: [dynamicHelpDescription, dynamicHelpUsageGuide].join("\n\n"),
80
+ description: [helpPreset.description, renderedPreset.usageGuide].join("\n\n"),
84
81
  inputSchema: dynamicHelpSchema,
85
- }, createHelpHandler(preset));
82
+ }, createHelpHandler(renderedPreset));
86
83
  }
87
84
  function createServer() {
88
85
  const server = new McpServer({
89
86
  name: "keiyaku",
90
- version: "1.0.0",
87
+ version: packageVersion,
91
88
  });
92
89
  registerTools(server);
93
90
  return server;
@@ -148,6 +145,10 @@ async function startStreamableHttp() {
148
145
  });
149
146
  }
150
147
  async function main() {
148
+ if (process.argv.includes("--version")) {
149
+ console.log(packageVersion);
150
+ return;
151
+ }
151
152
  const transport = (process.env.KEIYAKU_MCP_TRANSPORT || "stdio").toLowerCase();
152
153
  if (transport === "streamable-http") {
153
154
  await startStreamableHttp();
@@ -0,0 +1,16 @@
1
+ export function applyArgumentDescriptions(schema, descriptions) {
2
+ const shape = schema.shape;
3
+ if (!shape) {
4
+ return schema;
5
+ }
6
+ const describedShape = {};
7
+ for (const [key, value] of Object.entries(shape)) {
8
+ if (!value || typeof value.describe !== "function") {
9
+ continue;
10
+ }
11
+ const description = descriptions?.[key];
12
+ describedShape[key] =
13
+ typeof description === "string" && description.trim().length > 0 ? value.describe(description) : value;
14
+ }
15
+ return schema.extend(describedShape);
16
+ }
@@ -1,6 +1,12 @@
1
1
  function isMarkdownHeaderLine(line) {
2
2
  return /^\s*#{2,}\s+/.test(line);
3
3
  }
4
+ function isPlainObject(value) {
5
+ if (!value || typeof value !== "object")
6
+ return false;
7
+ const prototype = Object.getPrototypeOf(value);
8
+ return prototype === Object.prototype || prototype === null;
9
+ }
4
10
  function parseFenceLine(line) {
5
11
  const leadingSpaces = line.match(/^ */)?.[0].length ?? 0;
6
12
  if (leadingSpaces > 3)
@@ -23,6 +29,30 @@ function isFenceClosingLine(line, fence) {
23
29
  function isIndentedCodeHeaderLine(line) {
24
30
  return /^ {4,}#{2,}\s+/.test(line);
25
31
  }
32
+ export function renderTemplate(template, values) {
33
+ return template.replace(/\$\{(?<key>[A-Za-z0-9_]+)\}/g, (match, _captured, _offset, _text, groups) => {
34
+ const key = groups?.key;
35
+ if (!key)
36
+ return match;
37
+ return Object.prototype.hasOwnProperty.call(values, key) ? values[key] : match;
38
+ });
39
+ }
40
+ export function renderPreset(preset, values) {
41
+ if (typeof preset === "string") {
42
+ return renderTemplate(preset, values);
43
+ }
44
+ if (Array.isArray(preset)) {
45
+ return preset.map((entry) => renderPreset(entry, values));
46
+ }
47
+ if (!isPlainObject(preset)) {
48
+ return preset;
49
+ }
50
+ const renderedPreset = {};
51
+ for (const [key, value] of Object.entries(preset)) {
52
+ renderedPreset[key] = renderPreset(value, values);
53
+ }
54
+ return renderedPreset;
55
+ }
26
56
  export function renderMarkdownSections(items) {
27
57
  if (items.length === 0)
28
58
  return "- (none)";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/keiyaku",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "MCP server for running iterative keiyaku workflows with Codex subagents.",
5
5
  "license": "MIT",
6
6
  "type": "module",