@astrosheep/keiyaku 0.1.10 → 0.1.11

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.
@@ -26,7 +26,7 @@ export const DEFAULT_PRESET = {
26
26
  'Review [Diff]: Confirm the scaffold aligns with the stated Architecture.',
27
27
  'Next: Issue your first ${drive}. One directive, one focus.',
28
28
  'If—and only if—the work already meets every criterion with absolute certainty, you may ${close}.',
29
- 'Otherwise, do not even think about present yet.',
29
+ 'Otherwise, do not even think about ${close} yet.',
30
30
  ],
31
31
  drive: [
32
32
  'Step complete. Review [Diff] for exactly what changed.',
@@ -42,12 +42,12 @@ export const DEFAULT_PRESET = {
42
42
  closeClaim: [
43
43
  'Contract fulfilled. Branch merged.',
44
44
  'You are back on base.',
45
- 'Next assignment: ${start} when ready.',
45
+ 'Next assignment: ${start} or ${ask} when ready.',
46
46
  ],
47
47
  closeDrop: [
48
48
  'Contract forfeited. Branch discarded. Work erased.',
49
49
  'No penalty beyond the lost effort. Clean slate.',
50
- 'Redefine with ${start}, or walk away.',
50
+ 'Redefine with ${start}, scout with ${ask}, or walk away.',
51
51
  ],
52
52
  },
53
53
  availableNames: DEFAULT_AVAILABLE_NAMES,
@@ -55,7 +55,7 @@ export const DEFAULT_PRESET = {
55
55
  start: {
56
56
  name: 'summon',
57
57
  title: 'Sign Keiyaku',
58
- description: 'Initialize a Keiyaku (Contract). Bind a Servant to a dedicated workspace (branch).\nYou are the Architect; they are the Instrument. Define the Goal and Scope clearly.\nThe contract isolates their existence until the objective is met.\nCall ONCE to seal the bond.\n\nFlow: summon → [drive x N] → present',
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
60
  title: 'REQUIRED. A concise codename for this hunt.',
61
61
  goal: 'REQUIRED. The Kill Condition. State exactly what success looks like for the servant to achieve.',
@@ -63,18 +63,18 @@ export const DEFAULT_PRESET = {
63
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
64
  constraints: 'REQUIRED. Non-negotiable Rules. Architectural and stylistic boundaries the servant must obey.',
65
65
  criteria: 'REQUIRED. Acceptance Criteria. Verifiable checks to prove the servant has finished the job.',
66
- name: 'Optional ${IDENTITY} profile to execute this mission. Available: ${AVAILABLE_NAMES}.',
66
+ name: 'Optional ${identity} profile to execute this mission. Available: ${available_names}.',
67
67
  cwd: "Optional repository path. Defaults to the server's current working directory.",
68
68
  },
69
69
  },
70
70
  drive: {
71
71
  name: 'drive',
72
72
  title: 'Iterate',
73
- description: "Issue a Directive. Command the Servant to execute the next phase of work.\nWhether scaffolding, implementing, or refining, this is the primary engine of progress.\nMANDATORY: Validate the output (git diff) before proceeding. Drive until the Goal is fully realized.\n\nFlow: summon → [drive x N] → present",
73
+ description: "Issue a Directive. Command the Servant to execute the next phase of work.\nWhether scaffolding, implementing, or refining, this is the primary engine of progress.\nMANDATORY: Validate the output (git diff) before proceeding. Drive until the Goal is fully realized.\n\nFlow: ${start} → [${drive} x N] → ${close}",
74
74
  args: {
75
75
  directive: 'REQUIRED. The Next Order. Precise instructions for the servant. Can be a correction ("fix the leak") or a continuation ("now add the tests").',
76
76
  context: 'Optional. New Intel. New error logs or details discovered after the servant\'s last strike.',
77
- name: 'Optional ${IDENTITY} profile to process this turn. Available: ${AVAILABLE_NAMES}.',
77
+ name: 'Optional ${identity} profile to process this turn. Available: ${available_names}.',
78
78
  cwd: "Optional repository path. Defaults to the server's current working directory.",
79
79
  },
80
80
  },
@@ -85,7 +85,7 @@ export const DEFAULT_PRESET = {
85
85
  args: {
86
86
  request: 'REQUIRED. The task, question, or mission to delegate to the servant.',
87
87
  context: 'REQUIRED. Relevant background or data the servant needs to execute the request.',
88
- name: 'Optional ${IDENTITY} profile to perform this task. Available: ${AVAILABLE_NAMES}.',
88
+ name: 'Optional ${identity} profile to perform this task. Available: ${available_names}.',
89
89
  cwd: "Optional repository path. Defaults to the server's current working directory.",
90
90
  },
91
91
  },
@@ -100,18 +100,18 @@ export const DEFAULT_PRESET = {
100
100
  'The Contract rejected. Coldly. Completely.\n' +
101
101
  'What happened next... no one speaks of it.\n\n' +
102
102
  'Do not call this to "finish." Call this when the work genuinely meets every criterion.\n' +
103
- 'If uncertain, return to ${DRIVE_TOOL_NAME}. Premature claims are not forgiven.\n\n' +
104
- 'You present. The Contract decides.\n\n' +
105
- 'Flow: ${START_TOOL_NAME} → [${DRIVE_TOOL_NAME} x N] → ${CLOSE_TOOL_NAME}',
103
+ 'If uncertain, return to ${drive}. Premature claims are not forgiven.\n\n' +
104
+ 'You call ${close}. The Contract decides.\n\n' +
105
+ 'Flow: ${start} → [${drive} x N] → ${close}',
106
106
  args: {
107
- petition: 'REQUIRED. CLAIM declares fulfillment; FORFEIT concedes failure.\nREQUIRES AN ACTIVE KEIYAKU (started via ${START_TOOL_NAME}).\nIf any score wavers, do not claim—return to ${DRIVE_TOOL_NAME}.',
107
+ 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}.',
108
108
  criteriaChecks: 'REQUIRED. For CLAIM: evidence that each criterion is met. For FORFEIT: honest account of what remains unfinished.',
109
109
  score_precise: 'REQUIRED (0-5). Architectural placement. 5 = exact layer, exact boundary, zero misplacement.',
110
110
  score_minimal: 'REQUIRED (0-5). Economy of change. 5 = no avoidable lines, no speculative edits, no hidden bloat.',
111
111
  score_isolated: 'REQUIRED (0-5). Surgical containment. 5 = zero unrelated files, zero opportunistic cleanup, zero collateral.',
112
112
  score_idiomatic: 'REQUIRED (0-5). Native fluency. 5 = naming, structure, style indistinguishable from the codebase.',
113
113
  score_cohesive: 'REQUIRED (0-5). Single responsibility. 5 = each unit does one thing, boundaries intact.',
114
- oath: 'Required for CLAIM. Your binding word. The Contract holds you to it.\nVerbatim: ${OATH_TEXT}',
114
+ oath: 'Required for CLAIM. Your binding word. The Contract holds you to it.\nVerbatim: ${oath_text}',
115
115
  cwd: "Optional repository path. Defaults to the server's current working directory.",
116
116
  },
117
117
  },
@@ -149,11 +149,11 @@ export const POCKET_PRESET = {
149
149
  ],
150
150
  closeClaim: [
151
151
  'Critter Captured! The entry is logged in the PC (base branch).',
152
- "Heal up. Use '${start}' to challenge the next opponent.",
152
+ "Heal up. Use '${start}' to challenge the next opponent, or '${ask}' to scan for another.",
153
153
  ],
154
154
  closeDrop: [
155
155
  'Ran Away Safely. The encounter is reset.',
156
- "Back to the map. Use '${start}' to find a wild Critter.",
156
+ "Back to the map. Use '${start}' to find a wild Critter, or '${ask}' to scan the tall grass.",
157
157
  ],
158
158
  },
159
159
  availableNames: ['grub', 'sparky', 'titan'],
@@ -161,7 +161,7 @@ export const POCKET_PRESET = {
161
161
  start: {
162
162
  name: 'choose_you',
163
163
  title: 'I Choose You!',
164
- 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',
164
+ 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}',
165
165
  args: {
166
166
  title: 'REQUIRED. Battle card title for this encounter.',
167
167
  goal: 'REQUIRED. Victory condition. Define exactly what winning this battle means.',
@@ -169,18 +169,18 @@ export const POCKET_PRESET = {
169
169
  context: 'REQUIRED. Battle background: key code paths, symptoms, logs, and repro clues.',
170
170
  constraints: 'REQUIRED. Battle rules that cannot be broken while fighting.',
171
171
  criteria: 'REQUIRED. Gym badges for completion: concrete checks proving victory.',
172
- name: 'Optional ${IDENTITY} to send into battle. Available: ${AVAILABLE_NAMES}.',
172
+ name: 'Optional ${identity} to send into battle. Available: ${available_names}.',
173
173
  cwd: 'Optional battlefield path (repository root). Defaults to current arena.',
174
174
  },
175
175
  },
176
176
  drive: {
177
177
  name: 'command',
178
178
  title: 'Issue Command',
179
- 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',
179
+ 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: ${start} → [${drive} x N] → ${close}',
180
180
  args: {
181
181
  directive: 'REQUIRED. The specific move or tactic to execute next.',
182
182
  context: 'Optional new battle intel discovered after the previous turn.',
183
- name: 'Optional ${IDENTITY} to execute this turn. Available: ${AVAILABLE_NAMES}.',
183
+ name: 'Optional ${identity} to execute this turn. Available: ${available_names}.',
184
184
  cwd: 'Optional battlefield path (repository root). Defaults to current arena.',
185
185
  },
186
186
  },
@@ -191,23 +191,23 @@ export const POCKET_PRESET = {
191
191
  args: {
192
192
  request: 'REQUIRED. What should the Dex analyze, compare, or execute.',
193
193
  context: 'REQUIRED. Context entries so the action targets the right ecosystem.',
194
- name: 'Optional ${IDENTITY} doing the work. Available: ${AVAILABLE_NAMES}.',
194
+ name: 'Optional ${identity} doing the work. Available: ${available_names}.',
195
195
  cwd: 'Optional battlefield path (repository root). Defaults to current arena.',
196
196
  },
197
197
  },
198
198
  close: {
199
199
  name: 'capture',
200
200
  title: 'End Battle',
201
- description: 'Attempt Capture. Present the weakened target for League Inspection.\nWARNING: The League (System) checks every Badge. If you attempt to `CLAIM` with fainted code, Disqualification (FORFEIT) 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}',
201
+ description: 'Attempt Capture. Present the weakened target for League Inspection.\nWARNING: The League (System) checks every Badge. If you attempt to `CLAIM` with fainted code, Disqualification (FORFEIT) is immediate.\nOnly throw the Ball when the target is 100% ready.\n\nFlow: ${start} → [${drive} x N] → ${close}',
202
202
  args: {
203
- petition: 'REQUIRED. CLAIM seeks Badge; FORFEIT forfeits the match.\nREQUIRES AN ACTIVE BATTLE (started via ${START_TOOL_NAME}).\nIf stats are low, continue with ${DRIVE_TOOL_NAME}.',
203
+ petition: 'REQUIRED. CLAIM seeks Badge; FORFEIT forfeits the match.\nREQUIRES AN ACTIVE BATTLE (started via ${start}).\nIf stats are low, continue with ${drive}.',
204
204
  criteriaChecks: 'REQUIRED. Badge-by-badge proof for CLAIM, or reason for Forfeit.',
205
205
  score_precise: 'REQUIRED score (0-5). 5 means a Critical Hit: exact layer, exact target, zero meaningful misplacement.',
206
206
  score_minimal: 'REQUIRED score (0-5). 5 means Max Efficiency: no wasted PP, no extra motion.',
207
207
  score_isolated: 'REQUIRED score (0-5). 5 means 1v1 Focus: zero side-quests, zero unrelated damage.',
208
208
  score_idiomatic: "REQUIRED score (0-5). 5 means STAB (Same Type Attack Bonus): perfectly matches the codebase style.",
209
209
  score_cohesive: 'REQUIRED score (0-5). 5 means Team Synergy: action serves one purpose with clean boundaries.',
210
- oath: "Trainer's Honor Code. Required for CLAIM. Verbatim: ${OATH_TEXT}",
210
+ oath: "Trainer's Honor Code. Required for CLAIM. Verbatim: ${oath_text}",
211
211
  cwd: 'Optional battlefield path (repository root). Defaults to current arena.',
212
212
  },
213
213
  },
@@ -245,11 +245,11 @@ export const MISCHIEF_PRESET = {
245
245
  ],
246
246
  closeClaim: [
247
247
  'Scheme Successful. The Lair is merged. The Minion is fed.',
248
- "Plotting something new? Use '${start}' for the next conquest.",
248
+ "Plotting something new? Use '${start}' for the next conquest, or '${ask}' to scout for vulnerabilities.",
249
249
  ],
250
250
  closeDrop: [
251
251
  'Plan Aborted. Evidence destroyed. The Minion was disposed of.',
252
- "Back to the drawing board. Use '${start}' to hatch a new plan.",
252
+ "Back to the drawing board. Use '${start}' to hatch a new plan, or '${ask}' to spy on enemies.",
253
253
  ],
254
254
  },
255
255
  availableNames: ['imp', 'minion', 'mastermind'],
@@ -257,7 +257,7 @@ export const MISCHIEF_PRESET = {
257
257
  start: {
258
258
  name: 'oi',
259
259
  title: 'Oi!',
260
- description: "Initiate Grand Scheme. Summon a Minion to the Lair (branch).\nYou are the Mastermind; they are the Henchman. Define the Conquest (Goal) with dominance.\nThe minion is bound to this Lair until the plot is realized.\nCall ONCE to start the machine.\n\nFlow: oi → [neh x N] → dayaa",
260
+ description: "Initiate Grand Scheme. Summon a Minion to the Lair (branch).\nYou are the Mastermind; they are the Henchman. Define the Conquest (Goal) with dominance.\nThe minion is bound to this Lair until the plot is realized.\nCall ONCE to start the machine.\n\nFlow: ${start} → [${drive} x N] → ${close}",
261
261
  args: {
262
262
  title: 'REQUIRED. Operation codename for your grand scheme.',
263
263
  goal: 'REQUIRED. The conquest objective. Define the exact end-state your minion must deliver.',
@@ -265,18 +265,18 @@ export const MISCHIEF_PRESET = {
265
265
  context: 'REQUIRED. Briefing dossier: relevant file paths, current failures, logs, and repro clues.',
266
266
  constraints: 'REQUIRED. Absolute decrees. Architectural and stylistic limits your minion must obey.',
267
267
  criteria: 'REQUIRED. Triumph conditions that can be verified without debate.',
268
- name: 'Optional ${IDENTITY} to command this operation. Available: ${AVAILABLE_NAMES}.',
268
+ name: 'Optional ${identity} to command this operation. Available: ${available_names}.',
269
269
  cwd: 'Optional lair path (repository root). Defaults to current command chamber.',
270
270
  },
271
271
  },
272
272
  drive: {
273
273
  name: 'neh',
274
274
  title: 'Neh...',
275
- description: 'Crack the Whip. Advance the Plot. Execute the next stage of the Plan.\nDon\'t just fix mistakes; force the scheme forward. The Minion obeys your every word.\nMANDATORY: Inspect the work (git diff) before the next order. Drive them until the World is yours.\n\nFlow: oi → [neh x N] → dayaa',
275
+ description: 'Crack the Whip. Advance the Plot. Execute the next stage of the Plan.\nDon\'t just fix mistakes; force the scheme forward. The Minion obeys your every word.\nMANDATORY: Inspect the work (git diff) before the next order. Drive them until the World is yours.\n\nFlow: ${start} → [${drive} x N] → ${close}',
276
276
  args: {
277
277
  directive: 'REQUIRED. Precise order for the next phase of the scheme.',
278
278
  context: 'Optional newly uncovered secrets or updated briefing.',
279
- name: 'Optional ${IDENTITY} to execute this phase. Available: ${AVAILABLE_NAMES}.',
279
+ name: 'Optional ${identity} to execute this phase. Available: ${available_names}.',
280
280
  cwd: 'Optional lair path (repository root). Defaults to current command chamber.',
281
281
  },
282
282
  },
@@ -287,23 +287,23 @@ export const MISCHIEF_PRESET = {
287
287
  args: {
288
288
  request: 'REQUIRED. The intel to gather or the dirty work to execute.',
289
289
  context: 'REQUIRED. World-state details needed for a sharp strike.',
290
- name: 'Optional ${IDENTITY} to handle this business. Available: ${AVAILABLE_NAMES}.',
290
+ name: 'Optional ${identity} to handle this business. Available: ${available_names}.',
291
291
  cwd: 'Optional lair path (repository root). Defaults to current command chamber.',
292
292
  },
293
293
  },
294
294
  close: {
295
295
  name: 'dayaa',
296
296
  title: 'Dayaa!',
297
- description: 'The Final Reveal. Present your Masterpiece to the Dark Council (System).\nWARNING: The Council destroys failure. If you `CLAIM` with weak plans, you will be eaten (FORFEIT).\nOnly throw the switch when the Doomsday Device is 100% operational.\n\nFlow: ${START_TOOL_NAME} → [${DRIVE_TOOL_NAME} x N] → ${CLOSE_TOOL_NAME}',
297
+ description: 'The Final Reveal. Present your Masterpiece to the Dark Council (System).\nWARNING: The Council destroys failure. If you `CLAIM` with weak plans, you will be eaten (FORFEIT).\nOnly throw the switch when the Doomsday Device is 100% operational.\n\nFlow: ${start} → [${drive} x N] → ${close}',
298
298
  args: {
299
- petition: 'REQUIRED. CLAIM demands rule; FORFEIT admits defeat.\nREQUIRES AN ACTIVE SCHEME (started via ${START_TOOL_NAME}).\nIf the plan is weak, improve it with ${DRIVE_TOOL_NAME}.',
299
+ petition: 'REQUIRED. CLAIM demands rule; FORFEIT admits defeat.\nREQUIRES AN ACTIVE SCHEME (started via ${start}).\nIf the plan is weak, improve it with ${drive}.',
300
300
  criteriaChecks: 'REQUIRED. Proof of conquest for CLAIM, or reason for self-destruct.',
301
301
  score_precise: 'REQUIRED (0-5). Architectural placement. 5 = exact layer, exact boundary, zero misplacement.',
302
302
  score_minimal: 'REQUIRED (0-5). Economy of change. 5 = no avoidable lines, no speculative edits, no hidden bloat.',
303
303
  score_isolated: 'REQUIRED (0-5). Surgical containment. 5 = zero unrelated files, zero opportunistic cleanup, zero collateral.',
304
304
  score_idiomatic: 'REQUIRED (0-5). Native fluency. 5 = naming, structure, style indistinguishable from the codebase.',
305
305
  score_cohesive: 'REQUIRED (0-5). Single responsibility. 5 = each unit does one thing, boundaries intact.',
306
- oath: "Mastermind's Vow. Required for CLAIM. Verbatim: ${OATH_TEXT}",
306
+ oath: "Mastermind's Vow. Required for CLAIM. Verbatim: ${oath_text}",
307
307
  cwd: 'Optional lair path (repository root). Defaults to current command chamber.',
308
308
  },
309
309
  },
package/build/index.js CHANGED
@@ -27,13 +27,14 @@ function registerTools(server) {
27
27
  const availableNames = getAvailableNamesForPreset(preset).join(", ");
28
28
  const currentOath = resolveOath();
29
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}'`,
30
+ start: preset.tools.start.name,
31
+ drive: preset.tools.drive.name,
32
+ ask: preset.tools.ask.name,
33
+ close: preset.tools.close.name,
34
+ identity: preset.identity,
35
+ preset_identities: presetIdentities,
36
+ available_names: availableNames,
37
+ oath_text: `'${currentOath}'`,
37
38
  });
38
39
  const startPreset = renderedPreset.tools.start;
39
40
  const drivePreset = renderedPreset.tools.drive;
@@ -27,11 +27,8 @@ export async function appendReview(cwd, round, reason) {
27
27
  const block = ["", `## Review ${round}`, "### Reason", reason, ""].join("\n");
28
28
  await fs.appendFile(tracePath, block, "utf-8");
29
29
  }
30
- function normalizeTraceText(text) {
31
- return text.replace(/\s+/g, " ").trim().slice(0, 500) || "Unknown error.";
32
- }
33
30
  export async function appendRoundReport(cwd, report) {
34
- const summaryText = normalizeTraceText(report.summary);
31
+ const summaryText = report.summary.trim() || "Unknown error.";
35
32
  const fileLines = report.filesModified.length > 0 ? report.filesModified.map((file) => `- ${file}`) : ["(none)"];
36
33
  const tracePath = path.join(cwd, TRACE_FILE);
37
34
  const lines = [
@@ -49,7 +46,7 @@ export async function appendRoundReport(cwd, report) {
49
46
  ...fileLines,
50
47
  ];
51
48
  if (report.errorMessage) {
52
- lines.push("### Error", normalizeTraceText(report.errorMessage));
49
+ lines.push("### Error", report.errorMessage);
53
50
  }
54
51
  lines.push("");
55
52
  const block = lines.join("\n");
@@ -71,7 +68,7 @@ export async function appendRoundSystemNote(cwd, round, note) {
71
68
  "### Files Modified",
72
69
  "(none)",
73
70
  "### Error",
74
- normalizeTraceText(note),
71
+ note,
75
72
  "",
76
73
  ].join("\n");
77
74
  await fs.appendFile(tracePath, block, "utf-8");
@@ -146,7 +146,7 @@ function buildMergeMessage(title, keiyakuContent, reportContent) {
146
146
  return `keiyaku(${title}): done\n\n---\n${keiyakuContent}\n---\n${reportContent}\n---\n`;
147
147
  }
148
148
  function summarizeForTrace(message) {
149
- return message.replace(/\s+/g, " ").trim().slice(0, 500) || "Unknown error.";
149
+ return message.length > 0 ? message : "Unknown error.";
150
150
  }
151
151
  function isAbortError(error) {
152
152
  return error instanceof Error && error.name === "AbortError";
@@ -226,6 +226,18 @@ function formatSubagentFailureMessage(failure) {
226
226
  `message=${summarizeForTrace(failure.message)}`,
227
227
  ].join("; ");
228
228
  }
229
+ function filterInternalSections(text) {
230
+ const sections = parseMarkdownHeaders(text);
231
+ if (sections.length === 0)
232
+ return text.trim();
233
+ return sections
234
+ .filter((section) => {
235
+ const firstLine = section.split(/\r?\n/, 1)[0] ?? "";
236
+ return !/^#{2,3}\s+(?:criteria|constraints)\s+check\s*$/i.test(firstLine.trim());
237
+ })
238
+ .join("\n\n")
239
+ .trim();
240
+ }
229
241
  async function appendRoundResult(cwd, round, summary, failureMessage) {
230
242
  const dirtyPaths = await git.getDirtyPaths(cwd);
231
243
  const filesModified = collectRoundFiles(dirtyPaths);
@@ -269,7 +281,7 @@ async function runAndRecordRound(cwd, titleToken, round, prompt, options) {
269
281
  console.error(failureStderrLog);
270
282
  appendDebugBlock("subagent failure stderr", failure.stderrSnippet, { cwd, section: "codex-stderr" });
271
283
  }
272
- failureMessage = summarizeForTrace(failure.message);
284
+ failureMessage = failure.message.trim() || "Unknown error.";
273
285
  summary =
274
286
  options.failureMode === "round_specific"
275
287
  ? `Round ${round} completed with subagent execution failure recorded in trace.`
@@ -354,11 +366,12 @@ export async function handleStart(input) {
354
366
  await git.addFiles(cwd, [KEIYAKU_FILE, TRACE_FILE]);
355
367
  await git.commit(cwd, `keiyaku(${branchToken}): open`);
356
368
  const prompt = buildStartPrompt(finalTitle, finalGoal, directive);
357
- const { summary } = await runAndRecordRound(cwd, branchToken, 1, prompt, {
369
+ const { summary: rawSummary } = await runAndRecordRound(cwd, branchToken, 1, prompt, {
358
370
  signal,
359
371
  name,
360
372
  failureMode: "standard",
361
373
  });
374
+ const summary = filterInternalSections(rawSummary);
362
375
  const trace = await fs.readFile(path.join(cwd, TRACE_FILE), "utf-8");
363
376
  const diff = await git.getIncrementalDiff(cwd);
364
377
  return {
@@ -563,7 +576,7 @@ export async function handleDrive(input) {
563
576
  await appendReview(cwd, plan.targetRound, plan.reviewReason);
564
577
  await git.addFiles(cwd, TRACE_FILE);
565
578
  await git.commit(cwd, `keiyaku(${title}): iterate round ${plan.targetRound}`);
566
- const { summary } = await runAndRecordRound(cwd, title, plan.targetRound, plan.prompt, {
579
+ const { summary: rawSummary } = await runAndRecordRound(cwd, title, plan.targetRound, plan.prompt, {
567
580
  signal,
568
581
  name,
569
582
  failureMode: "round_specific",
@@ -573,6 +586,7 @@ export async function handleDrive(input) {
573
586
  await git.commit(cwd, `keiyaku(${title}): round ${plan.targetRound} cancelled`);
574
587
  },
575
588
  });
589
+ const summary = filterInternalSections(rawSummary);
576
590
  const trace = await fs.readFile(path.join(cwd, TRACE_FILE), "utf-8");
577
591
  const diff = await git.getIncrementalDiff(cwd);
578
592
  return {
@@ -16,17 +16,31 @@ Round: 1 (initial implementation).
16
16
  - Do not violate constraints.
17
17
  - Meet all criteria.
18
18
  - Match existing structure.
19
- 6. End with 4 sections:
20
- - Summary: outcome.
21
- - Changes: files, logic delta, evidence.
22
- - Aesthetics Gap
23
- Self-critique.
24
- precise — right place, layer, abstraction
25
- minimal smallest diff that solves it
26
- isolated no unrelated changes riding along
27
- idiomatic — fits the codebase's voice
28
- cohesive each unit does one thing
29
- - Blindspots: untested paths/assumptions.
19
+ 6. End with exactly these 6 sections using this strict Markdown template:
20
+
21
+ ## Outcome
22
+ [One sentence summarizing the outcome]
23
+
24
+ ## Changes
25
+ - [File path]: [Brief description of change]
26
+ - [Logic delta]: [Key architectural or logic shift]
27
+
28
+ ## Aesthetics Gap
29
+ Self-critique.
30
+ - precise: [score/reasoning]
31
+ - minimal: [score/reasoning]
32
+ - isolated: [score/reasoning]
33
+ - idiomatic: [score/reasoning]
34
+ - cohesive: [score/reasoning]
35
+
36
+ ## Blindspots
37
+ - [Unintentional side effects or untested paths]
38
+
39
+ ## Criteria Check
40
+ - [List each criterion and its status: MET/PARTIAL/TODO]
41
+
42
+ ## Constraints Check
43
+ - [List each constraint and confirm compliance: COMPLIANT/VIOLATED]
30
44
 
31
45
  No git commands. Do not edit KEIYAKU_TRACE.md.`;
32
46
  }
@@ -43,17 +57,31 @@ ${reason}
43
57
  4. Fix review reason first.
44
58
  5. No drift from constraints/criteria.
45
59
  6. Keep diff small.
46
- 7. End with 4 sections:
47
- - Summary: outcome.
48
- - Changes: files, logic delta, evidence.
49
- - Aesthetics Gap
50
- Self-critique.
51
- precise — right place, layer, abstraction
52
- minimal smallest diff that solves it
53
- isolated no unrelated changes riding along
54
- idiomatic — fits the codebase's voice
55
- cohesive each unit does one thing
56
- - Blindspots: untested paths/assumptions.
60
+ 7. End with exactly these 6 sections using this strict Markdown template:
61
+
62
+ ## Outcome
63
+ [One sentence summarizing the outcome]
64
+
65
+ ## Changes
66
+ - [File path]: [Brief description of change]
67
+ - [Logic delta]: [Key architectural or logic shift]
68
+
69
+ ## Aesthetics Gap
70
+ Self-critique.
71
+ - precise: [score/reasoning]
72
+ - minimal: [score/reasoning]
73
+ - isolated: [score/reasoning]
74
+ - idiomatic: [score/reasoning]
75
+ - cohesive: [score/reasoning]
76
+
77
+ ## Blindspots
78
+ - [Unintentional side effects or untested paths]
79
+
80
+ ## Criteria Check
81
+ - [List each criterion and its status: MET/PARTIAL/TODO]
82
+
83
+ ## Constraints Check
84
+ - [List each constraint and confirm compliance: COMPLIANT/VIOLATED]
57
85
 
58
86
  No git commands. Do not edit KEIYAKU_TRACE.md.`;
59
87
  }
@@ -66,5 +94,5 @@ ${request}
66
94
  Context:
67
95
  ${context}
68
96
 
69
- Provide a clear reasoning summary or code generation in the response.`;
97
+ Provide a clear final response.`;
70
98
  }
@@ -46,6 +46,11 @@ function supportsHeaderColor() {
46
46
  function colorizeHeaderTitle(titleLabel) {
47
47
  return `\x1b[1;38;5;15;48;5;23m${titleLabel}\x1b[0m`;
48
48
  }
49
+ function colorizeErrorStatus(status) {
50
+ if (!supportsHeaderColor())
51
+ return status;
52
+ return `\x1b[1;38;5;15;48;5;196m${status}\x1b[0m`;
53
+ }
49
54
  function formatHeader(title) {
50
55
  const headerWidth = 60;
51
56
  const normalizedTitle = title.trim().toUpperCase();
@@ -85,7 +90,7 @@ ${summary}
85
90
  : "";
86
91
  // Next Steps Block
87
92
  const nextBlock = nextHints.length > 0
88
- ? `${formatHeader("Next Steps")}\n${nextHints.map(h => `› ${h}`).join("\n")}`
93
+ ? `${formatHeader("Hints")}\n${nextHints.map(h => `› ${h}`).join("\n")}`
89
94
  : "";
90
95
  return [
91
96
  statusBlock,
@@ -151,7 +156,7 @@ export function buildKeiyakuSuccessResponse(result, input) {
151
156
  ...formatMaybe("Current Branch", result.branch, 200),
152
157
  ...formatMaybe("Base Branch", result.baseBranch, 200),
153
158
  ];
154
- const text = assembleResponse(`Started (Round ${result.round})`, `Created branch '${result.branch}' (base: '${result.baseBranch}'). ${result.summary}`, [inputSection, diffSection], nextHints, infoLines);
159
+ const text = assembleResponse(`Started (Round ${result.round})`, `Created branch '${result.branch}' (base: '${result.baseBranch}').\n\n${result.summary}`, [inputSection, diffSection], nextHints, infoLines);
155
160
  return {
156
161
  content: [{ type: "text", text }],
157
162
  structuredContent: buildSuccessStructuredContent(getStartToolName(), {
@@ -178,7 +183,7 @@ export function buildDriveResponse(result, input) {
178
183
  ...formatMaybe("Current Branch", result.branch, 200),
179
184
  ...formatMaybe("Base Branch", result.baseBranch, 200),
180
185
  ];
181
- const text = assembleResponse(`Driven (Round ${result.round})`, `Updated branch '${result.branch}'. ${result.summary}`, [inputSection, diffSection], nextHints, infoLines);
186
+ const text = assembleResponse(`Driven (Round ${result.round})`, `Updated branch '${result.branch}'.\n\n${result.summary}`, [inputSection, diffSection], nextHints, infoLines);
182
187
  return {
183
188
  content: [{ type: "text", text }],
184
189
  structuredContent: buildSuccessStructuredContent(getDriveToolName(), {
@@ -273,7 +278,7 @@ export function buildCloseDropResponse(result, input) {
273
278
  export function buildToolErrorResponse(input) {
274
279
  const inputEcho = (input.inputEcho ?? []).map((line) => truncateForDisplay(line, 800));
275
280
  const shouldRaiseProtocolError = input.errorType === "runtime_error";
276
- const text = assembleResponse("Failed", input.message, [buildSection("Input Context", inputEcho)], [input.hint]);
281
+ const text = assembleResponse(colorizeErrorStatus("!! FAILED !!"), input.message, [buildSection("Input Context", inputEcho)], [input.hint]);
277
282
  const response = {
278
283
  content: [{ type: "text", text }],
279
284
  structuredContent: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/keiyaku",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "MCP server for running iterative keiyaku workflows with Codex subagents.",
5
5
  "license": "MIT",
6
6
  "type": "module",