@astrosheep/keiyaku 0.1.14 → 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.
@@ -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"];
@@ -62,6 +62,14 @@ function hintForFlowCode(code, message) {
62
62
  return "Title cannot be converted to a valid branch token for `keiyaku/<title>`.";
63
63
  case "UNKNOWN_SUBAGENT":
64
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.`;
65
73
  }
66
74
  }
67
75
  const MESSAGE_HINT_PATTERNS = [
@@ -93,6 +101,10 @@ const MESSAGE_HINT_PATTERNS = [
93
101
  { code: "ROUND_SUBAGENT_FAILED", patterns: ["failed during subagent execution"] },
94
102
  { code: "INVALID_BRANCH_TITLE", patterns: ["cannot be converted to a valid branch name"] },
95
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"] },
96
108
  ];
97
109
  function inferFlowCodeFromMessage(message) {
98
110
  for (const entry of MESSAGE_HINT_PATTERNS) {
@@ -7,13 +7,13 @@ 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',
@@ -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
  },
@@ -103,11 +104,11 @@ export const DEFAULT_PRESET = {
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
106
  constraintsChecks: 'REQUIRED. For CLAIM: evidence that each constraint stayed compliant. For FORFEIT: list known violations or unresolved risk.',
106
- score_precise: 'REQUIRED (0-5). Architectural placement. 5 = exact layer, exact boundary, zero misplacement.',
107
- score_minimal: 'REQUIRED (0-5). Economy of change. 5 = no avoidable lines, no speculative edits, no hidden bloat.',
108
- score_isolated: 'REQUIRED (0-5). Surgical containment. 5 = zero unrelated files, zero opportunistic cleanup, zero collateral.',
109
- score_idiomatic: 'REQUIRED (0-5). Native fluency. 5 = naming, structure, style indistinguishable from the codebase.',
110
- score_cohesive: 'REQUIRED (0-5). Single responsibility. 5 = each unit does one thing, boundaries intact.',
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.',
111
112
  oath: 'Required for CLAIM. Your binding word. The Contract holds you to it.\nVerbatim: ${oath_text}',
112
113
  cwd: "Optional repository path. Defaults to the server's current working directory.",
113
114
  },
@@ -158,12 +159,13 @@ export const POCKET_PRESET = {
158
159
  title: 'I Choose You!',
159
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}',
160
161
  args: {
161
- title: 'REQUIRED. Battle card title for this encounter.',
162
- goal: 'REQUIRED. Victory condition. Define exactly what winning this battle means.',
163
- directive: 'Optional Turn 1 strategy. Use to focus on the opening move.',
164
- context: 'REQUIRED. Battle background: key code paths, symptoms, logs, and repro clues.',
165
- constraints: 'REQUIRED. Battle rules that cannot be broken while fighting.',
166
- 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.',
167
169
  name: 'Optional ${identity} to send into battle. Available: ${available_names}.',
168
170
  cwd: 'Optional battlefield path (repository root). Defaults to current arena.',
169
171
  },
@@ -198,11 +200,11 @@ export const POCKET_PRESET = {
198
200
  petition: 'REQUIRED. CLAIM seeks Badge; FORFEIT forfeits the match.\nREQUIRES AN ACTIVE BATTLE (started via ${start}).\nIf stats are low, continue with ${drive}.',
199
201
  criteriaChecks: 'REQUIRED. Badge-by-badge proof for CLAIM, or reason for Forfeit.',
200
202
  constraintsChecks: 'REQUIRED. Rule-by-rule proof that constraints were respected, or known breaks when forfeiting.',
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.',
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
  },
@@ -253,12 +255,13 @@ export const MISCHIEF_PRESET = {
253
255
  title: 'Oi!',
254
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}",
255
257
  args: {
256
- title: 'REQUIRED. Operation codename for your grand scheme.',
257
- goal: 'REQUIRED. The conquest objective. Define the exact end-state your minion must deliver.',
258
- directive: 'Optional first-order command. Point them in the right direction.',
259
- context: 'REQUIRED. Briefing dossier: relevant file paths, current failures, logs, and repro clues.',
260
- constraints: 'REQUIRED. Absolute decrees. Architectural and stylistic limits your minion must obey.',
261
- 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.',
262
265
  name: 'Optional ${identity} to command this operation. Available: ${available_names}.',
263
266
  cwd: 'Optional lair path (repository root). Defaults to current command chamber.',
264
267
  },
@@ -293,11 +296,11 @@ export const MISCHIEF_PRESET = {
293
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}.',
294
297
  criteriaChecks: 'REQUIRED. Proof of conquest for CLAIM, or reason for self-destruct.',
295
298
  constraintsChecks: 'REQUIRED. Constraint-by-constraint compliance proof for CLAIM, or confession of violations for FORFEIT.',
296
- score_precise: 'REQUIRED (0-5). Architectural placement. 5 = exact layer, exact boundary, zero misplacement.',
297
- score_minimal: 'REQUIRED (0-5). Economy of change. 5 = no avoidable lines, no speculative edits, no hidden bloat.',
298
- score_isolated: 'REQUIRED (0-5). Surgical containment. 5 = zero unrelated files, zero opportunistic cleanup, zero collateral.',
299
- score_idiomatic: 'REQUIRED (0-5). Native fluency. 5 = naming, structure, style indistinguishable from the codebase.',
300
- score_cohesive: 'REQUIRED (0-5). Single responsibility. 5 = each unit does one thing, boundaries intact.',
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.',
301
304
  oath: "Mastermind's Vow. Required for CLAIM. Verbatim: ${oath_text}",
302
305
  cwd: 'Optional lair path (repository root). Defaults to current command chamber.',
303
306
  },
@@ -31,7 +31,7 @@ export function createCloseHandler() {
31
31
  throw new Error("Unexpected CLAIM outcome shape");
32
32
  }
33
33
  const finalOutcome = outcome;
34
- appendDebugLog(`tool close CLAIM success branch=${finalOutcome.branch} base=${finalOutcome.baseBranch}`, {
34
+ appendDebugLog(`tool close CLAIM success branch=${finalOutcome.currentBranch} base=${finalOutcome.baseBranch}`, {
35
35
  cwd: workingDir,
36
36
  section: "script",
37
37
  });
@@ -51,7 +51,7 @@ export function createCloseHandler() {
51
51
  throw new Error("Unexpected FORFEIT outcome shape");
52
52
  }
53
53
  const finalOutcome = outcome;
54
- appendDebugLog(`tool close FORFEIT success branch=${finalOutcome.branch} base=${finalOutcome.baseBranch}`, {
54
+ appendDebugLog(`tool close FORFEIT success branch=${finalOutcome.currentBranch} base=${finalOutcome.baseBranch}`, {
55
55
  cwd: workingDir,
56
56
  section: "script",
57
57
  });
@@ -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,12 +55,13 @@ 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
66
  `Path: ${workingDir}`,
53
67
  ],
@@ -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
  });
@@ -25,11 +26,11 @@ 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
28
  constraintsChecks: z.array(z.string().trim().min(1)).min(1),
28
- score_precise: z.number().min(0).max(5),
29
- score_minimal: z.number().min(0).max(5),
30
- score_isolated: z.number().min(0).max(5),
31
- score_idiomatic: z.number().min(0).max(5),
32
- score_cohesive: z.number().min(0).max(5),
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),
33
34
  oath: z.string().optional(),
34
35
  cwd: z.string().optional(),
35
36
  });