@astrosheep/keiyaku 0.1.4 → 0.1.5
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/build/common/errors.js +76 -83
- package/build/config/term-presets.js +29 -29
- package/build/handlers/ask.js +9 -16
- package/build/handlers/close.js +10 -15
- package/build/handlers/drive.js +9 -16
- package/build/handlers/help.js +1 -21
- package/build/handlers/shared.js +17 -1
- package/build/handlers/start.js +12 -23
- package/build/index.js +2 -5
- package/build/types/tool-schemas.js +5 -5
- package/build/utils/git.js +12 -2
- package/build/workflow/oath.js +1 -1
- package/build/workflow/orchestrator.js +67 -18
- package/build/workflow/prompts.js +2 -2
- package/build/workflow/response-builders.js +2 -5
- package/package.json +1 -1
package/build/common/errors.js
CHANGED
|
@@ -28,92 +28,85 @@ export function wrapFlowError(action, err) {
|
|
|
28
28
|
}
|
|
29
29
|
return new Error(`${action} failed: ${asMessage(err)}`);
|
|
30
30
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return unknownSubagentHint();
|
|
64
|
-
default:
|
|
65
|
-
return "Review the error details, fix the issue, and retry.";
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
// Fallback for untyped runtime errors.
|
|
69
|
-
if (message.includes("is not a git repository")) {
|
|
70
|
-
return "The provided `cwd` is not a git repository.";
|
|
71
|
-
}
|
|
72
|
-
if (message.includes("active keiyaku already exists")) {
|
|
73
|
-
return "An active keiyaku branch already exists in this repository.";
|
|
74
|
-
}
|
|
75
|
-
if (message.includes("existing keiyaku branch found")) {
|
|
76
|
-
return "At least one local `keiyaku/*` branch already exists in this repository.";
|
|
77
|
-
}
|
|
78
|
-
if (message.includes("parameter") && message.includes("cannot be empty")) {
|
|
79
|
-
return "One or more required parameters are empty.";
|
|
80
|
-
}
|
|
81
|
-
if (message.includes("working tree has uncommitted changes")) {
|
|
82
|
-
return "The working tree has uncommitted changes.";
|
|
83
|
-
}
|
|
84
|
-
if (message.includes("current branch is not an active keiyaku branch")) {
|
|
85
|
-
return "No active keiyaku branch (`keiyaku/*`). If this task is not using keiyaku workflow, skip this tool call.";
|
|
86
|
-
}
|
|
87
|
-
if (message.includes("is missing base metadata")) {
|
|
88
|
-
return "Current keiyaku branch is missing `keiyakuBase` metadata.";
|
|
31
|
+
function hintForFlowCode(code, message) {
|
|
32
|
+
switch (code) {
|
|
33
|
+
case "NOT_GIT_REPO":
|
|
34
|
+
return "The provided `cwd` is not a git repository.";
|
|
35
|
+
case "ACTIVE_KEIYAKU_EXISTS":
|
|
36
|
+
return "An active keiyaku branch already exists in this repository. Continue with `drive`, or use `close` + `ABANDON` to drop it.";
|
|
37
|
+
case "EXISTING_KEIYAKU_BRANCH_FOUND":
|
|
38
|
+
return "At least one local `keiyaku/*` branch already exists in this repository.";
|
|
39
|
+
case "EMPTY_PARAM":
|
|
40
|
+
return "One or more required parameters are empty.";
|
|
41
|
+
case "DIRTY_WORKTREE":
|
|
42
|
+
return "The working tree has uncommitted changes.";
|
|
43
|
+
case "NOT_ACTIVE_KEIYAKU_BRANCH":
|
|
44
|
+
return message;
|
|
45
|
+
case "MISSING_KEIYAKU_BASE":
|
|
46
|
+
return "Current keiyaku branch is missing `keiyakuBase` metadata.";
|
|
47
|
+
case "MISSING_PROTOCOL_FILES":
|
|
48
|
+
return "Required protocol files are missing (`KEIYAKU.md` or `KEIYAKU_TRACE.md`).";
|
|
49
|
+
case "DONE_MERGE_CONFLICT":
|
|
50
|
+
return "DONE encountered a git merge conflict.";
|
|
51
|
+
case "CLOSE_QUALITY_GATE_FAILED":
|
|
52
|
+
return "INVOKE was denied by Divine Judgment because one or more commandment thresholds or the minimum total score were not met.";
|
|
53
|
+
case "OATH_MISMATCH":
|
|
54
|
+
return message;
|
|
55
|
+
case "SUBAGENT_DID_NOT_ADVANCE_ROUND":
|
|
56
|
+
return "Subagent run did not append the expected new round in `KEIYAKU_TRACE.md`.";
|
|
57
|
+
case "ROUND_SUBAGENT_FAILED":
|
|
58
|
+
return "Subagent execution failed. Review KEIYAKU_TRACE.md for details, then continue with a narrower directive.";
|
|
59
|
+
case "INVALID_BRANCH_TITLE":
|
|
60
|
+
return "Title cannot be converted to a valid branch token for `keiyaku/<title>`.";
|
|
61
|
+
case "UNKNOWN_SUBAGENT":
|
|
62
|
+
return unknownSubagentHint();
|
|
89
63
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
64
|
+
}
|
|
65
|
+
const MESSAGE_HINT_PATTERNS = [
|
|
66
|
+
{ code: "NOT_GIT_REPO", patterns: ["is not a git repository"] },
|
|
67
|
+
{ code: "ACTIVE_KEIYAKU_EXISTS", patterns: ["active keiyaku already exists"] },
|
|
68
|
+
{ code: "EXISTING_KEIYAKU_BRANCH_FOUND", patterns: ["existing keiyaku branch found"] },
|
|
69
|
+
{ code: "EMPTY_PARAM", patterns: ["cannot be empty"] },
|
|
70
|
+
{ code: "DIRTY_WORKTREE", patterns: ["working tree has uncommitted changes"] },
|
|
71
|
+
{ code: "NOT_ACTIVE_KEIYAKU_BRANCH", patterns: ["current branch is not an active keiyaku branch"] },
|
|
72
|
+
{ code: "MISSING_KEIYAKU_BASE", patterns: ["is missing base metadata"] },
|
|
73
|
+
{ code: "MISSING_PROTOCOL_FILES", patterns: ["missing protocol files"] },
|
|
74
|
+
{ code: "DONE_MERGE_CONFLICT", patterns: ["DONE merge conflict"] },
|
|
75
|
+
{ code: "CLOSE_QUALITY_GATE_FAILED", patterns: ["God's Wrath: INVOKE denied"] },
|
|
76
|
+
{
|
|
77
|
+
code: "OATH_MISMATCH",
|
|
78
|
+
patterns: [
|
|
79
|
+
"requires the sacred oath to exactly equal",
|
|
80
|
+
"requires oath to exactly match configured value",
|
|
81
|
+
"requires oath to match configured value. If template contains",
|
|
82
|
+
"To declare DONE, you must solemnly swear the sacred oath.",
|
|
83
|
+
"To declare DONE, oath mismatch.",
|
|
84
|
+
"Oath mismatch.",
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
code: "SUBAGENT_DID_NOT_ADVANCE_ROUND",
|
|
89
|
+
patterns: ["subagent did not advance round", "did not append KEIYAKU_TRACE"],
|
|
90
|
+
},
|
|
91
|
+
{ code: "ROUND_SUBAGENT_FAILED", patterns: ["failed during subagent execution"] },
|
|
92
|
+
{ code: "INVALID_BRANCH_TITLE", patterns: ["cannot be converted to a valid branch name"] },
|
|
93
|
+
{ code: "UNKNOWN_SUBAGENT", patterns: ["Unknown subagent"] },
|
|
94
|
+
];
|
|
95
|
+
function inferFlowCodeFromMessage(message) {
|
|
96
|
+
for (const entry of MESSAGE_HINT_PATTERNS) {
|
|
97
|
+
if (entry.patterns.some((pattern) => message.includes(pattern))) {
|
|
98
|
+
return entry.code;
|
|
99
|
+
}
|
|
111
100
|
}
|
|
112
|
-
|
|
113
|
-
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
export function pickHintFromError(err, message) {
|
|
104
|
+
if (isFlowError(err)) {
|
|
105
|
+
return hintForFlowCode(err.code, err.message);
|
|
114
106
|
}
|
|
115
|
-
|
|
116
|
-
|
|
107
|
+
const inferredCode = inferFlowCodeFromMessage(message);
|
|
108
|
+
if (inferredCode) {
|
|
109
|
+
return hintForFlowCode(inferredCode, message);
|
|
117
110
|
}
|
|
118
111
|
return "Review the error details, fix the issue, and retry.";
|
|
119
112
|
}
|
|
@@ -52,37 +52,37 @@ export const DEFAULT_PRESET = {
|
|
|
52
52
|
start: {
|
|
53
53
|
name: 'summon',
|
|
54
54
|
title: 'Sign Keiyaku',
|
|
55
|
-
description: 'Sign the Keiyaku.
|
|
55
|
+
description: 'Sign the Keiyaku and assign the mission to a servant. This creates a dedicated branch where the servant will labor until the goal is met.\nCall ONCE per task to start the cycle. Workspace must be clean.\n\nFlow: summon → [drive x N] → request_verdict',
|
|
56
56
|
args: {
|
|
57
57
|
title: 'REQUIRED. A concise codename for this hunt.',
|
|
58
|
-
goal: 'REQUIRED. The Kill Condition. State exactly what success looks like
|
|
59
|
-
directive: 'Optional Round 1 Focus. Use
|
|
60
|
-
context: 'REQUIRED. Mission Intel. The complete knowledge base: current vs. expected behavior, relevant file paths, error logs, and any critical background info.',
|
|
58
|
+
goal: 'REQUIRED. The Kill Condition. State exactly what success looks like for the servant to achieve.',
|
|
59
|
+
directive: 'Optional Round 1 Focus. Use to leash the servant to a specific starting point. Skip for simple tasks.',
|
|
60
|
+
context: 'REQUIRED. Mission Intel. The complete knowledge base for the servant: current vs. expected behavior, relevant file paths, error logs, and any critical background info.',
|
|
61
61
|
constraints: 'REQUIRED. Non-negotiable Rules. Architectural and stylistic boundaries the servant must obey.',
|
|
62
|
-
criteria: 'REQUIRED. Acceptance Criteria. Verifiable checks to prove the
|
|
63
|
-
name: 'Optional ${IDENTITY} profile
|
|
62
|
+
criteria: 'REQUIRED. Acceptance Criteria. Verifiable checks to prove the servant has finished the job.',
|
|
63
|
+
name: 'Optional ${IDENTITY} profile to execute this mission. Presets: ${PRESET_IDENTITIES}.',
|
|
64
64
|
cwd: "Optional repository path. Defaults to the server's current working directory.",
|
|
65
65
|
},
|
|
66
66
|
},
|
|
67
67
|
drive: {
|
|
68
68
|
name: 'drive',
|
|
69
69
|
title: 'Iterate',
|
|
70
|
-
description: "
|
|
70
|
+
description: "Direct the active servant's next step. Provide feedback or new orders for the servant to implement in the current branch.\nMANDATORY: Review the servant's work (git diff) before iterating.\n\nFlow: summon → [drive x N] → request_verdict",
|
|
71
71
|
args: {
|
|
72
|
-
directive: 'REQUIRED. The Next Order. Precise instructions for
|
|
73
|
-
context: 'Optional. New Intel. New error logs
|
|
74
|
-
name: 'Optional ${IDENTITY} profile
|
|
72
|
+
directive: 'REQUIRED. The Next Order. Precise instructions for the servant. Can be a correction ("fix the leak") or a continuation ("now add the tests").',
|
|
73
|
+
context: 'Optional. New Intel. New error logs or details discovered after the servant\'s last strike.',
|
|
74
|
+
name: 'Optional ${IDENTITY} profile to process this turn. Presets: ${PRESET_IDENTITIES}.',
|
|
75
75
|
cwd: "Optional repository path. Defaults to the server's current working directory.",
|
|
76
76
|
},
|
|
77
77
|
},
|
|
78
78
|
ask: {
|
|
79
79
|
name: 'ask',
|
|
80
80
|
title: 'Ask',
|
|
81
|
-
description: '
|
|
81
|
+
description: 'Delegate a quick task or analysis to a servant. Use for scouting, strategic consultation, or one-off tasks (like scripts or docs) where you want the servant to handle it without a full keiyaku.',
|
|
82
82
|
args: {
|
|
83
|
-
request: 'REQUIRED. The
|
|
84
|
-
context: 'REQUIRED. Relevant background or data
|
|
85
|
-
name: 'Optional ${IDENTITY} profile
|
|
83
|
+
request: 'REQUIRED. The task, question, or mission to delegate to the servant.',
|
|
84
|
+
context: 'REQUIRED. Relevant background or data the servant needs to execute the request.',
|
|
85
|
+
name: 'Optional ${IDENTITY} profile to perform this task. Presets: ${PRESET_IDENTITIES}.',
|
|
86
86
|
cwd: "Optional repository path. Defaults to the server's current working directory.",
|
|
87
87
|
},
|
|
88
88
|
},
|
|
@@ -93,11 +93,11 @@ export const DEFAULT_PRESET = {
|
|
|
93
93
|
args: {
|
|
94
94
|
petition: 'REQUIRED. INVOKE begs acceptance; ABANDON confesses surrender.\nREQUIRES AN ACTIVE KEIYAKU (started via ${START_TOOL_NAME}).\nIf scores are weak, return to ${DRIVE_TOOL_NAME}.',
|
|
95
95
|
criteriaChecks: 'REQUIRED. Evidence for INVOKE, or explicit reasons for ABANDON.',
|
|
96
|
-
score_precise: 'REQUIRED score (0-5). 5 means
|
|
97
|
-
score_minimal: 'REQUIRED score (0-5). 5 means no
|
|
98
|
-
score_isolated: 'REQUIRED score (0-5). 5 means unrelated
|
|
99
|
-
score_idiomatic: 'REQUIRED score (0-5). 5 means naming and
|
|
100
|
-
score_cohesive: 'REQUIRED score (0-5). 5 means
|
|
96
|
+
score_precise: 'REQUIRED score (0-5). 5 means architecturally flawless placement: exact layer, exact boundary, zero meaningful misplacement risk.',
|
|
97
|
+
score_minimal: 'REQUIRED score (0-5). 5 means ruthlessly minimal: no avoidable lines, no speculative edits, no hidden bloat.',
|
|
98
|
+
score_isolated: 'REQUIRED score (0-5). 5 means surgically isolated: zero unrelated files, zero opportunistic cleanup, zero collateral change.',
|
|
99
|
+
score_idiomatic: 'REQUIRED score (0-5). 5 means idiomatically perfect: naming, structure, and style are native-quality with no visible friction.',
|
|
100
|
+
score_cohesive: 'REQUIRED score (0-5). 5 means cohesion is pristine: each unit has one clear responsibility and boundaries are uncompromised.',
|
|
101
101
|
oath: 'Sacred confession text. Required for INVOKE. Verbatim: ${OATH_TEXT}',
|
|
102
102
|
cwd: "Optional repository path. Defaults to the server's current working directory.",
|
|
103
103
|
},
|
|
@@ -189,11 +189,11 @@ export const POKEMON_PRESET = {
|
|
|
189
189
|
args: {
|
|
190
190
|
petition: 'REQUIRED. INVOKE seeks blessing; ABANDON retreats in humility.\nREQUIRES AN ACTIVE BATTLE (started via ${START_TOOL_NAME}).\nIf scores are unworthy, continue with ${DRIVE_TOOL_NAME}.',
|
|
191
191
|
criteriaChecks: 'REQUIRED. Badge-by-badge proof for INVOKE, or confession for ABANDON.',
|
|
192
|
-
score_precise: 'REQUIRED score (0-5). 5 means
|
|
193
|
-
score_minimal: 'REQUIRED score (0-5). 5 means no unnecessary moves
|
|
194
|
-
score_isolated: 'REQUIRED score (0-5). 5 means
|
|
195
|
-
score_idiomatic: "REQUIRED score (0-5). 5 means
|
|
196
|
-
score_cohesive: 'REQUIRED score (0-5). 5 means each action
|
|
192
|
+
score_precise: 'REQUIRED score (0-5). 5 means a flawless hit: exact layer, exact target, zero meaningful misplacement.',
|
|
193
|
+
score_minimal: 'REQUIRED score (0-5). 5 means perfectly efficient: no unnecessary moves, no extra motion, no bloat.',
|
|
194
|
+
score_isolated: 'REQUIRED score (0-5). 5 means pure battle focus: zero side-quests, zero unrelated damage.',
|
|
195
|
+
score_idiomatic: "REQUIRED score (0-5). 5 means doctrine-perfect execution: reads exactly like the team's strongest native style.",
|
|
196
|
+
score_cohesive: 'REQUIRED score (0-5). 5 means immaculate role clarity: each action serves one purpose with clean boundaries.',
|
|
197
197
|
oath: "Trainer's sacred confession. Required for INVOKE. Verbatim: ${OATH_TEXT}",
|
|
198
198
|
cwd: 'Optional battlefield path (repository root). Defaults to current arena.',
|
|
199
199
|
},
|
|
@@ -285,11 +285,11 @@ export const MISCHIEF_PRESET = {
|
|
|
285
285
|
args: {
|
|
286
286
|
petition: 'REQUIRED. INVOKE pleads for ascension; ABANDON confesses failure.\nREQUIRES AN ACTIVE SCHEME (started via ${START_TOOL_NAME}).\nIf scores are low, continue via ${DRIVE_TOOL_NAME}.',
|
|
287
287
|
criteriaChecks: 'REQUIRED. Proof of mastery for INVOKE, or explicit confession for ABANDON.',
|
|
288
|
-
score_precise: 'REQUIRED score (0-5). 5 means
|
|
289
|
-
score_minimal: 'REQUIRED score (0-5). 5 means no needless machinery
|
|
290
|
-
score_isolated: 'REQUIRED score (0-5). 5 means unrelated chaos
|
|
291
|
-
score_idiomatic: 'REQUIRED score (0-5). 5 means
|
|
292
|
-
score_cohesive: 'REQUIRED score (0-5). 5 means each unit bears one
|
|
288
|
+
score_precise: 'REQUIRED score (0-5). 5 means ordained precision without flaw: exact layer, exact cut, zero meaningful drift.',
|
|
289
|
+
score_minimal: 'REQUIRED score (0-5). 5 means ascetic minimality: no needless machinery, no decorative motion, no excess.',
|
|
290
|
+
score_isolated: 'REQUIRED score (0-5). 5 means perfect containment: unrelated chaos remains fully outside this diff.',
|
|
291
|
+
score_idiomatic: 'REQUIRED score (0-5). 5 means native-tongue perfection: style and structure match house doctrine without strain.',
|
|
292
|
+
score_cohesive: 'REQUIRED score (0-5). 5 means sacred cohesion: each unit bears exactly one duty with uncompromised boundaries.',
|
|
293
293
|
oath: "Architect's sacred confession. Required for INVOKE. Verbatim: ${OATH_TEXT}",
|
|
294
294
|
cwd: 'Optional lair path (repository root). Defaults to current command chamber.',
|
|
295
295
|
},
|
package/build/handlers/ask.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { appendDebugLog } from "../utils/debug-log.js";
|
|
2
|
-
import { asMessage, pickHintFromError } from "../common/errors.js";
|
|
3
2
|
import { handleAsk } from "../workflow/orchestrator.js";
|
|
4
|
-
import { buildAskResponse,
|
|
3
|
+
import { buildAskResponse, } from "../workflow/response-builders.js";
|
|
5
4
|
import { resolveTermPreset } from "../config/term-presets.js";
|
|
6
|
-
import {
|
|
5
|
+
import { handleToolError } from "./shared.js";
|
|
7
6
|
export function createAskHandler() {
|
|
8
7
|
return async ({ request, context, name, cwd }, extra) => {
|
|
9
8
|
const workingDir = cwd || process.cwd();
|
|
@@ -20,22 +19,16 @@ export function createAskHandler() {
|
|
|
20
19
|
return buildAskResponse(result, { request, context, name, cwd: workingDir });
|
|
21
20
|
}
|
|
22
21
|
catch (err) {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
tool: "ask",
|
|
30
|
-
title: "Ask",
|
|
31
|
-
message,
|
|
32
|
-
hint,
|
|
33
|
-
errorType,
|
|
34
|
-
errorCode,
|
|
22
|
+
const preset = resolveTermPreset();
|
|
23
|
+
return handleToolError({
|
|
24
|
+
error: err,
|
|
25
|
+
toolName: preset.tools.ask.name,
|
|
26
|
+
cwd: workingDir,
|
|
27
|
+
logLabel: "tool ask",
|
|
35
28
|
inputEcho: [
|
|
36
29
|
`Request: ${request}`,
|
|
37
30
|
`Context: ${context}`,
|
|
38
|
-
...(name ? [`${identity}: ${name}`] : []),
|
|
31
|
+
...(name ? [`${preset.identity}: ${name}`] : []),
|
|
39
32
|
`CWD: ${workingDir}`,
|
|
40
33
|
],
|
|
41
34
|
});
|
package/build/handlers/close.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { appendDebugLog } from "../utils/debug-log.js";
|
|
2
|
-
import { asMessage, pickHintFromError } from "../common/errors.js";
|
|
3
2
|
import { handleClose } from "../workflow/orchestrator.js";
|
|
4
|
-
import { buildCloseDoneResponse, buildCloseDropResponse,
|
|
5
|
-
import {
|
|
6
|
-
|
|
3
|
+
import { buildCloseDoneResponse, buildCloseDropResponse, } from "../workflow/response-builders.js";
|
|
4
|
+
import { resolveTermPreset } from "../config/term-presets.js";
|
|
5
|
+
import { handleToolError } from "./shared.js";
|
|
6
|
+
export function createCloseHandler() {
|
|
7
7
|
return async ({ petition, criteriaChecks, 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;
|
|
@@ -64,17 +64,12 @@ export function createCloseHandler(toolInfo) {
|
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
66
|
catch (err) {
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
title: toolInfo.title,
|
|
74
|
-
message,
|
|
75
|
-
hint,
|
|
76
|
-
errorType,
|
|
77
|
-
errorCode,
|
|
67
|
+
const preset = resolveTermPreset();
|
|
68
|
+
return handleToolError({
|
|
69
|
+
error: err,
|
|
70
|
+
toolName: preset.tools.close.name,
|
|
71
|
+
cwd: workingDir,
|
|
72
|
+
logLabel: "tool close",
|
|
78
73
|
inputEcho: [
|
|
79
74
|
`Petition: ${petition}`,
|
|
80
75
|
`Criteria checks (${criteriaCheckParts.length}): ${criteriaCheckParts.join("; ")}`,
|
package/build/handlers/drive.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { appendDebugLog } from "../utils/debug-log.js";
|
|
2
|
-
import { asMessage, pickHintFromError } from "../common/errors.js";
|
|
3
2
|
import { handleDrive } from "../workflow/orchestrator.js";
|
|
4
|
-
import { buildDriveResponse,
|
|
3
|
+
import { buildDriveResponse, } from "../workflow/response-builders.js";
|
|
5
4
|
import { resolveTermPreset } from "../config/term-presets.js";
|
|
6
|
-
import {
|
|
5
|
+
import { handleToolError } from "./shared.js";
|
|
7
6
|
export function createDriveHandler() {
|
|
8
7
|
return async ({ directive, context, name, cwd }, extra) => {
|
|
9
8
|
const workingDir = cwd || process.cwd();
|
|
@@ -26,22 +25,16 @@ export function createDriveHandler() {
|
|
|
26
25
|
return buildDriveResponse(outcome, { directive, context, name, cwd: workingDir });
|
|
27
26
|
}
|
|
28
27
|
catch (err) {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
tool: "drive",
|
|
36
|
-
title: "Drive",
|
|
37
|
-
message,
|
|
38
|
-
hint,
|
|
39
|
-
errorType,
|
|
40
|
-
errorCode,
|
|
28
|
+
const preset = resolveTermPreset();
|
|
29
|
+
return handleToolError({
|
|
30
|
+
error: err,
|
|
31
|
+
toolName: preset.tools.drive.name,
|
|
32
|
+
cwd: workingDir,
|
|
33
|
+
logLabel: "tool drive",
|
|
41
34
|
inputEcho: [
|
|
42
35
|
`Directive: ${directive}`,
|
|
43
36
|
...(context ? [`Context: ${context}`] : []),
|
|
44
|
-
...(name ? [`${identity}: ${name}`] : []),
|
|
37
|
+
...(name ? [`${preset.identity}: ${name}`] : []),
|
|
45
38
|
`CWD: ${workingDir}`,
|
|
46
39
|
],
|
|
47
40
|
});
|
package/build/handlers/help.js
CHANGED
|
@@ -1,28 +1,8 @@
|
|
|
1
|
-
import * as git from '../utils/git.js';
|
|
2
1
|
export function createHelpHandler(preset) {
|
|
3
2
|
return async ({ question }) => {
|
|
4
|
-
const workingDir = process.cwd();
|
|
5
|
-
let branchState = 'Unknown';
|
|
6
|
-
try {
|
|
7
|
-
const currentBranch = await git.getCurrentBranch(workingDir);
|
|
8
|
-
const activeKeiyaku = await git.getActiveKeiyakuBranch(workingDir);
|
|
9
|
-
if (activeKeiyaku) {
|
|
10
|
-
const base = await git.getKeiyakuBase(workingDir, activeKeiyaku);
|
|
11
|
-
branchState = `Active Keiyaku: ${activeKeiyaku}\n Base Branch: ${base || 'unknown'}`;
|
|
12
|
-
}
|
|
13
|
-
else {
|
|
14
|
-
branchState = `No active keiyaku. Current branch: ${currentBranch}`;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
catch {
|
|
18
|
-
branchState = 'Not a git repository or git error.';
|
|
19
|
-
}
|
|
20
3
|
const helpContent = [
|
|
21
4
|
'# Keiyaku System Help',
|
|
22
5
|
'',
|
|
23
|
-
'## Current State',
|
|
24
|
-
branchState,
|
|
25
|
-
'',
|
|
26
6
|
'## Core Files (.keiyaku/)',
|
|
27
7
|
"These files define the 'Law' of the project. **CRITICAL**: Use Markdown level 2 headers (## header).",
|
|
28
8
|
'',
|
|
@@ -39,7 +19,7 @@ export function createHelpHandler(preset) {
|
|
|
39
19
|
].join('\n');
|
|
40
20
|
return {
|
|
41
21
|
content: [{ type: 'text', text: helpContent }],
|
|
42
|
-
structuredContent: { tool:
|
|
22
|
+
structuredContent: { tool: preset.tools.help.name, status: 'success' },
|
|
43
23
|
};
|
|
44
24
|
};
|
|
45
25
|
}
|
package/build/handlers/shared.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { isSubagentExecError } from "../agents/index.js";
|
|
2
|
-
import { isFlowError } from "../common/errors.js";
|
|
2
|
+
import { asMessage, isFlowError, pickHintFromError } from "../common/errors.js";
|
|
3
|
+
import { appendDebugLog } from "../utils/debug-log.js";
|
|
4
|
+
import { buildToolErrorResponse } from "../workflow/response-builders.js";
|
|
3
5
|
export function classifyToolError(error) {
|
|
4
6
|
if (isFlowError(error)) {
|
|
5
7
|
return { errorType: "keiyaku_error", errorCode: error.code };
|
|
@@ -12,3 +14,17 @@ export function classifyToolError(error) {
|
|
|
12
14
|
}
|
|
13
15
|
return { errorType: "runtime_error", errorCode: "INTERNAL_ERROR" };
|
|
14
16
|
}
|
|
17
|
+
export function handleToolError(input) {
|
|
18
|
+
const message = asMessage(input.error);
|
|
19
|
+
appendDebugLog(`${input.logLabel} error: ${message}`, { cwd: input.cwd, section: "script" });
|
|
20
|
+
const hint = pickHintFromError(input.error, message);
|
|
21
|
+
const { errorType, errorCode } = classifyToolError(input.error);
|
|
22
|
+
return buildToolErrorResponse({
|
|
23
|
+
tool: input.toolName,
|
|
24
|
+
message,
|
|
25
|
+
hint,
|
|
26
|
+
errorType,
|
|
27
|
+
errorCode,
|
|
28
|
+
inputEcho: input.inputEcho,
|
|
29
|
+
});
|
|
30
|
+
}
|
package/build/handlers/start.js
CHANGED
|
@@ -1,25 +1,20 @@
|
|
|
1
1
|
import { appendDebugLog } from "../utils/debug-log.js";
|
|
2
|
-
import { asMessage, pickHintFromError } from "../common/errors.js";
|
|
3
2
|
import { handleStart } from "../workflow/orchestrator.js";
|
|
4
|
-
import { buildKeiyakuSuccessResponse,
|
|
3
|
+
import { buildKeiyakuSuccessResponse, } from "../workflow/response-builders.js";
|
|
5
4
|
import { resolveTermPreset } from "../config/term-presets.js";
|
|
6
|
-
import {
|
|
7
|
-
function
|
|
8
|
-
return criteria.map((item) => `- ${item}`).join("\n");
|
|
9
|
-
}
|
|
10
|
-
export function createStartHandler(toolName) {
|
|
5
|
+
import { handleToolError } from "./shared.js";
|
|
6
|
+
export function createStartHandler() {
|
|
11
7
|
return async ({ title, goal, directive, context, constraints, criteria, name, cwd }, extra) => {
|
|
12
8
|
const workingDir = cwd || process.cwd();
|
|
13
9
|
try {
|
|
14
10
|
appendDebugLog(`tool start cwd=${workingDir}`, { cwd: workingDir, section: "script" });
|
|
15
|
-
const finalContext = context?.trim() || "No additional context provided.";
|
|
16
11
|
const result = await handleStart({
|
|
17
12
|
cwd: workingDir,
|
|
18
13
|
title,
|
|
19
14
|
goal,
|
|
20
15
|
directive,
|
|
21
|
-
context
|
|
22
|
-
criteria
|
|
16
|
+
context,
|
|
17
|
+
criteria,
|
|
23
18
|
constraints,
|
|
24
19
|
name,
|
|
25
20
|
signal: extra.signal,
|
|
@@ -40,18 +35,12 @@ export function createStartHandler(toolName) {
|
|
|
40
35
|
});
|
|
41
36
|
}
|
|
42
37
|
catch (err) {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
tool: toolName,
|
|
50
|
-
title: "Keiyaku",
|
|
51
|
-
message,
|
|
52
|
-
hint,
|
|
53
|
-
errorType,
|
|
54
|
-
errorCode,
|
|
38
|
+
const preset = resolveTermPreset();
|
|
39
|
+
return handleToolError({
|
|
40
|
+
error: err,
|
|
41
|
+
toolName: preset.tools.start.name,
|
|
42
|
+
cwd: workingDir,
|
|
43
|
+
logLabel: "tool start",
|
|
55
44
|
inputEcho: [
|
|
56
45
|
`Title: ${title}`,
|
|
57
46
|
`Goal: ${goal}`,
|
|
@@ -59,7 +48,7 @@ export function createStartHandler(toolName) {
|
|
|
59
48
|
`Criteria (${criteria.length}): ${criteria.join("; ")}`,
|
|
60
49
|
...(context ? [`Context: ${context}`] : []),
|
|
61
50
|
...(constraints ? [`Constraints: ${constraints}`] : []),
|
|
62
|
-
...(name ? [`${identity}: ${name}`] : []),
|
|
51
|
+
...(name ? [`${preset.identity}: ${name}`] : []),
|
|
63
52
|
`CWD: ${workingDir}`,
|
|
64
53
|
],
|
|
65
54
|
});
|
package/build/index.js
CHANGED
|
@@ -62,7 +62,7 @@ function registerTools(server) {
|
|
|
62
62
|
title: startPreset.title,
|
|
63
63
|
description: dynamicStartDescription,
|
|
64
64
|
inputSchema: dynamicStartSchema,
|
|
65
|
-
}, createStartHandler(
|
|
65
|
+
}, createStartHandler());
|
|
66
66
|
server.registerTool(drivePreset.name, {
|
|
67
67
|
title: drivePreset.title,
|
|
68
68
|
description: dynamicDriveDescription,
|
|
@@ -77,10 +77,7 @@ function registerTools(server) {
|
|
|
77
77
|
title: closePreset.title,
|
|
78
78
|
description: dynamicCloseDescription,
|
|
79
79
|
inputSchema: dynamicCloseSchema,
|
|
80
|
-
}, createCloseHandler(
|
|
81
|
-
name: closePreset.name,
|
|
82
|
-
title: closePreset.title,
|
|
83
|
-
}));
|
|
80
|
+
}, createCloseHandler());
|
|
84
81
|
server.registerTool(helpPreset.name, {
|
|
85
82
|
title: helpPreset.title,
|
|
86
83
|
description: [dynamicHelpDescription, dynamicHelpUsageGuide].join("\n\n"),
|
|
@@ -24,11 +24,11 @@ export const askToolSchema = z.object({
|
|
|
24
24
|
export const closeToolSchema = z.object({
|
|
25
25
|
petition: z.enum(["INVOKE", "ABANDON"]),
|
|
26
26
|
criteriaChecks: z.array(z.string().trim().min(1)).min(1),
|
|
27
|
-
score_precise: z.number().
|
|
28
|
-
score_minimal: z.number().
|
|
29
|
-
score_isolated: z.number().
|
|
30
|
-
score_idiomatic: z.number().
|
|
31
|
-
score_cohesive: z.number().
|
|
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),
|
|
32
32
|
oath: z.string().optional(),
|
|
33
33
|
cwd: z.string().optional(),
|
|
34
34
|
});
|
package/build/utils/git.js
CHANGED
|
@@ -362,8 +362,18 @@ export async function getIncrementalDiff(cwd) {
|
|
|
362
362
|
]);
|
|
363
363
|
}
|
|
364
364
|
catch (err) {
|
|
365
|
-
|
|
366
|
-
|
|
365
|
+
const source = (err ?? {});
|
|
366
|
+
const text = [source.message, source.stderr, source.stdErr, source.stdout, source.stdOut]
|
|
367
|
+
.filter((value) => typeof value === "string" && value.length > 0)
|
|
368
|
+
.join("\n");
|
|
369
|
+
const isMissingBaseCommit = text.includes("bad revision") ||
|
|
370
|
+
text.includes("unknown revision or path not in the working tree") ||
|
|
371
|
+
text.includes("ambiguous argument");
|
|
372
|
+
if (isMissingBaseCommit) {
|
|
373
|
+
// If there's no HEAD~1 (e.g. first commit), fallback
|
|
374
|
+
return "No incremental diff available.";
|
|
375
|
+
}
|
|
376
|
+
throw wrapGitError(`diff --no-color --no-ext-diff --unified=3 ${range}`, err, cwd);
|
|
367
377
|
}
|
|
368
378
|
const sections = splitDiffByFile(rawPatch);
|
|
369
379
|
if (sections.length === 0)
|
package/build/workflow/oath.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const DEFAULT_OATH = "I, [your name], solemnly swear that I have scrutinized this change line by line with my own eyes. This oath is handwritten as a testament to my responsibility. If logic fails where I claimed it sound, I shall bear the mark of this oversight forever.\n\nSigned: [your name]";
|
|
1
|
+
const DEFAULT_OATH = "I, [your name], solemnly swear that I have scrutinized this change line by line with my own eyes. I further swear that every score I submit is fully objective, with no exaggeration whatsoever. This oath is handwritten as a testament to my responsibility. If logic fails where I claimed it sound, I shall bear the mark of this oversight forever.\n\nSigned: [your name]";
|
|
2
2
|
const OATH_ENV_KEY = "KEIYAKU_CLOSE_OATH";
|
|
3
3
|
const LEGACY_CLOSE_OATH_ENV_KEY = "KEIYAKU_JUDGMENT_OATH";
|
|
4
4
|
const LEGACY_OATH_ENV_KEY = "KEIYAKU_SEAL_OATH";
|
|
@@ -36,6 +36,7 @@ const JUDGMENT_SETTINGS_FILE = path.join(".keiyaku", "settings.json");
|
|
|
36
36
|
export { resolveOath };
|
|
37
37
|
const BASE_CRITERIA_FILE = path.join(".keiyaku", "base-criteria.md");
|
|
38
38
|
const BASE_CONSTRAINTS_FILE = path.join(".keiyaku", "base-constraints.md");
|
|
39
|
+
const ACTIVE_KEIYAKU_PREVIEW_MAX_CHARS = 8000;
|
|
39
40
|
function clampThreshold(value, fallback) {
|
|
40
41
|
if (typeof value !== "number" || !Number.isFinite(value))
|
|
41
42
|
return fallback;
|
|
@@ -52,6 +53,55 @@ function clampMinTotalScore(value, fallback) {
|
|
|
52
53
|
return fallback;
|
|
53
54
|
return candidate;
|
|
54
55
|
}
|
|
56
|
+
function truncateForMessage(text, maxChars) {
|
|
57
|
+
if (text.length <= maxChars)
|
|
58
|
+
return text;
|
|
59
|
+
return `${text.slice(0, maxChars)}\n...[truncated ${text.length - maxChars} chars]...`;
|
|
60
|
+
}
|
|
61
|
+
async function buildActiveKeiyakuStartMessage(cwd, branch) {
|
|
62
|
+
const lines = [
|
|
63
|
+
`active keiyaku already exists (${branch})`,
|
|
64
|
+
"This task is still active. Decide whether to continue or abandon it.",
|
|
65
|
+
"Continue: run drive on the current keiyaku branch.",
|
|
66
|
+
"Abandon: run close with petition=ABANDON to drop the branch.",
|
|
67
|
+
];
|
|
68
|
+
try {
|
|
69
|
+
const keiyakuPath = path.join(cwd, KEIYAKU_FILE);
|
|
70
|
+
const keiyaku = await fs.readFile(keiyakuPath, "utf-8");
|
|
71
|
+
const preview = truncateForMessage(keiyaku.trim(), ACTIVE_KEIYAKU_PREVIEW_MAX_CHARS);
|
|
72
|
+
lines.push("", `[Current ${KEIYAKU_FILE}]`, preview || "(empty file)");
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error?.code === "ENOENT") {
|
|
76
|
+
lines.push("", `${KEIYAKU_FILE} not found on current branch.`);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const trace = await readTraceContent(cwd);
|
|
84
|
+
const traceState = computeTraceState(trace);
|
|
85
|
+
if (traceState.pendingReviewRound !== null) {
|
|
86
|
+
lines.push(`Trace status: pending review ${traceState.pendingReviewRound}; latest completed round ${traceState.maxRound}.`);
|
|
87
|
+
}
|
|
88
|
+
else if (traceState.maxRound > 0) {
|
|
89
|
+
lines.push(`Trace status: completed through round ${traceState.maxRound}; no pending review.`);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
lines.push("Trace status: no completed rounds yet.");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (error?.code === "ENOENT") {
|
|
97
|
+
lines.push(`${TRACE_FILE} not found on current branch.`);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return lines.join("\n");
|
|
104
|
+
}
|
|
55
105
|
async function resolveJudgmentConfig(cwd) {
|
|
56
106
|
const defaults = resolveTermPreset().divineJudgment;
|
|
57
107
|
const merged = {
|
|
@@ -239,9 +289,14 @@ export async function handleStart(input) {
|
|
|
239
289
|
const finalTitle = requireText("title", input.title);
|
|
240
290
|
const finalGoal = requireText("goal", input.goal);
|
|
241
291
|
const finalContext = requireText("context", input.context);
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
292
|
+
if (input.criteria.length === 0) {
|
|
293
|
+
throw new FlowError("EMPTY_PARAM", "parameter 'criteria' cannot be empty");
|
|
294
|
+
}
|
|
295
|
+
const normalizedTaskCriteria = input.criteria.flatMap((criterion, index) => {
|
|
296
|
+
const normalizedCriterion = requireText(`criteria[${index}]`, criterion);
|
|
297
|
+
const parsed = parseMarkdownHeaders(normalizedCriterion);
|
|
298
|
+
return parsed.length > 0 ? parsed : [normalizedCriterion];
|
|
299
|
+
});
|
|
245
300
|
const finalConstraints = input.constraints?.trim();
|
|
246
301
|
const taskConstraints = finalConstraints ? parseMarkdownHeaders(finalConstraints) : [];
|
|
247
302
|
const normalizedTaskConstraints = finalConstraints && taskConstraints.length > 0 ? taskConstraints : finalConstraints ? [finalConstraints] : [];
|
|
@@ -252,7 +307,7 @@ export async function handleStart(input) {
|
|
|
252
307
|
}
|
|
253
308
|
const existingKeiyaku = await git.getActiveKeiyakuBranch(cwd);
|
|
254
309
|
if (existingKeiyaku) {
|
|
255
|
-
throw new FlowError("ACTIVE_KEIYAKU_EXISTS",
|
|
310
|
+
throw new FlowError("ACTIVE_KEIYAKU_EXISTS", await buildActiveKeiyakuStartMessage(cwd, existingKeiyaku));
|
|
256
311
|
}
|
|
257
312
|
const existingBranches = await git.listLocalKeiyakuBranches(cwd);
|
|
258
313
|
if (existingBranches.length > 0) {
|
|
@@ -306,7 +361,6 @@ export async function handleStart(input) {
|
|
|
306
361
|
});
|
|
307
362
|
const trace = await fs.readFile(path.join(cwd, TRACE_FILE), "utf-8");
|
|
308
363
|
const diff = await git.getIncrementalDiff(cwd);
|
|
309
|
-
const content = `Round ${1} complete on ${keiyakuBranch}.`;
|
|
310
364
|
return {
|
|
311
365
|
status: "success",
|
|
312
366
|
round: 1,
|
|
@@ -315,7 +369,6 @@ export async function handleStart(input) {
|
|
|
315
369
|
summary,
|
|
316
370
|
branch: keiyakuBranch,
|
|
317
371
|
baseBranch,
|
|
318
|
-
content,
|
|
319
372
|
};
|
|
320
373
|
}
|
|
321
374
|
catch (err) {
|
|
@@ -365,6 +418,14 @@ export async function handleClose(input) {
|
|
|
365
418
|
const title = keiyakuBranch.slice("keiyaku/".length);
|
|
366
419
|
const petition = input.petition;
|
|
367
420
|
if (petition === "ABANDON") {
|
|
421
|
+
let round = 0;
|
|
422
|
+
try {
|
|
423
|
+
const trace = await fs.readFile(path.join(cwd, TRACE_FILE), "utf-8");
|
|
424
|
+
round = computeTraceState(trace).maxRound;
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
round = 0;
|
|
428
|
+
}
|
|
368
429
|
await assertCleanWorkingTree(cwd);
|
|
369
430
|
try {
|
|
370
431
|
await git.checkoutBranch(cwd, baseBranch);
|
|
@@ -374,14 +435,6 @@ export async function handleClose(input) {
|
|
|
374
435
|
catch (err) {
|
|
375
436
|
throw wrapFlowError(`execute ABANDON (${keiyakuBranch} -> ${baseBranch})`, err);
|
|
376
437
|
}
|
|
377
|
-
let round = 0;
|
|
378
|
-
try {
|
|
379
|
-
const trace = await fs.readFile(path.join(cwd, TRACE_FILE), "utf-8");
|
|
380
|
-
round = computeTraceState(trace).maxRound;
|
|
381
|
-
}
|
|
382
|
-
catch {
|
|
383
|
-
round = 0;
|
|
384
|
-
}
|
|
385
438
|
return {
|
|
386
439
|
status: "success",
|
|
387
440
|
result: "dropped",
|
|
@@ -389,7 +442,6 @@ export async function handleClose(input) {
|
|
|
389
442
|
branch: keiyakuBranch,
|
|
390
443
|
baseBranch,
|
|
391
444
|
diff: "Abandoned without merge.",
|
|
392
|
-
content: `Abandoned ${keiyakuBranch}.`,
|
|
393
445
|
};
|
|
394
446
|
}
|
|
395
447
|
await ensureKeiyakuFiles(cwd);
|
|
@@ -461,7 +513,6 @@ export async function handleClose(input) {
|
|
|
461
513
|
branch: keiyakuBranch,
|
|
462
514
|
baseBranch,
|
|
463
515
|
diff,
|
|
464
|
-
content: `Merged ${keiyakuBranch} into ${baseBranch}.`,
|
|
465
516
|
};
|
|
466
517
|
}
|
|
467
518
|
catch (err) {
|
|
@@ -510,7 +561,6 @@ export async function handleDrive(input) {
|
|
|
510
561
|
});
|
|
511
562
|
const trace = await fs.readFile(path.join(cwd, TRACE_FILE), "utf-8");
|
|
512
563
|
const diff = await git.getIncrementalDiff(cwd);
|
|
513
|
-
const content = `Round ${plan.targetRound} complete on ${keiyakuBranch}.`;
|
|
514
564
|
return {
|
|
515
565
|
status: "success",
|
|
516
566
|
round: plan.targetRound,
|
|
@@ -519,7 +569,6 @@ export async function handleDrive(input) {
|
|
|
519
569
|
summary,
|
|
520
570
|
branch: keiyakuBranch,
|
|
521
571
|
baseBranch,
|
|
522
|
-
content,
|
|
523
572
|
};
|
|
524
573
|
}
|
|
525
574
|
catch (err) {
|
|
@@ -8,7 +8,7 @@ Round: 1 (initial implementation).
|
|
|
8
8
|
- Directive: round scope.
|
|
9
9
|
|
|
10
10
|
1. Read KEIYAKU.md. Use it as source of truth.
|
|
11
|
-
2. If
|
|
11
|
+
2. If Directive exists, prioritize it.
|
|
12
12
|
3. Treat all constraints/criteria as required.
|
|
13
13
|
4. Read KEIYAKU_TRACE.md if present.
|
|
14
14
|
5. Execute:
|
|
@@ -38,7 +38,7 @@ Address this review reason:
|
|
|
38
38
|
${reason}
|
|
39
39
|
|
|
40
40
|
1. Read KEIYAKU.md. Keep constraints/criteria unchanged.
|
|
41
|
-
2. If
|
|
41
|
+
2. If Directive exists, prioritize it.
|
|
42
42
|
3. Read KEIYAKU_TRACE.md. Locate the latest "## Review ${round}" section.
|
|
43
43
|
4. Fix review reason first.
|
|
44
44
|
5. No drift from constraints/criteria.
|
|
@@ -102,7 +102,6 @@ export function buildKeiyakuSuccessResponse(result, input) {
|
|
|
102
102
|
nextHint: nextHints.join("\n"),
|
|
103
103
|
inputEcho,
|
|
104
104
|
...result,
|
|
105
|
-
content: text,
|
|
106
105
|
},
|
|
107
106
|
};
|
|
108
107
|
}
|
|
@@ -130,7 +129,6 @@ export function buildDriveResponse(result, input) {
|
|
|
130
129
|
nextHint: nextHints.join("\n"),
|
|
131
130
|
inputEcho,
|
|
132
131
|
...result,
|
|
133
|
-
content: text,
|
|
134
132
|
},
|
|
135
133
|
};
|
|
136
134
|
}
|
|
@@ -183,7 +181,6 @@ export function buildCloseDoneResponse(result, input) {
|
|
|
183
181
|
nextHint: nextHints.join("\n"),
|
|
184
182
|
inputEcho,
|
|
185
183
|
...result,
|
|
186
|
-
content: text,
|
|
187
184
|
},
|
|
188
185
|
};
|
|
189
186
|
}
|
|
@@ -211,12 +208,12 @@ export function buildCloseDropResponse(result, input) {
|
|
|
211
208
|
nextHint: nextHints.join("\n"),
|
|
212
209
|
inputEcho,
|
|
213
210
|
...result,
|
|
214
|
-
content: text,
|
|
215
211
|
},
|
|
216
212
|
};
|
|
217
213
|
}
|
|
218
214
|
export function buildToolErrorResponse(input) {
|
|
219
215
|
const inputEcho = (input.inputEcho ?? []).map((line) => truncateForDisplay(line, 800));
|
|
216
|
+
const shouldRaiseProtocolError = input.errorType === "runtime_error";
|
|
220
217
|
const text = assembleResponse("Failed", input.message, [buildSection("Input Context", inputEcho)], [input.hint]);
|
|
221
218
|
return {
|
|
222
219
|
content: [{ type: "text", text }],
|
|
@@ -229,6 +226,6 @@ export function buildToolErrorResponse(input) {
|
|
|
229
226
|
hint: input.hint,
|
|
230
227
|
inputEcho,
|
|
231
228
|
},
|
|
232
|
-
isError: true,
|
|
229
|
+
...(shouldRaiseProtocolError ? { isError: true } : {}),
|
|
233
230
|
};
|
|
234
231
|
}
|