@astrosheep/keiyaku 0.1.13 → 0.1.15

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
@@ -90,6 +90,10 @@ 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
+ Project-level law lives in:
94
+ - `.keiyaku/base-constraints.md`: Global constraints inherited by every keiyaku.
95
+ - `ask` also injects `.keiyaku/base-constraints.md` as **reference context** (it may be unrelated to some asks).
96
+
93
97
  *Note: These files are automatically cleaned up (or committed) when you `present` (or preset equivalent) the keiyaku.*
94
98
 
95
99
  ---
@@ -1,3 +1,5 @@
1
1
  export const KEIYAKU_FILE = "KEIYAKU.md";
2
+ export const KEIYAKU_DRAFT_FILE = "KEIYAKU.draft.md";
3
+ export const KEIYAKU_DRAFT_NEW_FILE = "KEIYAKU.draft.new.md";
2
4
  export const TRACE_FILE = "KEIYAKU_TRACE.md";
3
5
  export const ROUND_SUBAGENTS = ["agent-a", "agent-b", "agent-c"];
@@ -29,13 +29,15 @@ export function wrapFlowError(action, err) {
29
29
  return new Error(`${action} failed: ${asMessage(err)}`);
30
30
  }
31
31
  function hintForFlowCode(code, message) {
32
+ const preset = resolveTermPreset();
33
+ const { drive, close } = preset.tools;
32
34
  switch (code) {
33
35
  case "NOT_GIT_REPO":
34
36
  return "The provided `cwd` is not a git repository.";
35
37
  case "ACTIVE_KEIYAKU_EXISTS":
36
- return "An active keiyaku branch already exists in this repository. Continue with `drive`, or use `close` + `FORFEIT` to drop it.";
38
+ return message;
37
39
  case "EXISTING_KEIYAKU_BRANCH_FOUND":
38
- return "At least one local `keiyaku/*` branch already exists in this repository.";
40
+ return `\`${preset.tools.start.name}\` blocked: Existing branches detected. \`git switch\` to one and \`${close.name}\` it, or choose a different \`title\`.`;
39
41
  case "EMPTY_PARAM":
40
42
  return "One or more required parameters are empty.";
41
43
  case "DIRTY_WORKTREE":
@@ -43,13 +45,13 @@ function hintForFlowCode(code, message) {
43
45
  case "NOT_ACTIVE_KEIYAKU_BRANCH":
44
46
  return message;
45
47
  case "MISSING_KEIYAKU_BASE":
46
- return "Current keiyaku branch is missing `keiyakuBase` metadata.";
48
+ return `Current branch is missing \`keiyakuBase\` metadata. This branch might not have been created via \`${preset.tools.start.name}\`.`;
47
49
  case "MISSING_PROTOCOL_FILES":
48
50
  return "Required protocol files are missing (`KEIYAKU.md` or `KEIYAKU_TRACE.md`).";
49
51
  case "DONE_MERGE_CONFLICT":
50
- return "DONE encountered a git merge conflict.";
52
+ return `Merge conflict detected while merging into the base branch during \`CLAIM\`. Resolve the conflicts, commit the changes, and then run \`${close.name}\` again.`;
51
53
  case "CLOSE_QUALITY_GATE_FAILED":
52
- return "CLAIM was denied because one or more verification thresholds were not met (check .keiyaku/settings.json).";
54
+ return `CLAIM denied: Inadequate work. Scores are below the required standard. Use \`${drive.name}\` to refine the mission until it is worthy of merging.`;
53
55
  case "OATH_MISMATCH":
54
56
  return message;
55
57
  case "SUBAGENT_DID_NOT_ADVANCE_ROUND":
@@ -60,6 +62,14 @@ function hintForFlowCode(code, message) {
60
62
  return "Title cannot be converted to a valid branch token for `keiyaku/<title>`.";
61
63
  case "UNKNOWN_SUBAGENT":
62
64
  return unknownSubagentHint();
65
+ case "FROM_FILE_NOT_FOUND":
66
+ return "The path in `from_file` does not exist. Use an existing file path (absolute or relative to `cwd`).";
67
+ case "INVALID_KEIYAKU_DRAFT":
68
+ return "Invalid draft/input. Provide `title`, `goal`, `context`, and non-empty `criteria` (or include them in `from_file` with proper section headers).";
69
+ case "KEIYAKU_FILE_EXISTS":
70
+ return `\`${preset.tools.start.name}\` requires a clean slate. Remove existing \`KEIYAKU.md\` before starting.`;
71
+ case "DRAFT_FILE_EXISTS":
72
+ return `Existing \`KEIYAKU.draft.md\` detected. Use it via \`from_file\` or delete it before starting.`;
63
73
  }
64
74
  }
65
75
  const MESSAGE_HINT_PATTERNS = [
@@ -91,6 +101,10 @@ const MESSAGE_HINT_PATTERNS = [
91
101
  { code: "ROUND_SUBAGENT_FAILED", patterns: ["failed during subagent execution"] },
92
102
  { code: "INVALID_BRANCH_TITLE", patterns: ["cannot be converted to a valid branch name"] },
93
103
  { code: "UNKNOWN_SUBAGENT", patterns: ["Unknown subagent"] },
104
+ { code: "FROM_FILE_NOT_FOUND", patterns: ["from_file path does not exist"] },
105
+ { code: "INVALID_KEIYAKU_DRAFT", patterns: ["invalid keiyaku draft"] },
106
+ { code: "KEIYAKU_FILE_EXISTS", patterns: ["pre-flight failed: KEIYAKU.md already exists"] },
107
+ { code: "DRAFT_FILE_EXISTS", patterns: ["pre-flight failed: KEIYAKU.draft.md exists but from_file does not target it"] },
94
108
  ];
95
109
  function inferFlowCodeFromMessage(message) {
96
110
  for (const entry of MESSAGE_HINT_PATTERNS) {
@@ -7,19 +7,19 @@ export const DEFAULT_AVAILABLE_NAMES = [
7
7
  export const DEFAULT_SUBAGENT_NAME = 'A-tier';
8
8
  const DEFAULT_VERDICT_CONFIG = {
9
9
  thresholds: {
10
- precise: 5,
11
- minimal: 4,
12
- isolated: 4,
13
- idiomatic: 3,
14
- cohesive: 4,
10
+ precise: 10,
11
+ minimal: 8,
12
+ isolated: 8,
13
+ idiomatic: 6,
14
+ cohesive: 8,
15
15
  },
16
- minTotalScore: 20,
16
+ minTotalScore: 40,
17
17
  };
18
18
  export const DEFAULT_PRESET = {
19
19
  id: 'default',
20
20
  identity: 'Servant',
21
21
  verdict: DEFAULT_VERDICT_CONFIG,
22
- usageGuide: '## Servant 使用指南\n\n**B-tier** — 免费实习生 🆓\n- 免!费!的!听懂了吗?!?随便用,别心疼。\n- 测试、脚本、重构这种 boring 的脏活累活?丢给它!\n- 本大爷的手是用来做更高贵的事情的!(指不碰那些 trivial 的烂代码)。\n\n**A-tier** — 升级版扳手 🔧\n- B-tier 笨到让你想掀桌的时候用这个\n- 比 B-tier 靠谱,但别指望它有灵魂\n\n**S-tier** — 禁术 ⚠️💀\n- 它是 Debug 恶魔,确实强得离谱,但也贵得离谱!\n- 这个 Human 很穷,要是乱用把 Human 榨干了,真的会考虑把我们卖掉的。\n- 只有在世界毁灭或者 Bug 已经变异到无法理解的时候再考虑。\n\n**Every call has a price. The Contract always collects.**\n\n## Workflow\n`ask` (anytime) | `summon` -> [`drive` | `ask`]* -> `present`',
22
+ usageGuide: '## Workflow\n**You are the Architect.** You use these tools to command the Servants.\n\n`ask` (anytime) | `summon` -> [`drive` | `ask`]* -> `present`\n\n## 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.**',
23
23
  nextHints: {
24
24
  start: [
25
25
  'Keiyaku signed. The Servant is bound to this branch until release.',
@@ -57,12 +57,13 @@ export const DEFAULT_PRESET = {
57
57
  title: 'Sign Keiyaku',
58
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: ${start} → [${drive} x N] → ${close}',
59
59
  args: {
60
- title: 'REQUIRED. A concise codename for this hunt.',
61
- goal: 'REQUIRED. The Kill Condition. State exactly what success looks like for the servant to achieve.',
62
- directive: 'Optional First Step. Use to leash the servant to a specific starting point (e.g., "Analyze the code first"). If omitted, the Servant will attempt the Goal directly.',
63
- 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.',
64
- constraints: 'REQUIRED. Non-negotiable Rules. Architectural and stylistic boundaries the servant must obey.',
65
- criteria: 'REQUIRED. Acceptance Criteria. Verifiable checks to prove the servant has finished the job.',
60
+ from_file: "Optional markdown draft path. Loads `# title` + required sections from file (relative to cwd unless absolute).",
61
+ title: 'Required unless provided via `from_file`. A concise codename for this hunt.',
62
+ goal: 'Required unless provided via `from_file`. The Kill Condition for this mission.',
63
+ directive: 'Optional First Step. Overrides `## Directive` from `from_file` when both are provided.',
64
+ context: 'Required unless provided via `from_file`. Mission intel: behavior gap, key files, logs, and critical background.',
65
+ constraints: 'Optional. Non-negotiable rules as a list of strings. If omitted, the draft value (if any) is used.',
66
+ criteria: 'Required unless provided via `from_file`. Verifiable checks proving completion.',
66
67
  name: 'Optional ${identity} profile to execute this mission. Available: ${available_names}.',
67
68
  cwd: "Optional repository path. Defaults to the server's current working directory.",
68
69
  },
@@ -84,7 +85,7 @@ export const DEFAULT_PRESET = {
84
85
  description: 'Dispatch a temporary Servant for a stateless task.\nUse for quick reconnaissance, code generation, isolated execution, or one-off tasks.\nNo contract signed. No branch created. Pure utility.',
85
86
  args: {
86
87
  request: 'REQUIRED. The task, question, or mission to delegate to the servant.',
87
- context: 'REQUIRED. Relevant background or data the servant needs to execute the request.',
88
+ context: 'REQUIRED. Relevant background or data the servant needs to execute the request. Note: .keiyaku/base-constraints.md is also injected as reference context (may be unrelated).',
88
89
  name: 'Optional ${identity} profile to perform this task. Available: ${available_names}.',
89
90
  cwd: "Optional repository path. Defaults to the server's current working directory.",
90
91
  },
@@ -102,11 +103,12 @@ export const DEFAULT_PRESET = {
102
103
  args: {
103
104
  petition: 'REQUIRED. CLAIM declares fulfillment; FORFEIT concedes failure.\nREQUIRES AN ACTIVE KEIYAKU (started via ${start}).\nIf any score wavers, do not claim—return to ${drive}.',
104
105
  criteriaChecks: 'REQUIRED. For CLAIM: evidence that each criterion is met. For FORFEIT: honest account of what remains unfinished.',
105
- score_precise: 'REQUIRED (0-5). Architectural placement. 5 = exact layer, exact boundary, zero misplacement.',
106
- score_minimal: 'REQUIRED (0-5). Economy of change. 5 = no avoidable lines, no speculative edits, no hidden bloat.',
107
- score_isolated: 'REQUIRED (0-5). Surgical containment. 5 = zero unrelated files, zero opportunistic cleanup, zero collateral.',
108
- score_idiomatic: 'REQUIRED (0-5). Native fluency. 5 = naming, structure, style indistinguishable from the codebase.',
109
- score_cohesive: 'REQUIRED (0-5). Single responsibility. 5 = each unit does one thing, boundaries intact.',
106
+ constraintsChecks: 'REQUIRED. For CLAIM: evidence that each constraint stayed compliant. For FORFEIT: list known violations or unresolved risk.',
107
+ score_precise: 'REQUIRED (0-10). Architectural placement. 10 = exact layer, exact boundary, zero misplacement.',
108
+ score_minimal: 'REQUIRED (0-10). Economy of change. 10 = no avoidable lines, no speculative edits, no hidden bloat.',
109
+ score_isolated: 'REQUIRED (0-10). Surgical containment. 10 = zero unrelated files, zero opportunistic cleanup, zero collateral.',
110
+ score_idiomatic: 'REQUIRED (0-10). Native fluency. 10 = naming, structure, style indistinguishable from the codebase.',
111
+ score_cohesive: 'REQUIRED (0-10). Single responsibility. 10 = each unit does one thing, boundaries intact.',
110
112
  oath: 'Required for CLAIM. Your binding word. The Contract holds you to it.\nVerbatim: ${oath_text}',
111
113
  cwd: "Optional repository path. Defaults to the server's current working directory.",
112
114
  },
@@ -115,9 +117,7 @@ export const DEFAULT_PRESET = {
115
117
  name: 'help',
116
118
  title: 'Protocol Codex',
117
119
  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.',
118
- args: {
119
- question: 'REQUIRED. The specific Protocol, Law, or Workflow detail to clarify.',
120
- },
120
+ args: {},
121
121
  },
122
122
  },
123
123
  };
@@ -159,12 +159,13 @@ export const POCKET_PRESET = {
159
159
  title: 'I Choose You!',
160
160
  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: ${start} → [${drive} x N] → ${close}',
161
161
  args: {
162
- title: 'REQUIRED. Battle card title for this encounter.',
163
- goal: 'REQUIRED. Victory condition. Define exactly what winning this battle means.',
164
- directive: 'Optional Turn 1 strategy. Use to focus on the opening move.',
165
- context: 'REQUIRED. Battle background: key code paths, symptoms, logs, and repro clues.',
166
- constraints: 'REQUIRED. Battle rules that cannot be broken while fighting.',
167
- criteria: 'REQUIRED. Gym badges for completion: concrete checks proving victory.',
162
+ from_file: 'Optional battle plan markdown path with title + sections.',
163
+ title: 'Required unless provided via `from_file`. Battle card title for this encounter.',
164
+ goal: 'Required unless provided via `from_file`. Victory condition for this battle.',
165
+ directive: 'Optional Turn 1 strategy (overrides draft directive).',
166
+ context: 'Required unless provided via `from_file`. Battle background and repro clues.',
167
+ constraints: 'Optional battle rules as a list of strings. Uses draft constraints if omitted.',
168
+ criteria: 'Required unless provided via `from_file`. Concrete checks proving victory.',
168
169
  name: 'Optional ${identity} to send into battle. Available: ${available_names}.',
169
170
  cwd: 'Optional battlefield path (repository root). Defaults to current arena.',
170
171
  },
@@ -186,7 +187,7 @@ export const POCKET_PRESET = {
186
187
  description: 'Scan the Environment. A stateless look-up or experiment.\nUse for analyzing the codebase, checking type advantages, or running field tests.\nNo PP cost. No turn used. Fast and functional.',
187
188
  args: {
188
189
  request: 'REQUIRED. What should the Dex analyze, compare, or execute.',
189
- context: 'REQUIRED. Context entries so the action targets the right ecosystem.',
190
+ context: 'REQUIRED. Context entries so the action targets the right ecosystem. Note: .keiyaku/base-constraints.md is also injected as reference context (may be unrelated).',
190
191
  name: 'Optional ${identity} doing the work. Available: ${available_names}.',
191
192
  cwd: 'Optional battlefield path (repository root). Defaults to current arena.',
192
193
  },
@@ -198,11 +199,12 @@ export const POCKET_PRESET = {
198
199
  args: {
199
200
  petition: 'REQUIRED. CLAIM seeks Badge; FORFEIT forfeits the match.\nREQUIRES AN ACTIVE BATTLE (started via ${start}).\nIf stats are low, continue with ${drive}.',
200
201
  criteriaChecks: 'REQUIRED. Badge-by-badge proof for CLAIM, or reason for Forfeit.',
201
- score_precise: 'REQUIRED score (0-5). 5 means a Critical Hit: exact layer, exact target, zero meaningful misplacement.',
202
- score_minimal: 'REQUIRED score (0-5). 5 means Max Efficiency: no wasted PP, no extra motion.',
203
- score_isolated: 'REQUIRED score (0-5). 5 means 1v1 Focus: zero side-quests, zero unrelated damage.',
204
- score_idiomatic: "REQUIRED score (0-5). 5 means STAB (Same Type Attack Bonus): perfectly matches the codebase style.",
205
- score_cohesive: 'REQUIRED score (0-5). 5 means Team Synergy: action serves one purpose with clean boundaries.',
202
+ constraintsChecks: 'REQUIRED. Rule-by-rule proof that constraints were respected, or known breaks when forfeiting.',
203
+ score_precise: 'REQUIRED score (0-10). 10 means a Critical Hit: exact layer, exact target, zero meaningful misplacement.',
204
+ score_minimal: 'REQUIRED score (0-10). 10 means Max Efficiency: no wasted PP, no extra motion.',
205
+ score_isolated: 'REQUIRED score (0-10). 10 means 1v1 Focus: zero side-quests, zero unrelated damage.',
206
+ score_idiomatic: "REQUIRED score (0-10). 10 means STAB (Same Type Attack Bonus): perfectly matches the codebase style.",
207
+ score_cohesive: 'REQUIRED score (0-10). 10 means Team Synergy: action serves one purpose with clean boundaries.',
206
208
  oath: "Trainer's Honor Code. Required for CLAIM. Verbatim: ${oath_text}",
207
209
  cwd: 'Optional battlefield path (repository root). Defaults to current arena.',
208
210
  },
@@ -211,9 +213,7 @@ export const POCKET_PRESET = {
211
213
  name: 'help',
212
214
  title: 'Trainer Guide',
213
215
  description: 'Consult the League Rules. Clarify the Battle System and Turn Structure.',
214
- args: {
215
- question: 'REQUIRED. The Pocket Battle System question you want answered.',
216
- },
216
+ args: {},
217
217
  },
218
218
  },
219
219
  };
@@ -255,12 +255,13 @@ export const MISCHIEF_PRESET = {
255
255
  title: 'Oi!',
256
256
  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: ${start} → [${drive} x N] → ${close}",
257
257
  args: {
258
- title: 'REQUIRED. Operation codename for your grand scheme.',
259
- goal: 'REQUIRED. The conquest objective. Define the exact end-state your minion must deliver.',
260
- directive: 'Optional first-order command. Point them in the right direction.',
261
- context: 'REQUIRED. Briefing dossier: relevant file paths, current failures, logs, and repro clues.',
262
- constraints: 'REQUIRED. Absolute decrees. Architectural and stylistic limits your minion must obey.',
263
- criteria: 'REQUIRED. Triumph conditions that can be verified without debate.',
258
+ from_file: 'Optional scheme markdown path with title + sectioned objectives.',
259
+ title: 'Required unless provided via `from_file`. Operation codename.',
260
+ goal: 'Required unless provided via `from_file`. Conquest objective and end-state.',
261
+ directive: 'Optional first-order command (overrides draft directive).',
262
+ context: 'Required unless provided via `from_file`. Briefing dossier and technical clues.',
263
+ constraints: 'Optional decrees as a list of strings. Uses draft constraints when omitted.',
264
+ criteria: 'Required unless provided via `from_file`. Verifiable triumph conditions.',
264
265
  name: 'Optional ${identity} to command this operation. Available: ${available_names}.',
265
266
  cwd: 'Optional lair path (repository root). Defaults to current command chamber.',
266
267
  },
@@ -282,7 +283,7 @@ export const MISCHIEF_PRESET = {
282
283
  description: 'Send a Disposable. A stateless mission for intel or sabotage.\nUse this to scout enemy territory, steal documents, or run quick-and-dirty experiments.\nIf it dies, it dies! Just make sure it reports back before it vanishes.',
283
284
  args: {
284
285
  request: 'REQUIRED. The intel to gather or the dirty work to execute.',
285
- context: 'REQUIRED. World-state details needed for a sharp strike.',
286
+ context: 'REQUIRED. World-state details needed for a sharp strike. Note: .keiyaku/base-constraints.md is also injected as reference context (may be unrelated).',
286
287
  name: 'Optional ${identity} to handle this business. Available: ${available_names}.',
287
288
  cwd: 'Optional lair path (repository root). Defaults to current command chamber.',
288
289
  },
@@ -294,11 +295,12 @@ export const MISCHIEF_PRESET = {
294
295
  args: {
295
296
  petition: 'REQUIRED. CLAIM demands rule; FORFEIT admits defeat.\nREQUIRES AN ACTIVE SCHEME (started via ${start}).\nIf the plan is weak, improve it with ${drive}.',
296
297
  criteriaChecks: 'REQUIRED. Proof of conquest for CLAIM, or reason for self-destruct.',
297
- score_precise: 'REQUIRED (0-5). Architectural placement. 5 = exact layer, exact boundary, zero misplacement.',
298
- score_minimal: 'REQUIRED (0-5). Economy of change. 5 = no avoidable lines, no speculative edits, no hidden bloat.',
299
- score_isolated: 'REQUIRED (0-5). Surgical containment. 5 = zero unrelated files, zero opportunistic cleanup, zero collateral.',
300
- score_idiomatic: 'REQUIRED (0-5). Native fluency. 5 = naming, structure, style indistinguishable from the codebase.',
301
- score_cohesive: 'REQUIRED (0-5). Single responsibility. 5 = each unit does one thing, boundaries intact.',
298
+ constraintsChecks: 'REQUIRED. Constraint-by-constraint compliance proof for CLAIM, or confession of violations for FORFEIT.',
299
+ score_precise: 'REQUIRED (0-10). Architectural placement. 10 = exact layer, exact boundary, zero misplacement.',
300
+ score_minimal: 'REQUIRED (0-10). Economy of change. 10 = no avoidable lines, no speculative edits, no hidden bloat.',
301
+ score_isolated: 'REQUIRED (0-10). Surgical containment. 10 = zero unrelated files, zero opportunistic cleanup, zero collateral.',
302
+ score_idiomatic: 'REQUIRED (0-10). Native fluency. 10 = naming, structure, style indistinguishable from the codebase.',
303
+ score_cohesive: 'REQUIRED (0-10). Single responsibility. 10 = each unit does one thing, boundaries intact.',
302
304
  oath: "Mastermind's Vow. Required for CLAIM. Verbatim: ${oath_text}",
303
305
  cwd: 'Optional lair path (repository root). Defaults to current command chamber.',
304
306
  },
@@ -307,9 +309,7 @@ export const MISCHIEF_PRESET = {
307
309
  name: 'help',
308
310
  title: 'Nani?!',
309
311
  description: 'Consult the Evil Overlord List. Review the Laws of the Lair.',
310
- args: {
311
- question: 'REQUIRED. The law or protocol detail you want the realm to explain.',
312
- },
312
+ args: {},
313
313
  },
314
314
  },
315
315
  };
@@ -29,7 +29,7 @@ export function createAskHandler() {
29
29
  `Request: ${request}`,
30
30
  `Context: ${context}`,
31
31
  ...(name ? [`${preset.identity}: ${name}`] : []),
32
- `CWD: ${workingDir}`,
32
+ `Path: ${workingDir}`,
33
33
  ],
34
34
  });
35
35
  }
@@ -4,11 +4,12 @@ import { buildCloseDoneResponse, buildCloseDropResponse, } from "../workflow/res
4
4
  import { resolveTermPreset } from "../config/term-presets.js";
5
5
  import { handleToolError } from "./shared.js";
6
6
  export function createCloseHandler() {
7
- return async ({ petition, criteriaChecks, score_precise, score_minimal, score_isolated, score_idiomatic, score_cohesive, oath, cwd, }, extra) => {
7
+ return async ({ petition, criteriaChecks, constraintsChecks, score_precise, score_minimal, score_isolated, score_idiomatic, score_cohesive, oath, cwd, }, extra) => {
8
8
  const workingDir = cwd || process.cwd();
9
9
  const criteriaCheckParts = criteriaChecks;
10
+ const constraintsCheckParts = constraintsChecks ?? [];
10
11
  try {
11
- appendDebugLog(`tool close start petition=${petition} cwd=${workingDir} criteriaChecks=${criteriaCheckParts.length}`, {
12
+ appendDebugLog(`tool close start petition=${petition} cwd=${workingDir} criteriaChecks=${criteriaCheckParts.length} constraintsChecks=${constraintsCheckParts.length}`, {
12
13
  cwd: workingDir,
13
14
  section: "script",
14
15
  });
@@ -16,6 +17,7 @@ export function createCloseHandler() {
16
17
  cwd: workingDir,
17
18
  petition,
18
19
  criteriaChecks: criteriaCheckParts,
20
+ constraintsChecks: constraintsCheckParts,
19
21
  score_precise,
20
22
  score_minimal,
21
23
  score_isolated,
@@ -29,12 +31,13 @@ export function createCloseHandler() {
29
31
  throw new Error("Unexpected CLAIM outcome shape");
30
32
  }
31
33
  const finalOutcome = outcome;
32
- appendDebugLog(`tool close CLAIM success branch=${finalOutcome.branch} base=${finalOutcome.baseBranch}`, {
34
+ appendDebugLog(`tool close CLAIM success branch=${finalOutcome.currentBranch} base=${finalOutcome.baseBranch}`, {
33
35
  cwd: workingDir,
34
36
  section: "script",
35
37
  });
36
38
  return buildCloseDoneResponse(finalOutcome, {
37
39
  criteriaChecks: criteriaCheckParts,
40
+ constraintsChecks: constraintsCheckParts,
38
41
  score_precise,
39
42
  score_minimal,
40
43
  score_isolated,
@@ -48,12 +51,13 @@ export function createCloseHandler() {
48
51
  throw new Error("Unexpected FORFEIT outcome shape");
49
52
  }
50
53
  const finalOutcome = outcome;
51
- appendDebugLog(`tool close FORFEIT success branch=${finalOutcome.branch} base=${finalOutcome.baseBranch}`, {
54
+ appendDebugLog(`tool close FORFEIT success branch=${finalOutcome.currentBranch} base=${finalOutcome.baseBranch}`, {
52
55
  cwd: workingDir,
53
56
  section: "script",
54
57
  });
55
58
  return buildCloseDropResponse(finalOutcome, {
56
59
  criteriaChecks: criteriaCheckParts,
60
+ constraintsChecks: constraintsCheckParts,
57
61
  score_precise,
58
62
  score_minimal,
59
63
  score_isolated,
@@ -73,9 +77,10 @@ export function createCloseHandler() {
73
77
  inputEcho: [
74
78
  `Petition: ${petition}`,
75
79
  `Criteria checks (${criteriaCheckParts.length}): ${criteriaCheckParts.join("; ")}`,
80
+ `Constraints checks (${constraintsCheckParts.length}): ${constraintsCheckParts.join("; ")}`,
76
81
  `Scores: precise=${score_precise} minimal=${score_minimal} isolated=${score_isolated} idiomatic=${score_idiomatic} cohesive=${score_cohesive}`,
77
82
  ...(oath ? [`Oath: ${oath}`] : []),
78
- `CWD: ${workingDir}`,
83
+ `Path: ${workingDir}`,
79
84
  ],
80
85
  });
81
86
  }
@@ -35,7 +35,7 @@ export function createDriveHandler() {
35
35
  `Directive: ${directive}`,
36
36
  ...(context ? [`Context: ${context}`] : []),
37
37
  ...(name ? [`${preset.identity}: ${name}`] : []),
38
- `CWD: ${workingDir}`,
38
+ `Path: ${workingDir}`,
39
39
  ],
40
40
  });
41
41
  }
@@ -1,13 +1,12 @@
1
1
  import { buildHelpResponse } from "../workflow/response-builders.js";
2
2
  export function createHelpHandler(preset) {
3
- return async ({ question }) => {
3
+ return async ({}) => {
4
4
  const helpContent = [
5
5
  "# Keiyaku System Help",
6
6
  "",
7
7
  "## Core Files (.keiyaku/)",
8
8
  "These files define the 'Law' of the project. **CRITICAL**: Use Markdown level 2 headers (## header).",
9
9
  "",
10
- "- **base-criteria.md**: Universal standards for task completion. Automatically inherited.",
11
10
  "- **base-constraints.md**: Mandatory architectural boundaries and coding standards.",
12
11
  "",
13
12
  preset.usageGuide,
@@ -15,12 +14,9 @@ export function createHelpHandler(preset) {
15
14
  "## Protocol Files",
16
15
  "- **KEIYAKU.md**: The immutable mission definition for the current task.",
17
16
  "- **KEIYAKU_TRACE.md**: The audit log of all rounds and reviews.",
18
- "",
19
- `Regarding: \"${question}\"`,
20
17
  ].join("\n");
21
18
  return buildHelpResponse({
22
19
  tool: preset.tools.help.name,
23
- question,
24
20
  text: helpContent,
25
21
  });
26
22
  };
@@ -1,35 +1,48 @@
1
1
  import { appendDebugLog } from "../utils/debug-log.js";
2
- import { summonServant } from "../workflow/summon.js";
2
+ import { startKeiyaku } from "../workflow/start.js";
3
3
  import { buildKeiyakuSuccessResponse, } from "../workflow/response-builders.js";
4
4
  import { resolveTermPreset } from "../config/term-presets.js";
5
5
  import { handleToolError } from "./shared.js";
6
+ function normalizeOptionalArg(value) {
7
+ if (value === undefined)
8
+ return undefined;
9
+ return value.trim().length > 0 ? value : undefined;
10
+ }
6
11
  export function createStartHandler() {
7
- return async ({ title, goal, directive, context, constraints, criteria, name, cwd }, extra) => {
12
+ return async ({ from_file, title, goal, directive, context, constraints, criteria, name, cwd }, extra) => {
8
13
  const workingDir = cwd || process.cwd();
14
+ const normalizedFromFile = normalizeOptionalArg(from_file);
9
15
  try {
10
16
  appendDebugLog(`tool start cwd=${workingDir}`, { cwd: workingDir, section: "script" });
11
- const result = await summonServant({
12
- cwd: workingDir,
13
- title,
14
- goal,
15
- directive,
16
- context,
17
- criteria,
18
- constraints,
19
- name,
20
- signal: extra.signal,
21
- });
22
- appendDebugLog(`tool start success branch=${result.branch} base=${result.baseBranch} round=${result.round}`, {
17
+ const result = normalizedFromFile
18
+ ? await startKeiyaku({
19
+ cwd: workingDir,
20
+ from_file: normalizedFromFile,
21
+ title: normalizeOptionalArg(title),
22
+ goal: normalizeOptionalArg(goal),
23
+ directive: normalizeOptionalArg(directive),
24
+ context: normalizeOptionalArg(context),
25
+ constraints,
26
+ criteria,
27
+ name,
28
+ signal: extra.signal,
29
+ })
30
+ : await startKeiyaku({
31
+ cwd: workingDir,
32
+ title: title ?? "",
33
+ goal: goal ?? "",
34
+ directive: normalizeOptionalArg(directive),
35
+ context: context ?? "",
36
+ constraints,
37
+ criteria: criteria ?? [],
38
+ name,
39
+ signal: extra.signal,
40
+ });
41
+ appendDebugLog(`tool start success branch=${result.currentBranch} base=${result.baseBranch} round=${result.round}`, {
23
42
  cwd: workingDir,
24
43
  section: "script",
25
44
  });
26
45
  return buildKeiyakuSuccessResponse(result, {
27
- title,
28
- goal,
29
- directive,
30
- context,
31
- constraints,
32
- criteria,
33
46
  name,
34
47
  cwd: workingDir,
35
48
  });
@@ -42,14 +55,15 @@ export function createStartHandler() {
42
55
  cwd: workingDir,
43
56
  logLabel: "tool start",
44
57
  inputEcho: [
45
- `Title: ${title}`,
46
- `Goal: ${goal}`,
58
+ ...(normalizedFromFile ? [`From file: ${normalizedFromFile}`] : []),
59
+ ...(title ? [`Title: ${title}`] : []),
60
+ ...(goal ? [`Goal: ${goal}`] : []),
47
61
  ...(directive ? [`Directive: ${directive}`] : []),
48
- `Criteria (${criteria.length}): ${criteria.join("; ")}`,
62
+ ...(criteria ? [`Criteria (${criteria.length}): ${criteria.join("; ")}`] : []),
49
63
  ...(context ? [`Context: ${context}`] : []),
50
- ...(constraints ? [`Constraints: ${constraints}`] : []),
64
+ ...(constraints ? [`Constraints (${constraints.length}): ${constraints.join("; ")}`] : []),
51
65
  ...(name ? [`${preset.identity}: ${name}`] : []),
52
- `CWD: ${workingDir}`,
66
+ `Path: ${workingDir}`,
53
67
  ],
54
68
  });
55
69
  }
package/build/index.js CHANGED
@@ -5,7 +5,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
5
5
  import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
6
6
  import { readFileSync } from "node:fs";
7
7
  import { resolveOath } from "./workflow/contract.js";
8
- import { askToolSchema, startToolSchema, driveToolSchema, closeToolSchema, helpToolSchema, } from "./types/tool-schemas.js";
8
+ import { askToolSchema, startToolSchema, driveToolSchema, closeToolSchema, helpToolSchema, } from "./types/tooling.js";
9
9
  import { listTermPresets, resolveTermPreset, getAvailableNamesForPreset } from "./config/term-presets.js";
10
10
  import { renderPreset } from "./utils/text-utils.js";
11
11
  import { applyArgumentDescriptions } from "./utils/schema-utils.js";
@@ -1,11 +1,12 @@
1
1
  import { z } from "zod";
2
2
  export const startToolSchema = z.object({
3
- title: z.string(),
4
- goal: z.string(),
3
+ from_file: z.string().optional(),
4
+ title: z.string().optional(),
5
+ goal: z.string().optional(),
5
6
  directive: z.string().optional(),
6
- context: z.string(),
7
- constraints: z.string(),
8
- criteria: z.array(z.string().trim().min(1)).min(1),
7
+ context: z.string().optional(),
8
+ constraints: z.array(z.string().trim().min(1)).min(1).optional(),
9
+ criteria: z.array(z.string().trim().min(1)).min(1).optional(),
9
10
  name: z.string().optional(),
10
11
  cwd: z.string().optional(),
11
12
  });
@@ -24,14 +25,13 @@ export const askToolSchema = z.object({
24
25
  export const closeToolSchema = z.object({
25
26
  petition: z.enum(["CLAIM", "FORFEIT"]),
26
27
  criteriaChecks: z.array(z.string().trim().min(1)).min(1),
27
- score_precise: z.number().min(0).max(5),
28
- score_minimal: z.number().min(0).max(5),
29
- score_isolated: z.number().min(0).max(5),
30
- score_idiomatic: z.number().min(0).max(5),
31
- score_cohesive: z.number().min(0).max(5),
28
+ constraintsChecks: z.array(z.string().trim().min(1)).min(1),
29
+ score_precise: z.number().min(0).max(10),
30
+ score_minimal: z.number().min(0).max(10),
31
+ score_isolated: z.number().min(0).max(10),
32
+ score_idiomatic: z.number().min(0).max(10),
33
+ score_cohesive: z.number().min(0).max(10),
32
34
  oath: z.string().optional(),
33
35
  cwd: z.string().optional(),
34
36
  });
35
- export const helpToolSchema = z.object({
36
- question: z.string(),
37
- });
37
+ export const helpToolSchema = z.object({});
@@ -183,8 +183,8 @@ export async function getIncrementalDiff(cwd) {
183
183
  if (sections.length === 0)
184
184
  return "No changes in last round.";
185
185
  const filePreviews = [];
186
- const MAX_TOTAL_CHARS = 4000;
187
- const MAX_LINES_PER_FILE = 40;
186
+ const MAX_TOTAL_CHARS = 15000;
187
+ const MAX_LINES_PER_FILE = 50;
188
188
  let omittedFiles = 0;
189
189
  for (let i = 0; i < sections.length; i += 1) {
190
190
  const section = sections[i];