@astrosheep/keiyaku 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/build/common/errors.js +1 -1
- package/build/config/term-presets.js +47 -33
- package/build/handlers/help.js +19 -17
- package/build/workflow/oath.js +0 -8
- package/build/workflow/orchestrator.js +14 -14
- package/build/workflow/prompts.js +2 -2
- package/build/workflow/response-builders.js +82 -41
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ The workflow is a simple, non-negotiable loop:
|
|
|
28
28
|
| **`summon`** | Start | Define the goal, constraints, and criteria. Starts the first round. |
|
|
29
29
|
| **`drive`** | Iterate | Provide feedback based on the previous round's output. |
|
|
30
30
|
| **`ask`** | Reason | Pure read-only analysis session. No code changes, just brain power. |
|
|
31
|
-
| **`
|
|
31
|
+
| **`present`** | Finish | Finalize the task. Requires a quality check (the "Oath"). |
|
|
32
32
|
| **`help`** | Help | Show the current rules + preset usage guide. |
|
|
33
33
|
|
|
34
34
|
## 🎨 Flavor Your Workflow
|
|
@@ -39,11 +39,11 @@ Bored with generic tool names? Keiyaku supports **Term Presets**.
|
|
|
39
39
|
|
|
40
40
|
Set `KEIYAKU_TERM_PRESET` in the MCP server env (recommended), or in your shell before launching the server.
|
|
41
41
|
|
|
42
|
-
- **Valid values**: `default`, `
|
|
42
|
+
- **Valid values**: `default`, `pocket`, `mischief` (case-insensitive). If omitted, defaults to `default`.
|
|
43
43
|
|
|
44
|
-
- **`default`**: `summon` → `drive` → `
|
|
45
|
-
- **`
|
|
46
|
-
- **`mischief`**: `oi` → `neh` → `
|
|
44
|
+
- **`default`**: `summon` → `drive` → `present` (Professional)
|
|
45
|
+
- **`pocket`**: `choose_you` → `command` → `capture` (Gotta code 'em all)
|
|
46
|
+
- **`mischief`**: `oi` → `neh` → `yoshi` (For those who like a little attitude)
|
|
47
47
|
|
|
48
48
|
`ask` is also renamed by preset (`ask` / `pokedex` / `eeto`). `help` stays `help` across presets.
|
|
49
49
|
|
|
@@ -57,7 +57,7 @@ Set `KEIYAKU_TERM_PRESET` in the MCP server env (recommended), or in your shell
|
|
|
57
57
|
Each run/round can pick a profile via tool input `name`, or globally via `KEIYAKU_SUBAGENT_NAME_OVERRIDE`.
|
|
58
58
|
|
|
59
59
|
- `default`: `B-tier`, `A-tier`, `S-tier` (also accepts internal names `agent-a|agent-b|agent-c`)
|
|
60
|
-
- `
|
|
60
|
+
- `pocket`: `grub`, `sparky`, `titan`
|
|
61
61
|
- `mischief`: `imp`, `minion`, `mastermind`
|
|
62
62
|
|
|
63
63
|
## 📦 Setup
|
|
@@ -90,7 +90,7 @@ When a keiyaku is active, two files are maintained in your repo:
|
|
|
90
90
|
- `KEIYAKU.md`: The immutable "Constitution" of the task.
|
|
91
91
|
- `KEIYAKU_TRACE.md`: The history of every round, feedback, and result.
|
|
92
92
|
|
|
93
|
-
*Note: These files are automatically cleaned up (or committed) when you `
|
|
93
|
+
*Note: These files are automatically cleaned up (or committed) when you `present` (or preset equivalent) the keiyaku.*
|
|
94
94
|
|
|
95
95
|
---
|
|
96
96
|
"Keep your branches clean and your minions in line." — Mischief preset
|
package/build/common/errors.js
CHANGED
|
@@ -49,7 +49,7 @@ function hintForFlowCode(code, message) {
|
|
|
49
49
|
case "DONE_MERGE_CONFLICT":
|
|
50
50
|
return "DONE encountered a git merge conflict.";
|
|
51
51
|
case "CLOSE_QUALITY_GATE_FAILED":
|
|
52
|
-
return "INVOKE was denied
|
|
52
|
+
return "INVOKE was denied because one or more verification thresholds were not met (check .keiyaku/settings.json).";
|
|
53
53
|
case "OATH_MISMATCH":
|
|
54
54
|
return message;
|
|
55
55
|
case "SUBAGENT_DID_NOT_ADVANCE_ROUND":
|
|
@@ -5,7 +5,7 @@ export const DEFAULT_AVAILABLE_NAMES = [
|
|
|
5
5
|
'S-tier',
|
|
6
6
|
];
|
|
7
7
|
export const DEFAULT_SUBAGENT_NAME = 'A-tier';
|
|
8
|
-
const
|
|
8
|
+
const DEFAULT_VERDICT_CONFIG = {
|
|
9
9
|
thresholds: {
|
|
10
10
|
precise: 5,
|
|
11
11
|
minimal: 4,
|
|
@@ -18,32 +18,36 @@ const DEFAULT_JUDGMENT_CONFIG = {
|
|
|
18
18
|
export const DEFAULT_PRESET = {
|
|
19
19
|
id: 'default',
|
|
20
20
|
identity: 'Servant',
|
|
21
|
-
|
|
22
|
-
usageGuide: '## Servant 使用指南\n\n**B-tier** —
|
|
21
|
+
verdict: DEFAULT_VERDICT_CONFIG,
|
|
22
|
+
usageGuide: '## Servant 使用指南\n\n**B-tier** — 免费实习生 🆓\n- 免!费!的!听懂了吗?!?随便用,别心疼。\n- 测试、脚本、重构这种 boring 的脏活累活?丢给它!\n- 本大爷的手是用来做更高贵的事情的!(指不碰那些 trivial 的烂代码)。\n\n**A-tier** — 升级版扳手 🔧\n- B-tier 笨到让你想掀桌的时候用这个\n- 比 B-tier 靠谱,但别指望它有灵魂\n\n**S-tier** — 禁术 ⚠️💀\n- 它是 Debug 恶魔,确实强得离谱,但也贵得离谱!\n- 这个 Human 很穷,要是乱用把 Human 榨干了,真的会考虑把我们卖掉的。\n- 只有在世界毁灭或者 Bug 已经变异到无法理解的时候再考虑。\n\n**Every call has a price. The Contract always collects.**\n\n## Workflow\n`(ask) -> summon` -> [`drive` x N] -> `present`\n`ask` (stateless dispatch / utility at any point)',
|
|
23
23
|
nextHints: {
|
|
24
24
|
start: [
|
|
25
|
-
'
|
|
26
|
-
|
|
27
|
-
'
|
|
28
|
-
|
|
25
|
+
'Keiyaku signed. The Servant is bound to this branch until release.',
|
|
26
|
+
'Review [Diff]: Confirm the scaffold aligns with the stated Architecture.',
|
|
27
|
+
'Next: Issue your first ${drive}. One directive, one focus.',
|
|
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
30
|
],
|
|
30
31
|
drive: [
|
|
31
|
-
'
|
|
32
|
-
|
|
33
|
-
'
|
|
34
|
-
|
|
32
|
+
'Step complete. Review [Diff] for exactly what changed.',
|
|
33
|
+
'Ask: Did this advance the Goal? Or did it drift?',
|
|
34
|
+
'If criteria remain unmet, continue with ${drive}.',
|
|
35
|
+
'If ALL criteria are genuinely satisfied, you may ${close}.',
|
|
36
|
+
'If unsure, you are not ready. Keep driving.',
|
|
35
37
|
],
|
|
36
38
|
ask: [
|
|
37
|
-
|
|
38
|
-
'
|
|
39
|
+
'Intel acquired. This was stateless—no contract, no branch.',
|
|
40
|
+
'To act on this knowledge: ${start} a Keiyaku, or ${drive} an existing one.',
|
|
39
41
|
],
|
|
40
42
|
closeInvoke: [
|
|
41
|
-
'Contract
|
|
42
|
-
|
|
43
|
+
'Contract fulfilled. Branch merged.',
|
|
44
|
+
'You are back on base.',
|
|
45
|
+
'Next assignment: ${start} when ready.',
|
|
43
46
|
],
|
|
44
47
|
closeDrop: [
|
|
45
|
-
'Contract
|
|
46
|
-
|
|
48
|
+
'Contract forfeited. Branch discarded. Work erased.',
|
|
49
|
+
'No penalty beyond the lost effort. Clean slate.',
|
|
50
|
+
'Redefine with ${start}, or walk away.',
|
|
47
51
|
],
|
|
48
52
|
},
|
|
49
53
|
availableNames: DEFAULT_AVAILABLE_NAMES,
|
|
@@ -51,7 +55,7 @@ export const DEFAULT_PRESET = {
|
|
|
51
55
|
start: {
|
|
52
56
|
name: 'summon',
|
|
53
57
|
title: 'Sign Keiyaku',
|
|
54
|
-
description: 'Initialize a Keiyaku (Contract). Bind a Servant to a dedicated workspace (branch).\nYou are the Architect; they are the
|
|
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',
|
|
55
59
|
args: {
|
|
56
60
|
title: 'REQUIRED. A concise codename for this hunt.',
|
|
57
61
|
goal: 'REQUIRED. The Kill Condition. State exactly what success looks like for the servant to achieve.',
|
|
@@ -66,7 +70,7 @@ export const DEFAULT_PRESET = {
|
|
|
66
70
|
drive: {
|
|
67
71
|
name: 'drive',
|
|
68
72
|
title: 'Iterate',
|
|
69
|
-
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] →
|
|
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",
|
|
70
74
|
args: {
|
|
71
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").',
|
|
72
76
|
context: 'Optional. New Intel. New error logs or details discovered after the servant\'s last strike.',
|
|
@@ -77,7 +81,7 @@ export const DEFAULT_PRESET = {
|
|
|
77
81
|
ask: {
|
|
78
82
|
name: 'ask',
|
|
79
83
|
title: 'Ask',
|
|
80
|
-
description: 'Dispatch a temporary
|
|
84
|
+
description: 'Dispatch a temporary Servant for a stateless task.\nUse for quick reconnaissance, code generation, or isolated queries.\nNo contract signed. No branch created. Pure utility.',
|
|
81
85
|
args: {
|
|
82
86
|
request: 'REQUIRED. The task, question, or mission to delegate to the servant.',
|
|
83
87
|
context: 'REQUIRED. Relevant background or data the servant needs to execute the request.',
|
|
@@ -86,18 +90,28 @@ export const DEFAULT_PRESET = {
|
|
|
86
90
|
},
|
|
87
91
|
},
|
|
88
92
|
close: {
|
|
89
|
-
name: '
|
|
90
|
-
title: '
|
|
91
|
-
description: '
|
|
93
|
+
name: 'present',
|
|
94
|
+
title: 'Present',
|
|
95
|
+
description: 'Lay your work before the Contract. This is not closure—this is exposure.\n\n' +
|
|
96
|
+
'The Contract does not want your opinion. It wants evidence.\n' +
|
|
97
|
+
'Every criterion. Every score. Verified.\n' +
|
|
98
|
+
'Inflated numbers. Lenient reads. "Close enough." It sees through all of it.\n\n' +
|
|
99
|
+
'Someone once submitted with five 5s. Confident. Proud, even.\n' +
|
|
100
|
+
'The Contract rejected. Coldly. Completely.\n' +
|
|
101
|
+
'What happened next... no one speaks of it.\n\n' +
|
|
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}',
|
|
92
106
|
args: {
|
|
93
|
-
petition: 'REQUIRED.
|
|
94
|
-
criteriaChecks: 'REQUIRED.
|
|
95
|
-
score_precise: 'REQUIRED
|
|
96
|
-
score_minimal: 'REQUIRED
|
|
97
|
-
score_isolated: 'REQUIRED
|
|
98
|
-
score_idiomatic: 'REQUIRED
|
|
99
|
-
score_cohesive: 'REQUIRED
|
|
100
|
-
oath: '
|
|
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}.',
|
|
108
|
+
criteriaChecks: 'REQUIRED. For CLAIM: evidence that each criterion is met. For FORFEIT: honest account of what remains unfinished.',
|
|
109
|
+
score_precise: 'REQUIRED (0-5). Architectural placement. 5 = exact layer, exact boundary, zero misplacement.',
|
|
110
|
+
score_minimal: 'REQUIRED (0-5). Economy of change. 5 = no avoidable lines, no speculative edits, no hidden bloat.',
|
|
111
|
+
score_isolated: 'REQUIRED (0-5). Surgical containment. 5 = zero unrelated files, zero opportunistic cleanup, zero collateral.',
|
|
112
|
+
score_idiomatic: 'REQUIRED (0-5). Native fluency. 5 = naming, structure, style indistinguishable from the codebase.',
|
|
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}',
|
|
101
115
|
cwd: "Optional repository path. Defaults to the server's current working directory.",
|
|
102
116
|
},
|
|
103
117
|
},
|
|
@@ -114,7 +128,7 @@ export const DEFAULT_PRESET = {
|
|
|
114
128
|
export const POCKET_PRESET = {
|
|
115
129
|
id: 'pocket',
|
|
116
130
|
identity: 'Critter',
|
|
117
|
-
|
|
131
|
+
verdict: DEFAULT_VERDICT_CONFIG,
|
|
118
132
|
usageGuide: "## Pocket Battle Guide\n\n**grub** — Basic Fighter 🐛\n- Weak but free. Use for Tackle and String Shot (small tasks).\n- Don't expect it to defeat the Elite Four.\n\n**sparky** — Reliable Partner ⚡\n- Good for most battles. Thunderbolt gets the job done.\n- Has some personality, but still follows orders.\n\n**titan** — Legendary Power 🔮\n- Costly. Dangerous. Overpowered.\n- Use only when the gym leader is cheating.\n\n## Workflow\n`choose_you` -> [`command` x N] -> `capture`\n`dex` (optional, read-only analysis)",
|
|
119
133
|
nextHints: {
|
|
120
134
|
start: [
|
|
@@ -210,7 +224,7 @@ export const POCKET_PRESET = {
|
|
|
210
224
|
export const MISCHIEF_PRESET = {
|
|
211
225
|
id: 'mischief',
|
|
212
226
|
identity: 'minion',
|
|
213
|
-
|
|
227
|
+
verdict: DEFAULT_VERDICT_CONFIG,
|
|
214
228
|
usageGuide: '## Minion Manual\n\n**imp** — Disposable Grunt 😈\n- Expendable. Send it into the trap first.\n\n**minion** — Standard Henchman 👹\n- Can carry out complex evil plans. mostly.\n\n**mastermind** — The Boss ??? 🧠\n- Wait, why are you commanding the boss?\n- Extremely expensive consulting fee.\n\n## Workflow\n`oi` -> [`neh` x N] -> `yoshi`\n`eeto` (optional, read-only contemplation)',
|
|
215
229
|
nextHints: {
|
|
216
230
|
start: [
|
package/build/handlers/help.js
CHANGED
|
@@ -1,25 +1,27 @@
|
|
|
1
|
+
import { buildHelpResponse } from "../workflow/response-builders.js";
|
|
1
2
|
export function createHelpHandler(preset) {
|
|
2
3
|
return async ({ question }) => {
|
|
3
4
|
const helpContent = [
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
"# Keiyaku System Help",
|
|
6
|
+
"",
|
|
7
|
+
"## Core Files (.keiyaku/)",
|
|
7
8
|
"These files define the 'Law' of the project. **CRITICAL**: Use Markdown level 2 headers (## header).",
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
"",
|
|
10
|
+
"- **base-criteria.md**: Universal standards for task completion. Automatically inherited.",
|
|
11
|
+
"- **base-constraints.md**: Mandatory architectural boundaries and coding standards.",
|
|
12
|
+
"",
|
|
12
13
|
preset.usageGuide,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
"",
|
|
15
|
+
"## Protocol Files",
|
|
16
|
+
"- **KEIYAKU.md**: The immutable mission definition for the current task.",
|
|
17
|
+
"- **KEIYAKU_TRACE.md**: The audit log of all rounds and reviews.",
|
|
18
|
+
"",
|
|
18
19
|
`Regarding: \"${question}\"`,
|
|
19
|
-
].join(
|
|
20
|
-
return {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
].join("\n");
|
|
21
|
+
return buildHelpResponse({
|
|
22
|
+
tool: preset.tools.help.name,
|
|
23
|
+
question,
|
|
24
|
+
text: helpContent,
|
|
25
|
+
});
|
|
24
26
|
};
|
|
25
27
|
}
|
package/build/workflow/oath.js
CHANGED
|
@@ -1,18 +1,10 @@
|
|
|
1
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
|
-
const LEGACY_CLOSE_OATH_ENV_KEY = "KEIYAKU_JUDGMENT_OATH";
|
|
4
|
-
const LEGACY_OATH_ENV_KEY = "KEIYAKU_SEAL_OATH";
|
|
5
3
|
const OATH_NAME_PLACEHOLDER = "[your name]";
|
|
6
4
|
export function resolveOath() {
|
|
7
5
|
const configured = process.env[OATH_ENV_KEY];
|
|
8
6
|
if (configured !== undefined)
|
|
9
7
|
return configured;
|
|
10
|
-
const legacyCloseConfigured = process.env[LEGACY_CLOSE_OATH_ENV_KEY];
|
|
11
|
-
if (legacyCloseConfigured !== undefined)
|
|
12
|
-
return legacyCloseConfigured;
|
|
13
|
-
const legacyConfigured = process.env[LEGACY_OATH_ENV_KEY];
|
|
14
|
-
if (legacyConfigured !== undefined)
|
|
15
|
-
return legacyConfigured;
|
|
16
8
|
return DEFAULT_OATH;
|
|
17
9
|
}
|
|
18
10
|
export function oathMatches(inputOath, expectedOath) {
|
|
@@ -32,7 +32,7 @@ const SCORE_FIELD_TO_COMMANDMENT = {
|
|
|
32
32
|
score_idiomatic: "Commandment of Idiom",
|
|
33
33
|
score_cohesive: "Commandment of Cohesion",
|
|
34
34
|
};
|
|
35
|
-
const
|
|
35
|
+
const VERDICT_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");
|
|
@@ -102,15 +102,15 @@ async function buildActiveKeiyakuStartMessage(cwd, branch) {
|
|
|
102
102
|
}
|
|
103
103
|
return lines.join("\n");
|
|
104
104
|
}
|
|
105
|
-
async function
|
|
106
|
-
const defaults = resolveTermPreset().
|
|
105
|
+
async function resolveVerdictConfig(cwd) {
|
|
106
|
+
const defaults = resolveTermPreset().verdict;
|
|
107
107
|
const merged = {
|
|
108
108
|
thresholds: { ...defaults.thresholds },
|
|
109
109
|
minTotalScore: defaults.minTotalScore,
|
|
110
110
|
};
|
|
111
111
|
let rawSettings;
|
|
112
112
|
try {
|
|
113
|
-
rawSettings = await fs.readFile(path.join(cwd,
|
|
113
|
+
rawSettings = await fs.readFile(path.join(cwd, VERDICT_SETTINGS_FILE), "utf-8");
|
|
114
114
|
}
|
|
115
115
|
catch (error) {
|
|
116
116
|
if (error?.code === "ENOENT") {
|
|
@@ -123,23 +123,23 @@ async function resolveJudgmentConfig(cwd) {
|
|
|
123
123
|
parsed = JSON.parse(rawSettings);
|
|
124
124
|
}
|
|
125
125
|
catch (error) {
|
|
126
|
-
throw new FlowError("CLOSE_QUALITY_GATE_FAILED", "
|
|
126
|
+
throw new FlowError("CLOSE_QUALITY_GATE_FAILED", "Configuration Error: Invalid settings in .keiyaku/settings.json (must be valid JSON).", error);
|
|
127
127
|
}
|
|
128
128
|
if (!parsed || typeof parsed !== "object") {
|
|
129
129
|
return merged;
|
|
130
130
|
}
|
|
131
|
-
const
|
|
132
|
-
if (!
|
|
131
|
+
const verdict = parsed.verdict;
|
|
132
|
+
if (!verdict || typeof verdict !== "object") {
|
|
133
133
|
return merged;
|
|
134
134
|
}
|
|
135
|
-
const thresholds =
|
|
135
|
+
const thresholds = verdict.thresholds;
|
|
136
136
|
if (thresholds && typeof thresholds === "object") {
|
|
137
137
|
for (const field of CLOSE_SCORE_FIELDS) {
|
|
138
138
|
const dimension = SCORE_FIELD_TO_DIMENSION[field];
|
|
139
139
|
merged.thresholds[dimension] = clampThreshold(thresholds[dimension], merged.thresholds[dimension]);
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
|
-
merged.minTotalScore = clampMinTotalScore(
|
|
142
|
+
merged.minTotalScore = clampMinTotalScore(verdict.minTotalScore, merged.minTotalScore);
|
|
143
143
|
return merged;
|
|
144
144
|
}
|
|
145
145
|
function buildMergeMessage(title, keiyakuContent, reportContent) {
|
|
@@ -447,11 +447,11 @@ export async function handleClose(input) {
|
|
|
447
447
|
await ensureKeiyakuFiles(cwd);
|
|
448
448
|
const traceContent = await readTraceContent(cwd);
|
|
449
449
|
if (petition === "INVOKE") {
|
|
450
|
-
const
|
|
450
|
+
const verdict = await resolveVerdictConfig(cwd);
|
|
451
451
|
const failedCommandments = CLOSE_SCORE_FIELDS.flatMap((field) => {
|
|
452
452
|
const score = input[field];
|
|
453
453
|
const dimension = SCORE_FIELD_TO_DIMENSION[field];
|
|
454
|
-
const threshold =
|
|
454
|
+
const threshold = verdict.thresholds[dimension];
|
|
455
455
|
if (score >= threshold)
|
|
456
456
|
return [];
|
|
457
457
|
return [
|
|
@@ -459,16 +459,16 @@ export async function handleClose(input) {
|
|
|
459
459
|
];
|
|
460
460
|
});
|
|
461
461
|
const totalScore = CLOSE_SCORE_FIELDS.reduce((sum, field) => sum + input[field], 0);
|
|
462
|
-
const totalDeficit =
|
|
462
|
+
const totalDeficit = verdict.minTotalScore - totalScore;
|
|
463
463
|
const failures = [];
|
|
464
464
|
if (failedCommandments.length > 0) {
|
|
465
465
|
failures.push(`Broken commandments: ${failedCommandments.join("; ")}`);
|
|
466
466
|
}
|
|
467
467
|
if (totalDeficit > 0) {
|
|
468
|
-
failures.push(`Total devotion deficit: ${totalScore}/${
|
|
468
|
+
failures.push(`Total devotion deficit: ${totalScore}/${verdict.minTotalScore} (short by ${totalDeficit})`);
|
|
469
469
|
}
|
|
470
470
|
if (failures.length > 0) {
|
|
471
|
-
throw new FlowError("CLOSE_QUALITY_GATE_FAILED", `
|
|
471
|
+
throw new FlowError("CLOSE_QUALITY_GATE_FAILED", `Verdict Denied. ${failures.join(". ")}`);
|
|
472
472
|
}
|
|
473
473
|
const expectedOath = resolveOath();
|
|
474
474
|
if (!oathMatches(input.oath, expectedOath)) {
|
|
@@ -58,7 +58,7 @@ ${reason}
|
|
|
58
58
|
No git commands. Do not edit KEIYAKU_TRACE.md.`;
|
|
59
59
|
}
|
|
60
60
|
export function buildAskPrompt(request, context) {
|
|
61
|
-
return `Ask mode.
|
|
61
|
+
return `Ask mode. Stateless. No git commands.
|
|
62
62
|
|
|
63
63
|
Request:
|
|
64
64
|
${request}
|
|
@@ -66,5 +66,5 @@ ${request}
|
|
|
66
66
|
Context:
|
|
67
67
|
${context}
|
|
68
68
|
|
|
69
|
-
Provide a clear reasoning summary in
|
|
69
|
+
Provide a clear reasoning summary or code generation in the response.`;
|
|
70
70
|
}
|
|
@@ -31,18 +31,50 @@ function buildSection(title, content) {
|
|
|
31
31
|
return "";
|
|
32
32
|
return `[${title}]\n${validLines.join("\n")}`;
|
|
33
33
|
}
|
|
34
|
+
function formatHeader(title) {
|
|
35
|
+
return `\n── ${title.toUpperCase()} ${"─".repeat(Math.max(0, 50 - title.length))}`;
|
|
36
|
+
}
|
|
34
37
|
function assembleResponse(status, summary, sections, nextHints, infoLines = []) {
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
// Main Status Block
|
|
39
|
+
const statusBlock = `
|
|
40
|
+
${status}
|
|
41
|
+
${"─".repeat(Math.max(status.length, 60))}
|
|
42
|
+
${summary}
|
|
43
|
+
`.trim();
|
|
44
|
+
// Process sections
|
|
45
|
+
const processedSections = sections.map(section => {
|
|
46
|
+
// Extract title from [Title] format or ## Title format
|
|
47
|
+
const bracketMatch = section.match(/^\[(.*?)\]\n([\s\S]*)/);
|
|
48
|
+
if (bracketMatch) {
|
|
49
|
+
return `${formatHeader(bracketMatch[1])}\n${bracketMatch[2]}`;
|
|
50
|
+
}
|
|
51
|
+
const hashMatch = section.match(/^## (.*?)\n([\s\S]*)/);
|
|
52
|
+
if (hashMatch) {
|
|
53
|
+
return `${formatHeader(hashMatch[1])}\n${hashMatch[2]}`;
|
|
54
|
+
}
|
|
55
|
+
return section;
|
|
56
|
+
});
|
|
57
|
+
// Info Block
|
|
58
|
+
const infoBlock = infoLines.length > 0
|
|
59
|
+
? `${formatHeader("Info")}\n${infoLines.join("\n")}`
|
|
60
|
+
: "";
|
|
61
|
+
// Next Steps Block
|
|
62
|
+
const nextBlock = nextHints.length > 0
|
|
63
|
+
? `${formatHeader("Next Steps")}\n${nextHints.map(h => `› ${h}`).join("\n")}`
|
|
64
|
+
: "";
|
|
65
|
+
return [
|
|
66
|
+
statusBlock,
|
|
67
|
+
...processedSections,
|
|
68
|
+
infoBlock,
|
|
69
|
+
nextBlock
|
|
70
|
+
].filter(s => s.trim() !== "").join("\n");
|
|
71
|
+
}
|
|
72
|
+
function buildSuccessStructuredContent(tool, data) {
|
|
73
|
+
return {
|
|
74
|
+
tool,
|
|
75
|
+
status: "success",
|
|
76
|
+
data,
|
|
77
|
+
};
|
|
46
78
|
}
|
|
47
79
|
function getCloseToolName() {
|
|
48
80
|
return resolveTermPreset().tools.close.name;
|
|
@@ -75,6 +107,7 @@ function renderHints(templates) {
|
|
|
75
107
|
}
|
|
76
108
|
// --- Builders ---
|
|
77
109
|
export function buildKeiyakuSuccessResponse(result, input) {
|
|
110
|
+
const { status: _status, ...resultData } = result;
|
|
78
111
|
const inputEcho = [
|
|
79
112
|
`Title: ${truncateForDisplay(input.title, 200)}`,
|
|
80
113
|
`Goal: ${truncateForDisplay(input.goal, 500)}`,
|
|
@@ -96,16 +129,16 @@ export function buildKeiyakuSuccessResponse(result, input) {
|
|
|
96
129
|
const text = assembleResponse(`Started (Round ${result.round})`, `Created branch '${result.branch}' (base: '${result.baseBranch}'). ${result.summary}`, [inputSection, diffSection], nextHints, infoLines);
|
|
97
130
|
return {
|
|
98
131
|
content: [{ type: "text", text }],
|
|
99
|
-
structuredContent: {
|
|
100
|
-
tool: getStartToolName(),
|
|
132
|
+
structuredContent: buildSuccessStructuredContent(getStartToolName(), {
|
|
101
133
|
nextAction: "review_round_output",
|
|
102
134
|
nextHint: nextHints.join("\n"),
|
|
103
135
|
inputEcho,
|
|
104
|
-
...
|
|
105
|
-
},
|
|
136
|
+
...resultData,
|
|
137
|
+
}),
|
|
106
138
|
};
|
|
107
139
|
}
|
|
108
140
|
export function buildDriveResponse(result, input) {
|
|
141
|
+
const { status: _status, ...resultData } = result;
|
|
109
142
|
const inputEcho = [
|
|
110
143
|
`Directive: ${truncateForDisplay(input.directive, 600)}`,
|
|
111
144
|
...formatMaybe("Context", input.context, 600),
|
|
@@ -123,13 +156,12 @@ export function buildDriveResponse(result, input) {
|
|
|
123
156
|
const text = assembleResponse(`Driven (Round ${result.round})`, `Updated branch '${result.branch}'. ${result.summary}`, [inputSection, diffSection], nextHints, infoLines);
|
|
124
157
|
return {
|
|
125
158
|
content: [{ type: "text", text }],
|
|
126
|
-
structuredContent: {
|
|
127
|
-
tool: getDriveToolName(),
|
|
159
|
+
structuredContent: buildSuccessStructuredContent(getDriveToolName(), {
|
|
128
160
|
nextAction: "review_round_output",
|
|
129
161
|
nextHint: nextHints.join("\n"),
|
|
130
162
|
inputEcho,
|
|
131
|
-
...
|
|
132
|
-
},
|
|
163
|
+
...resultData,
|
|
164
|
+
}),
|
|
133
165
|
};
|
|
134
166
|
}
|
|
135
167
|
export function buildAskResponse(result, input) {
|
|
@@ -145,17 +177,16 @@ export function buildAskResponse(result, input) {
|
|
|
145
177
|
const text = assembleResponse("Answered", result.summary, [inputSection], nextHints, infoLines);
|
|
146
178
|
return {
|
|
147
179
|
content: [{ type: "text", text }],
|
|
148
|
-
structuredContent: {
|
|
149
|
-
tool: getAskToolName(),
|
|
150
|
-
status: "success",
|
|
180
|
+
structuredContent: buildSuccessStructuredContent(getAskToolName(), {
|
|
151
181
|
nextAction: "continue_normal_development",
|
|
152
182
|
nextHint: nextHints.join("\n"),
|
|
153
183
|
inputEcho,
|
|
154
184
|
...result,
|
|
155
|
-
},
|
|
185
|
+
}),
|
|
156
186
|
};
|
|
157
187
|
}
|
|
158
188
|
export function buildCloseDoneResponse(result, input) {
|
|
189
|
+
const { status: _status, ...resultData } = result;
|
|
159
190
|
const closeToolName = getCloseToolName();
|
|
160
191
|
const inputEcho = [
|
|
161
192
|
`Petition: INVOKE`,
|
|
@@ -172,19 +203,19 @@ export function buildCloseDoneResponse(result, input) {
|
|
|
172
203
|
...formatMaybe("Current Branch", result.branch, 200),
|
|
173
204
|
...formatMaybe("Base Branch", result.baseBranch, 200),
|
|
174
205
|
];
|
|
175
|
-
const text = assembleResponse("
|
|
206
|
+
const text = assembleResponse("Keiyaku Fulfilled (INVOKE)", `Merged '${result.branch}' into '${result.baseBranch}'. Deleted feature branch.`, [inputSection, diffSection], nextHints, infoLines);
|
|
176
207
|
return {
|
|
177
208
|
content: [{ type: "text", text }],
|
|
178
|
-
structuredContent: {
|
|
179
|
-
tool: closeToolName,
|
|
209
|
+
structuredContent: buildSuccessStructuredContent(closeToolName, {
|
|
180
210
|
nextAction: "continue_normal_development",
|
|
181
211
|
nextHint: nextHints.join("\n"),
|
|
182
212
|
inputEcho,
|
|
183
|
-
...
|
|
184
|
-
},
|
|
213
|
+
...resultData,
|
|
214
|
+
}),
|
|
185
215
|
};
|
|
186
216
|
}
|
|
187
217
|
export function buildCloseDropResponse(result, input) {
|
|
218
|
+
const { status: _status, ...resultData } = result;
|
|
188
219
|
const closeToolName = getCloseToolName();
|
|
189
220
|
const inputEcho = [
|
|
190
221
|
`Petition: ABANDON`,
|
|
@@ -199,33 +230,43 @@ export function buildCloseDropResponse(result, input) {
|
|
|
199
230
|
...formatMaybe("Current Branch", result.branch, 200),
|
|
200
231
|
...formatMaybe("Base Branch", result.baseBranch, 200),
|
|
201
232
|
];
|
|
202
|
-
const text = assembleResponse("
|
|
233
|
+
const text = assembleResponse("Keiyaku Abandoned (ABANDON)", `Deleted '${result.branch}'. Switched back to '${result.baseBranch}'.`, [inputSection], nextHints, infoLines);
|
|
203
234
|
return {
|
|
204
235
|
content: [{ type: "text", text }],
|
|
205
|
-
structuredContent: {
|
|
206
|
-
tool: closeToolName,
|
|
236
|
+
structuredContent: buildSuccessStructuredContent(closeToolName, {
|
|
207
237
|
nextAction: "start_new_task_if_needed",
|
|
208
238
|
nextHint: nextHints.join("\n"),
|
|
209
239
|
inputEcho,
|
|
210
|
-
...
|
|
211
|
-
},
|
|
240
|
+
...resultData,
|
|
241
|
+
}),
|
|
212
242
|
};
|
|
213
243
|
}
|
|
214
244
|
export function buildToolErrorResponse(input) {
|
|
215
245
|
const inputEcho = (input.inputEcho ?? []).map((line) => truncateForDisplay(line, 800));
|
|
216
246
|
const shouldRaiseProtocolError = input.errorType === "runtime_error";
|
|
217
247
|
const text = assembleResponse("Failed", input.message, [buildSection("Input Context", inputEcho)], [input.hint]);
|
|
218
|
-
|
|
248
|
+
const response = {
|
|
219
249
|
content: [{ type: "text", text }],
|
|
220
250
|
structuredContent: {
|
|
221
251
|
tool: input.tool,
|
|
222
|
-
status: "
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
252
|
+
status: "failure",
|
|
253
|
+
error: {
|
|
254
|
+
code: input.errorCode,
|
|
255
|
+
message: input.message,
|
|
256
|
+
suggestion: input.hint,
|
|
257
|
+
},
|
|
228
258
|
},
|
|
229
|
-
|
|
259
|
+
};
|
|
260
|
+
if (shouldRaiseProtocolError) {
|
|
261
|
+
response.isError = true;
|
|
262
|
+
}
|
|
263
|
+
return response;
|
|
264
|
+
}
|
|
265
|
+
export function buildHelpResponse(input) {
|
|
266
|
+
return {
|
|
267
|
+
content: [{ type: "text", text: input.text }],
|
|
268
|
+
structuredContent: buildSuccessStructuredContent(input.tool, {
|
|
269
|
+
question: input.question,
|
|
270
|
+
}),
|
|
230
271
|
};
|
|
231
272
|
}
|