@astrosheep/keiyaku 0.1.15 → 0.1.16

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.
@@ -2,32 +2,37 @@ import { appendDebugLog } from "../utils/debug-log.js";
2
2
  import { presentWork } from "../workflow/present.js";
3
3
  import { buildCloseDoneResponse, buildCloseDropResponse, } from "../workflow/response-builders.js";
4
4
  import { resolveTermPreset } from "../config/term-presets.js";
5
+ import { closeToolSchema } from "../types/tooling.js";
5
6
  import { handleToolError } from "./shared.js";
6
7
  export function createCloseHandler() {
7
- return async ({ petition, criteriaChecks, constraintsChecks, score_precise, score_minimal, score_isolated, score_idiomatic, score_cohesive, oath, cwd, }, extra) => {
8
- const workingDir = cwd || process.cwd();
9
- const criteriaCheckParts = criteriaChecks;
10
- const constraintsCheckParts = constraintsChecks ?? [];
8
+ return async (args, extra) => {
9
+ let petition = "UNKNOWN";
10
+ let workingDir = process.cwd();
11
+ let criteriaCheckParts = [];
12
+ let constraintsCheckParts = [];
13
+ let oath;
14
+ let scoreLine;
11
15
  try {
16
+ const input = closeToolSchema.parse(args);
17
+ petition = input.petition;
18
+ workingDir = input.cwd || process.cwd();
19
+ if (input.petition === "CLAIM") {
20
+ criteriaCheckParts = input.criteriaChecks;
21
+ constraintsCheckParts = input.constraintsChecks;
22
+ oath = input.oath;
23
+ scoreLine = `Scores: precise=${input.score_precise} minimal=${input.score_minimal} isolated=${input.score_isolated} idiomatic=${input.score_idiomatic} cohesive=${input.score_cohesive}`;
24
+ }
12
25
  appendDebugLog(`tool close start petition=${petition} cwd=${workingDir} criteriaChecks=${criteriaCheckParts.length} constraintsChecks=${constraintsCheckParts.length}`, {
13
26
  cwd: workingDir,
14
27
  section: "script",
15
28
  });
16
29
  const outcome = await presentWork({
30
+ ...input,
17
31
  cwd: workingDir,
18
- petition,
19
- criteriaChecks: criteriaCheckParts,
20
- constraintsChecks: constraintsCheckParts,
21
- score_precise,
22
- score_minimal,
23
- score_isolated,
24
- score_idiomatic,
25
- score_cohesive,
26
- oath,
27
32
  signal: extra.signal,
28
33
  });
29
- if (petition === "CLAIM") {
30
- if (!("result" in outcome) || outcome.result !== "done") {
34
+ if (input.petition === "CLAIM") {
35
+ if (!("result" in outcome) || outcome.result !== "merged") {
31
36
  throw new Error("Unexpected CLAIM outcome shape");
32
37
  }
33
38
  const finalOutcome = outcome;
@@ -38,12 +43,12 @@ export function createCloseHandler() {
38
43
  return buildCloseDoneResponse(finalOutcome, {
39
44
  criteriaChecks: criteriaCheckParts,
40
45
  constraintsChecks: constraintsCheckParts,
41
- score_precise,
42
- score_minimal,
43
- score_isolated,
44
- score_idiomatic,
45
- score_cohesive,
46
- oath,
46
+ score_precise: input.score_precise,
47
+ score_minimal: input.score_minimal,
48
+ score_isolated: input.score_isolated,
49
+ score_idiomatic: input.score_idiomatic,
50
+ score_cohesive: input.score_cohesive,
51
+ oath: input.oath,
47
52
  cwd: workingDir,
48
53
  });
49
54
  }
@@ -56,14 +61,6 @@ export function createCloseHandler() {
56
61
  section: "script",
57
62
  });
58
63
  return buildCloseDropResponse(finalOutcome, {
59
- criteriaChecks: criteriaCheckParts,
60
- constraintsChecks: constraintsCheckParts,
61
- score_precise,
62
- score_minimal,
63
- score_isolated,
64
- score_idiomatic,
65
- score_cohesive,
66
- oath,
67
64
  cwd: workingDir,
68
65
  });
69
66
  }
@@ -78,8 +75,8 @@ export function createCloseHandler() {
78
75
  `Petition: ${petition}`,
79
76
  `Criteria checks (${criteriaCheckParts.length}): ${criteriaCheckParts.join("; ")}`,
80
77
  `Constraints checks (${constraintsCheckParts.length}): ${constraintsCheckParts.join("; ")}`,
81
- `Scores: precise=${score_precise} minimal=${score_minimal} isolated=${score_isolated} idiomatic=${score_idiomatic} cohesive=${score_cohesive}`,
82
- ...(oath ? [`Oath: ${oath}`] : []),
78
+ ...(scoreLine ? [scoreLine] : []),
79
+ ...(petition === "CLAIM" && oath ? [`Oath: ${oath}`] : []),
83
80
  `Path: ${workingDir}`,
84
81
  ],
85
82
  });
@@ -5,9 +5,14 @@ export function createHelpHandler(preset) {
5
5
  "# Keiyaku System Help",
6
6
  "",
7
7
  "## Core Files (.keiyaku/)",
8
- "These files define the 'Law' of the project. **CRITICAL**: Use Markdown level 2 headers (## header).",
8
+ "These files define the 'Law' and configuration of the project.",
9
9
  "",
10
10
  "- **base-constraints.md**: Mandatory architectural boundaries and coding standards.",
11
+ " - **Purpose**: Global constraints injected into every mission.",
12
+ " - **Logic**: The system prioritize extracting all list items (`-` or `*`) as individual constraints.",
13
+ " - **Formatting**: Use lists for constraints. You can use H3+ headers (`###`) for organization, but they are stripped if the section contains lists.",
14
+ " - **Strict Rule**: DO NOT use H1 (`#`) or H2 (`##`) within constraints or as content if they aren't meant to be item separators, as they will trigger validation errors.",
15
+ "- **settings.json**: Local configuration for the Keiyaku environment.",
11
16
  "",
12
17
  preset.usageGuide,
13
18
  "",
package/build/index.js CHANGED
@@ -41,9 +41,7 @@ function registerTools(server) {
41
41
  const askPreset = renderedPreset.tools.ask;
42
42
  const closePreset = renderedPreset.tools.close;
43
43
  const helpPreset = renderedPreset.tools.help;
44
- const dynamicCloseSchema = applyArgumentDescriptions(closeToolSchema, {
45
- ...closePreset.args,
46
- });
44
+ const dynamicCloseSchema = closeToolSchema;
47
45
  const dynamicStartSchema = applyArgumentDescriptions(startToolSchema, {
48
46
  ...startPreset.args,
49
47
  });
@@ -22,16 +22,22 @@ export const askToolSchema = z.object({
22
22
  name: z.string().optional(),
23
23
  cwd: z.string().optional(),
24
24
  });
25
- export const closeToolSchema = z.object({
26
- petition: z.enum(["CLAIM", "FORFEIT"]),
27
- criteriaChecks: z.array(z.string().trim().min(1)).min(1),
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),
34
- oath: z.string().optional(),
35
- cwd: z.string().optional(),
36
- });
25
+ export const closeToolSchema = z.discriminatedUnion("petition", [
26
+ z.object({
27
+ petition: z.literal("CLAIM"),
28
+ criteriaChecks: z.array(z.string().trim().min(1)).min(1),
29
+ constraintsChecks: z.array(z.string().trim().min(1)).min(1),
30
+ score_precise: z.number().min(0).max(10),
31
+ score_minimal: z.number().min(0).max(10),
32
+ score_isolated: z.number().min(0).max(10),
33
+ score_idiomatic: z.number().min(0).max(10),
34
+ score_cohesive: z.number().min(0).max(10),
35
+ oath: z.string().optional(),
36
+ cwd: z.string().optional(),
37
+ }),
38
+ z.object({
39
+ petition: z.literal("FORFEIT"),
40
+ cwd: z.string().optional(),
41
+ }),
42
+ ]);
37
43
  export const helpToolSchema = z.object({});
@@ -265,6 +265,16 @@ export async function getKeiyakuBase(cwd, branchName) {
265
265
  return null;
266
266
  }
267
267
  }
268
+ export async function getLatestCommitHash(cwd) {
269
+ const git = createGit(cwd);
270
+ try {
271
+ const hash = await git.revparse(["--short", "HEAD"]);
272
+ return hash.trim();
273
+ }
274
+ catch (err) {
275
+ throw wrapGitError("rev-parse --short HEAD", err, cwd);
276
+ }
277
+ }
268
278
  export async function clearKeiyakuBase(cwd, branchName) {
269
279
  const git = createGit(cwd);
270
280
  try {
@@ -47,7 +47,6 @@ export async function driveServant(input) {
47
47
  const traceState = computeTraceState(traceContent);
48
48
  const title = keiyakuBranch.slice("keiyaku/".length);
49
49
  const goal = readGoalFromKeiyaku(keiyakuContent);
50
- const keiyakuContext = readKeiyakuSection(keiyakuContent, "Context");
51
50
  const constraints = readKeiyakuSection(keiyakuContent, "Constraints");
52
51
  const criteria = readKeiyakuSection(keiyakuContent, "Acceptance Criteria");
53
52
  const normalizedDirective = requireText("directive", directive);
@@ -74,7 +73,6 @@ export async function driveServant(input) {
74
73
  diff,
75
74
  summary,
76
75
  goal,
77
- context: keiyakuContext,
78
76
  constraints,
79
77
  criteria,
80
78
  currentBranch: keiyakuBranch,
@@ -97,8 +97,6 @@ function requireChecks(name, values) {
97
97
  }
98
98
  export async function presentWork(input) {
99
99
  const { cwd } = input;
100
- requireChecks("criteriaChecks", input.criteriaChecks);
101
- requireChecks("constraintsChecks", input.constraintsChecks);
102
100
  const isRepo = await git.isGitRepo(cwd);
103
101
  if (!isRepo) {
104
102
  throw new FlowError("NOT_GIT_REPO", `${cwd} is not a git repository`);
@@ -135,14 +133,17 @@ export async function presentWork(input) {
135
133
  status: "success",
136
134
  result: "dropped",
137
135
  round,
138
- currentBranch: keiyakuBranch,
136
+ currentBranch: baseBranch,
139
137
  baseBranch,
138
+ deletedBranch: keiyakuBranch,
140
139
  diff: "Forfeited without merge.",
141
140
  };
142
141
  }
143
142
  await ensureKeiyakuFiles(cwd);
144
143
  const traceContent = await readTraceContent(cwd);
145
144
  if (petition === "CLAIM") {
145
+ requireChecks("criteriaChecks", input.criteriaChecks);
146
+ requireChecks("constraintsChecks", input.constraintsChecks);
146
147
  const verdict = await resolveVerdictConfig(cwd);
147
148
  const failedCommandments = CLOSE_SCORE_FIELDS.flatMap((field) => {
148
149
  const score = input[field];
@@ -194,6 +195,7 @@ export async function presentWork(input) {
194
195
  console.error(invokeMergeLog);
195
196
  appendDebugLog(invokeMergeLog, { cwd, section: "script" });
196
197
  await git.merge(cwd, keiyakuBranch, message);
198
+ const mergedCommit = await git.getLatestCommitHash(cwd);
197
199
  const invokeFinalizeLog = `[CLAIM] Deleting merged branch '${keiyakuBranch}' and clearing metadata`;
198
200
  console.error(invokeFinalizeLog);
199
201
  appendDebugLog(invokeFinalizeLog, { cwd, section: "script" });
@@ -202,10 +204,13 @@ export async function presentWork(input) {
202
204
  const round = computeTraceState(traceContent).maxRound;
203
205
  return {
204
206
  status: "success",
205
- result: "done",
207
+ result: "merged",
206
208
  round,
207
- currentBranch: keiyakuBranch,
209
+ currentBranch: baseBranch,
208
210
  baseBranch,
211
+ mergedCommit,
212
+ mergedInto: baseBranch,
213
+ deletedBranch: keiyakuBranch,
209
214
  diff,
210
215
  };
211
216
  }
@@ -28,12 +28,12 @@ Round: 1 (initial implementation).
28
28
  - [Logic delta]: [Key architectural or logic shift]
29
29
 
30
30
  ## Aesthetics Gap
31
- Self-critique.
32
- - precise: [score/reasoning]
33
- - minimal: [score/reasoning]
34
- - isolated: [score/reasoning]
35
- - idiomatic: [score/reasoning]
36
- - cohesive: [score/reasoning]
31
+ Self-critique (The Delta). Focus only on what is missing or imperfect relative to the ideal. No scores.
32
+ - precise (Architectural placement): [Gap from exact layer/boundary, zero misplacement]
33
+ - minimal (Economy of change): [Gap from no avoidable lines, no speculative edits, no hidden bloat]
34
+ - isolated (Surgical containment): [Gap from zero unrelated files, zero opportunistic cleanup, zero collateral]
35
+ - idiomatic (Native fluency): [Gap from naming, structure, style indistinguishable from the codebase]
36
+ - cohesive (Single responsibility): [Gap from each unit does one thing, boundaries intact]
37
37
 
38
38
  ## Blindspots
39
39
  - [Unintentional side effects or untested paths]
@@ -70,12 +70,12 @@ Round: ${round} (iteration after review).
70
70
  - [Logic delta]: [Key architectural or logic shift]
71
71
 
72
72
  ## Aesthetics Gap
73
- Self-critique.
74
- - precise: [score/reasoning]
75
- - minimal: [score/reasoning]
76
- - isolated: [score/reasoning]
77
- - idiomatic: [score/reasoning]
78
- - cohesive: [score/reasoning]
73
+ Self-critique (The Delta). Focus only on what is missing or imperfect relative to the ideal. No scores.
74
+ - precise (Architectural placement): [Gap from exact layer/boundary, zero misplacement]
75
+ - minimal (Economy of change): [Gap from no avoidable lines, no speculative edits, no hidden bloat]
76
+ - isolated (Surgical containment): [Gap from zero unrelated files, zero opportunistic cleanup, zero collateral]
77
+ - idiomatic (Native fluency): [Gap from naming, structure, style indistinguishable from the codebase]
78
+ - cohesive (Single responsibility): [Gap from each unit does one thing, boundaries intact]
79
79
 
80
80
  ## Blindspots
81
81
  - [Unintentional side effects or untested paths]
@@ -146,7 +146,6 @@ export function buildDriveResponse(result, input) {
146
146
  const { status: _status, ...resultData } = result;
147
147
  const summarySection = buildSection("Summary", result.summary);
148
148
  const goalSection = buildSection("Goal", truncateForDisplay(result.goal, 800));
149
- const contextSection = buildSection("Context", truncateForDisplay(result.context ?? "", 1200));
150
149
  const constraintsSection = buildSection("Constraints", truncateForDisplay(result.constraints ?? "", 1200));
151
150
  const criteriaSection = buildSection("Criteria", truncateForDisplay(result.criteria ?? "", 1200));
152
151
  const diffSection = buildSection("Diff", result.diff);
@@ -156,7 +155,7 @@ export function buildDriveResponse(result, input) {
156
155
  ...formatMaybe("Current Branch", result.currentBranch, 200),
157
156
  ...formatMaybe("Base Branch", result.baseBranch, 200),
158
157
  ];
159
- const text = assembleResponse(`◆ Driven (Round ${result.round})`, `Updated branch '${result.currentBranch}'.`, [summarySection, goalSection, contextSection, constraintsSection, criteriaSection, diffSection].filter((section) => section !== null), infoLines);
158
+ const text = assembleResponse(`◆ Driven (Round ${result.round})`, `Updated branch '${result.currentBranch}'.`, [summarySection, goalSection, constraintsSection, criteriaSection, diffSection].filter((section) => section !== null), infoLines);
160
159
  return {
161
160
  content: [{ type: "text", text }],
162
161
  structuredContent: buildSuccessStructuredContent(getDriveToolName(), resultData),
@@ -181,6 +180,10 @@ export function buildCloseDoneResponse(result, input) {
181
180
  const diffSection = result.diff ? buildSection("Diff", result.diff) : "";
182
181
  const infoLines = [
183
182
  ...formatMaybe("Path", input.cwd, 300),
183
+ ...formatMaybe("Result", result.result, 100),
184
+ ...formatMaybe("Merged Commit", result.mergedCommit, 100),
185
+ ...formatMaybe("Merged Into", result.mergedInto, 200),
186
+ ...formatMaybe("Deleted Branch", result.deletedBranch, 200),
184
187
  ...formatMaybe("Current Branch", result.currentBranch, 200),
185
188
  ...formatMaybe("Base Branch", result.baseBranch, 200),
186
189
  ...formatList("Criteria Checks", input.criteriaChecks, { maxItems: 10, maxItemChars: 220 }),
@@ -188,7 +191,7 @@ export function buildCloseDoneResponse(result, input) {
188
191
  `Scores: precise=${input.score_precise}/10 minimal=${input.score_minimal}/10 isolated=${input.score_isolated}/10 idiomatic=${input.score_idiomatic}/10 cohesive=${input.score_cohesive}/10`,
189
192
  ...formatMaybe("Oath", input.oath, 220),
190
193
  ];
191
- const text = assembleResponse("✓ Keiyaku Fulfilled (CLAIM)", `Merged '${result.currentBranch}' into '${result.baseBranch}'. Deleted feature branch.`, [typeof diffSection === "string" ? null : diffSection].filter((section) => section !== null), infoLines);
194
+ const text = assembleResponse("✓ Keiyaku Fulfilled (CLAIM)", `Merged '${result.deletedBranch}' into '${result.mergedInto}' (commit: ${result.mergedCommit}). Deleted feature branch.`, [typeof diffSection === "string" ? null : diffSection].filter((section) => section !== null), infoLines);
192
195
  return {
193
196
  content: [{ type: "text", text }],
194
197
  structuredContent: buildSuccessStructuredContent(closeToolName, resultData),
@@ -199,23 +202,19 @@ export function buildCloseDropResponse(result, input) {
199
202
  const closeToolName = getCloseToolName();
200
203
  const infoLines = [
201
204
  ...formatMaybe("Path", input.cwd, 300),
205
+ ...formatMaybe("Deleted Branch", result.deletedBranch, 200),
202
206
  ...formatMaybe("Current Branch", result.currentBranch, 200),
203
207
  ...formatMaybe("Base Branch", result.baseBranch, 200),
204
- ...formatList("Reasons/Checks", input.criteriaChecks, { maxItems: 10, maxItemChars: 220 }),
205
- ...formatList("Constraints Checks", input.constraintsChecks ?? [], { maxItems: 10, maxItemChars: 220 }),
206
- `Scores: precise=${input.score_precise}/10 minimal=${input.score_minimal}/10 isolated=${input.score_isolated}/10 idiomatic=${input.score_idiomatic}/10 cohesive=${input.score_cohesive}/10`,
207
208
  ];
208
- const text = assembleResponse("✗ Keiyaku Forfeited (FORFEIT)", `Deleted '${result.currentBranch}'. Switched back to '${result.baseBranch}'.`, [], infoLines);
209
+ const text = assembleResponse("✗ Keiyaku Forfeited (FORFEIT)", `Deleted '${result.deletedBranch}'. Switched back to '${result.baseBranch}'.`, [], infoLines);
209
210
  return {
210
211
  content: [{ type: "text", text }],
211
212
  structuredContent: buildSuccessStructuredContent(closeToolName, resultData),
212
213
  };
213
214
  }
214
215
  export function buildToolErrorResponse(input) {
215
- const inputEcho = (input.inputEcho ?? []).map((line) => truncateForDisplay(line, 800));
216
216
  const shouldRaiseProtocolError = input.errorType === "runtime_error";
217
- const contextSection = buildSection("Context", inputEcho);
218
- const text = assembleResponse(colorizeErrorStatus("!! FAILED !!"), input.message, [contextSection].filter((section) => section !== null), [`Hint: ${input.hint}`]);
217
+ const text = assembleResponse(colorizeErrorStatus("!! FAILED !!"), input.message, [], [`Hint: ${input.hint}`]);
219
218
  const response = {
220
219
  content: [{ type: "text", text }],
221
220
  structuredContent: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/keiyaku",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "MCP server for running iterative keiyaku workflows with Codex subagents.",
5
5
  "license": "MIT",
6
6
  "type": "module",