@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 +1 -1
- package/build/config/term-presets.js +108 -113
- package/build/index.js +37 -36
- package/build/utils/schema-utils.js +16 -0
- package/build/utils/text-utils.js +30 -0
- package/package.json +1 -1
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`: `
|
|
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
|
-
'
|
|
4
|
-
'
|
|
5
|
-
'
|
|
3
|
+
'B-tier',
|
|
4
|
+
'A-tier',
|
|
5
|
+
'S-tier',
|
|
6
6
|
];
|
|
7
|
-
const
|
|
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**
|
|
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
|
|
27
|
-
"
|
|
28
|
-
'
|
|
29
|
-
"
|
|
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
|
|
33
|
-
"
|
|
34
|
-
'
|
|
35
|
-
"
|
|
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
|
-
"
|
|
39
|
-
'Need more? Continue asking, or check the logs
|
|
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
|
-
'
|
|
43
|
-
"
|
|
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
|
-
'
|
|
47
|
-
"Clean slate. Use
|
|
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: '
|
|
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.
|
|
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: "
|
|
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.
|
|
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: '
|
|
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.
|
|
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: '
|
|
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: '
|
|
108
|
-
description: '
|
|
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
|
|
109
|
+
question: 'REQUIRED. The specific Protocol, Law, or Workflow detail to clarify.',
|
|
111
110
|
},
|
|
112
111
|
},
|
|
113
112
|
},
|
|
114
113
|
};
|
|
115
|
-
export const
|
|
116
|
-
id: '
|
|
117
|
-
identity: '
|
|
114
|
+
export const POCKET_PRESET = {
|
|
115
|
+
id: 'pocket',
|
|
116
|
+
identity: 'Critter',
|
|
118
117
|
divineJudgment: DEFAULT_JUDGMENT_CONFIG,
|
|
119
|
-
usageGuide: "##
|
|
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
|
-
'
|
|
123
|
-
"
|
|
124
|
-
|
|
125
|
-
"Next
|
|
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
|
|
129
|
-
"
|
|
130
|
-
'
|
|
131
|
-
"
|
|
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
|
-
"
|
|
135
|
-
"
|
|
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
|
-
'
|
|
139
|
-
"
|
|
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
|
-
'
|
|
143
|
-
"
|
|
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: ['
|
|
145
|
+
availableNames: ['grub', 'sparky', 'titan'],
|
|
147
146
|
tools: {
|
|
148
147
|
start: {
|
|
149
148
|
name: 'choose_you',
|
|
150
149
|
title: 'I Choose You!',
|
|
151
|
-
description: '
|
|
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
|
|
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.
|
|
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: '
|
|
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.
|
|
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.
|
|
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: '
|
|
176
|
-
title: '
|
|
177
|
-
description: 'Scan the
|
|
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
|
|
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.
|
|
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: '
|
|
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
|
|
191
|
-
criteriaChecks: 'REQUIRED. Badge-by-badge proof for INVOKE, or
|
|
192
|
-
score_precise: 'REQUIRED score (0-5). 5 means a
|
|
193
|
-
score_minimal: 'REQUIRED score (0-5). 5 means
|
|
194
|
-
score_isolated: 'REQUIRED score (0-5). 5 means
|
|
195
|
-
score_idiomatic: "REQUIRED score (0-5). 5 means
|
|
196
|
-
score_cohesive: 'REQUIRED score (0-5). 5 means
|
|
197
|
-
oath: "Trainer's
|
|
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: '
|
|
204
|
-
description: '
|
|
202
|
+
title: 'Trainer Guide',
|
|
203
|
+
description: 'Consult the League Rules. Clarify the Battle System and Turn Structure.',
|
|
205
204
|
args: {
|
|
206
|
-
question: 'REQUIRED. The
|
|
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
|
-
"
|
|
219
|
-
"
|
|
220
|
-
"
|
|
221
|
-
"Next
|
|
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
|
-
'
|
|
225
|
-
"Deep Scrutiny: Use 'git diff HEAD~1 -- <path>' to
|
|
226
|
-
'Validation: Ensure no
|
|
227
|
-
"
|
|
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
|
|
231
|
-
"Still Puzzled?
|
|
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
|
|
235
|
-
"Plotting something new? Use '${start}'
|
|
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
|
-
'
|
|
239
|
-
"
|
|
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: "
|
|
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
|
|
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.
|
|
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: '
|
|
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
|
|
265
|
-
context: 'Optional newly uncovered
|
|
266
|
-
name: 'Optional ${IDENTITY} to execute this
|
|
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:
|
|
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.
|
|
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: '
|
|
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
|
|
287
|
-
criteriaChecks: 'REQUIRED. Proof of
|
|
288
|
-
score_precise: 'REQUIRED score (0-5). 5 means
|
|
289
|
-
score_minimal: 'REQUIRED score (0-5). 5 means
|
|
290
|
-
score_isolated: 'REQUIRED score (0-5). 5 means
|
|
291
|
-
score_idiomatic: 'REQUIRED score (0-5). 5 means
|
|
292
|
-
score_cohesive: 'REQUIRED score (0-5). 5 means
|
|
293
|
-
oath: "
|
|
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: '
|
|
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 === '
|
|
313
|
-
return
|
|
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', '
|
|
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,
|
|
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
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
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
|
|
30
|
-
.
|
|
31
|
-
|
|
32
|
-
|
|
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:
|
|
60
|
+
description: startPreset.description,
|
|
64
61
|
inputSchema: dynamicStartSchema,
|
|
65
62
|
}, createStartHandler());
|
|
66
63
|
server.registerTool(drivePreset.name, {
|
|
67
64
|
title: drivePreset.title,
|
|
68
|
-
description:
|
|
65
|
+
description: drivePreset.description,
|
|
69
66
|
inputSchema: dynamicDriveSchema,
|
|
70
67
|
}, createDriveHandler());
|
|
71
68
|
server.registerTool(askPreset.name, {
|
|
72
69
|
title: askPreset.title,
|
|
73
|
-
description:
|
|
70
|
+
description: askPreset.description,
|
|
74
71
|
inputSchema: dynamicAskSchema,
|
|
75
72
|
}, createAskHandler());
|
|
76
73
|
server.registerTool(closePreset.name, {
|
|
77
74
|
title: closePreset.title,
|
|
78
|
-
description:
|
|
75
|
+
description: closePreset.description,
|
|
79
76
|
inputSchema: dynamicCloseSchema,
|
|
80
77
|
}, createCloseHandler());
|
|
81
78
|
server.registerTool(helpPreset.name, {
|
|
82
79
|
title: helpPreset.title,
|
|
83
|
-
description: [
|
|
80
|
+
description: [helpPreset.description, renderedPreset.usageGuide].join("\n\n"),
|
|
84
81
|
inputSchema: dynamicHelpSchema,
|
|
85
|
-
}, createHelpHandler(
|
|
82
|
+
}, createHelpHandler(renderedPreset));
|
|
86
83
|
}
|
|
87
84
|
function createServer() {
|
|
88
85
|
const server = new McpServer({
|
|
89
86
|
name: "keiyaku",
|
|
90
|
-
version:
|
|
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)";
|