@astrosheep/keiyaku 0.1.8 → 0.1.10
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 +3 -3
- package/build/config/term-presets.js +28 -28
- package/build/handlers/close.js +5 -5
- package/build/types/tool-schemas.js +1 -1
- package/build/utils/git.js +28 -6
- package/build/workflow/orchestrator.js +30 -16
- package/build/workflow/response-builders.js +36 -7
- package/package.json +1 -1
package/build/common/errors.js
CHANGED
|
@@ -33,7 +33,7 @@ function hintForFlowCode(code, message) {
|
|
|
33
33
|
case "NOT_GIT_REPO":
|
|
34
34
|
return "The provided `cwd` is not a git repository.";
|
|
35
35
|
case "ACTIVE_KEIYAKU_EXISTS":
|
|
36
|
-
return "An active keiyaku branch already exists in this repository. Continue with `drive`, or use `close` + `
|
|
36
|
+
return "An active keiyaku branch already exists in this repository. Continue with `drive`, or use `close` + `FORFEIT` to drop it.";
|
|
37
37
|
case "EXISTING_KEIYAKU_BRANCH_FOUND":
|
|
38
38
|
return "At least one local `keiyaku/*` branch already exists in this repository.";
|
|
39
39
|
case "EMPTY_PARAM":
|
|
@@ -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 "
|
|
52
|
+
return "CLAIM 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":
|
|
@@ -72,7 +72,7 @@ const MESSAGE_HINT_PATTERNS = [
|
|
|
72
72
|
{ code: "MISSING_KEIYAKU_BASE", patterns: ["is missing base metadata"] },
|
|
73
73
|
{ code: "MISSING_PROTOCOL_FILES", patterns: ["missing protocol files"] },
|
|
74
74
|
{ code: "DONE_MERGE_CONFLICT", patterns: ["DONE merge conflict"] },
|
|
75
|
-
{ code: "CLOSE_QUALITY_GATE_FAILED", patterns: ["God's Wrath:
|
|
75
|
+
{ code: "CLOSE_QUALITY_GATE_FAILED", patterns: ["God's Wrath: CLAIM denied"] },
|
|
76
76
|
{
|
|
77
77
|
code: "OATH_MISMATCH",
|
|
78
78
|
patterns: [
|
|
@@ -19,7 +19,7 @@ export const DEFAULT_PRESET = {
|
|
|
19
19
|
id: 'default',
|
|
20
20
|
identity: 'Servant',
|
|
21
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`(
|
|
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` (anytime) | `summon` -> [`drive` | `ask`]* -> `present`',
|
|
23
23
|
nextHints: {
|
|
24
24
|
start: [
|
|
25
25
|
'Keiyaku signed. The Servant is bound to this branch until release.',
|
|
@@ -39,7 +39,7 @@ export const DEFAULT_PRESET = {
|
|
|
39
39
|
'Intel acquired. This was stateless—no contract, no branch.',
|
|
40
40
|
'To act on this knowledge: ${start} a Keiyaku, or ${drive} an existing one.',
|
|
41
41
|
],
|
|
42
|
-
|
|
42
|
+
closeClaim: [
|
|
43
43
|
'Contract fulfilled. Branch merged.',
|
|
44
44
|
'You are back on base.',
|
|
45
45
|
'Next assignment: ${start} when ready.',
|
|
@@ -81,7 +81,7 @@ export const DEFAULT_PRESET = {
|
|
|
81
81
|
ask: {
|
|
82
82
|
name: 'ask',
|
|
83
83
|
title: 'Ask',
|
|
84
|
-
description: 'Dispatch a temporary Servant for a stateless task.\nUse for quick reconnaissance, code generation, or
|
|
84
|
+
description: 'Dispatch a temporary Servant for a stateless task.\nUse for quick reconnaissance, code generation, isolated execution, or one-off tasks.\nNo contract signed. No branch created. Pure utility.',
|
|
85
85
|
args: {
|
|
86
86
|
request: 'REQUIRED. The task, question, or mission to delegate to the servant.',
|
|
87
87
|
context: 'REQUIRED. Relevant background or data the servant needs to execute the request.',
|
|
@@ -129,7 +129,7 @@ export const POCKET_PRESET = {
|
|
|
129
129
|
id: 'pocket',
|
|
130
130
|
identity: 'Critter',
|
|
131
131
|
verdict: DEFAULT_VERDICT_CONFIG,
|
|
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`
|
|
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`dex` (scan/test) | `choose_you` -> [`command` | `dex`]* -> `capture`",
|
|
133
133
|
nextHints: {
|
|
134
134
|
start: [
|
|
135
135
|
'Battle Started: The [Diff] section shows the opening moves.',
|
|
@@ -147,7 +147,7 @@ export const POCKET_PRESET = {
|
|
|
147
147
|
"Data Recorded: Use '${start}' to begin the encounter, or '${drive}' to use this intel.",
|
|
148
148
|
"Still Searching? Keep using '${ask}' to fill the Dex.",
|
|
149
149
|
],
|
|
150
|
-
|
|
150
|
+
closeClaim: [
|
|
151
151
|
'Critter Captured! The entry is logged in the PC (base branch).',
|
|
152
152
|
"Heal up. Use '${start}' to challenge the next opponent.",
|
|
153
153
|
],
|
|
@@ -187,11 +187,11 @@ export const POCKET_PRESET = {
|
|
|
187
187
|
ask: {
|
|
188
188
|
name: 'dex',
|
|
189
189
|
title: 'Dex',
|
|
190
|
-
description: 'Scan the Environment. A stateless look-up
|
|
190
|
+
description: 'Scan the Environment. A stateless look-up or experiment.\nUse for analyzing the codebase, checking type advantages, or running field tests.\nNo PP cost. No turn used. Fast and functional.',
|
|
191
191
|
args: {
|
|
192
|
-
request: 'REQUIRED. What should the Dex analyze, compare, or
|
|
193
|
-
context: 'REQUIRED. Context entries so the
|
|
194
|
-
name: 'Optional ${IDENTITY} doing the
|
|
192
|
+
request: 'REQUIRED. What should the Dex analyze, compare, or execute.',
|
|
193
|
+
context: 'REQUIRED. Context entries so the action targets the right ecosystem.',
|
|
194
|
+
name: 'Optional ${IDENTITY} doing the work. Available: ${AVAILABLE_NAMES}.',
|
|
195
195
|
cwd: 'Optional battlefield path (repository root). Defaults to current arena.',
|
|
196
196
|
},
|
|
197
197
|
},
|
|
@@ -225,7 +225,7 @@ export const MISCHIEF_PRESET = {
|
|
|
225
225
|
id: 'mischief',
|
|
226
226
|
identity: 'minion',
|
|
227
227
|
verdict: DEFAULT_VERDICT_CONFIG,
|
|
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`
|
|
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`nn` (Nn——! disposable/experiment) | `oi` -> [`neh` | `nn`]* -> `dayaa` (Dayaa!)',
|
|
229
229
|
nextHints: {
|
|
230
230
|
start: [
|
|
231
231
|
"Inspect the Minion's Work: The [Diff] section shows the first step of the plan.",
|
|
@@ -241,9 +241,9 @@ export const MISCHIEF_PRESET = {
|
|
|
241
241
|
],
|
|
242
242
|
ask: [
|
|
243
243
|
"Intel Stolen: Use '${start}' to launch the scheme, or '${drive}' to exploit this weakness.",
|
|
244
|
-
"Still Puzzled? Send the
|
|
244
|
+
"Still Puzzled? Send the disposable ('${ask}') out again.",
|
|
245
245
|
],
|
|
246
|
-
|
|
246
|
+
closeClaim: [
|
|
247
247
|
'Scheme Successful. The Lair is merged. The Minion is fed.',
|
|
248
248
|
"Plotting something new? Use '${start}' for the next conquest.",
|
|
249
249
|
],
|
|
@@ -257,7 +257,7 @@ export const MISCHIEF_PRESET = {
|
|
|
257
257
|
start: {
|
|
258
258
|
name: 'oi',
|
|
259
259
|
title: 'Oi!',
|
|
260
|
-
description: "Initiate Grand Scheme. Summon a Minion to the Lair (branch).\nYou are the Mastermind; they are the Henchman. Define the Conquest (Goal) with dominance.\nThe minion is bound to this Lair until the plot is realized.\nCall ONCE to start the machine.\n\nFlow: oi → [neh x N] →
|
|
260
|
+
description: "Initiate Grand Scheme. Summon a Minion to the Lair (branch).\nYou are the Mastermind; they are the Henchman. Define the Conquest (Goal) with dominance.\nThe minion is bound to this Lair until the plot is realized.\nCall ONCE to start the machine.\n\nFlow: oi → [neh x N] → dayaa",
|
|
261
261
|
args: {
|
|
262
262
|
title: 'REQUIRED. Operation codename for your grand scheme.',
|
|
263
263
|
goal: 'REQUIRED. The conquest objective. Define the exact end-state your minion must deliver.',
|
|
@@ -272,7 +272,7 @@ export const MISCHIEF_PRESET = {
|
|
|
272
272
|
drive: {
|
|
273
273
|
name: 'neh',
|
|
274
274
|
title: 'Neh...',
|
|
275
|
-
description: 'Crack the Whip. Advance the Plot. Execute the next stage of the Plan.\nDon\'t just fix mistakes; force the scheme forward. The Minion obeys your every word.\nMANDATORY: Inspect the work (git diff) before the next order. Drive them until the World is yours.\n\nFlow: oi → [neh x N] →
|
|
275
|
+
description: 'Crack the Whip. Advance the Plot. Execute the next stage of the Plan.\nDon\'t just fix mistakes; force the scheme forward. The Minion obeys your every word.\nMANDATORY: Inspect the work (git diff) before the next order. Drive them until the World is yours.\n\nFlow: oi → [neh x N] → dayaa',
|
|
276
276
|
args: {
|
|
277
277
|
directive: 'REQUIRED. Precise order for the next phase of the scheme.',
|
|
278
278
|
context: 'Optional newly uncovered secrets or updated briefing.',
|
|
@@ -281,28 +281,28 @@ export const MISCHIEF_PRESET = {
|
|
|
281
281
|
},
|
|
282
282
|
},
|
|
283
283
|
ask: {
|
|
284
|
-
name: '
|
|
285
|
-
title: '
|
|
286
|
-
description: 'Send a
|
|
284
|
+
name: 'nn',
|
|
285
|
+
title: 'Nn——!',
|
|
286
|
+
description: 'Send a Disposable. A stateless mission for intel or sabotage.\nUse this to scout enemy territory, steal documents, or run quick-and-dirty experiments.\nIf it dies, it dies! Just make sure it reports back before it vanishes.',
|
|
287
287
|
args: {
|
|
288
|
-
request: 'REQUIRED. The intel to gather or the
|
|
289
|
-
context: 'REQUIRED. World-state details needed for a sharp
|
|
290
|
-
name: 'Optional ${IDENTITY} to
|
|
288
|
+
request: 'REQUIRED. The intel to gather or the dirty work to execute.',
|
|
289
|
+
context: 'REQUIRED. World-state details needed for a sharp strike.',
|
|
290
|
+
name: 'Optional ${IDENTITY} to handle this business. Available: ${AVAILABLE_NAMES}.',
|
|
291
291
|
cwd: 'Optional lair path (repository root). Defaults to current command chamber.',
|
|
292
292
|
},
|
|
293
293
|
},
|
|
294
294
|
close: {
|
|
295
|
-
name: '
|
|
296
|
-
title: '
|
|
297
|
-
description: 'The Final Reveal. Present your Masterpiece to the Dark Council (System).\nWARNING: The Council destroys failure. If you `CLAIM` with weak plans,
|
|
295
|
+
name: 'dayaa',
|
|
296
|
+
title: 'Dayaa!',
|
|
297
|
+
description: 'The Final Reveal. Present your Masterpiece to the Dark Council (System).\nWARNING: The Council destroys failure. If you `CLAIM` with weak plans, you will be eaten (FORFEIT).\nOnly throw the switch when the Doomsday Device is 100% operational.\n\nFlow: ${START_TOOL_NAME} → [${DRIVE_TOOL_NAME} x N] → ${CLOSE_TOOL_NAME}',
|
|
298
298
|
args: {
|
|
299
299
|
petition: 'REQUIRED. CLAIM demands rule; FORFEIT admits defeat.\nREQUIRES AN ACTIVE SCHEME (started via ${START_TOOL_NAME}).\nIf the plan is weak, improve it with ${DRIVE_TOOL_NAME}.',
|
|
300
300
|
criteriaChecks: 'REQUIRED. Proof of conquest for CLAIM, or reason for self-destruct.',
|
|
301
|
-
score_precise: 'REQUIRED
|
|
302
|
-
score_minimal: 'REQUIRED
|
|
303
|
-
score_isolated: 'REQUIRED
|
|
304
|
-
score_idiomatic: 'REQUIRED
|
|
305
|
-
score_cohesive: 'REQUIRED
|
|
301
|
+
score_precise: 'REQUIRED (0-5). Architectural placement. 5 = exact layer, exact boundary, zero misplacement.',
|
|
302
|
+
score_minimal: 'REQUIRED (0-5). Economy of change. 5 = no avoidable lines, no speculative edits, no hidden bloat.',
|
|
303
|
+
score_isolated: 'REQUIRED (0-5). Surgical containment. 5 = zero unrelated files, zero opportunistic cleanup, zero collateral.',
|
|
304
|
+
score_idiomatic: 'REQUIRED (0-5). Native fluency. 5 = naming, structure, style indistinguishable from the codebase.',
|
|
305
|
+
score_cohesive: 'REQUIRED (0-5). Single responsibility. 5 = each unit does one thing, boundaries intact.',
|
|
306
306
|
oath: "Mastermind's Vow. Required for CLAIM. Verbatim: ${OATH_TEXT}",
|
|
307
307
|
cwd: 'Optional lair path (repository root). Defaults to current command chamber.',
|
|
308
308
|
},
|
package/build/handlers/close.js
CHANGED
|
@@ -24,12 +24,12 @@ export function createCloseHandler() {
|
|
|
24
24
|
oath,
|
|
25
25
|
signal: extra.signal,
|
|
26
26
|
});
|
|
27
|
-
if (petition === "
|
|
27
|
+
if (petition === "CLAIM") {
|
|
28
28
|
if (!("result" in outcome) || outcome.result !== "done") {
|
|
29
|
-
throw new Error("Unexpected
|
|
29
|
+
throw new Error("Unexpected CLAIM outcome shape");
|
|
30
30
|
}
|
|
31
31
|
const finalOutcome = outcome;
|
|
32
|
-
appendDebugLog(`tool close
|
|
32
|
+
appendDebugLog(`tool close CLAIM success branch=${finalOutcome.branch} base=${finalOutcome.baseBranch}`, {
|
|
33
33
|
cwd: workingDir,
|
|
34
34
|
section: "script",
|
|
35
35
|
});
|
|
@@ -45,10 +45,10 @@ export function createCloseHandler() {
|
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
47
|
if (!("result" in outcome) || outcome.result !== "dropped") {
|
|
48
|
-
throw new Error("Unexpected
|
|
48
|
+
throw new Error("Unexpected FORFEIT outcome shape");
|
|
49
49
|
}
|
|
50
50
|
const finalOutcome = outcome;
|
|
51
|
-
appendDebugLog(`tool close
|
|
51
|
+
appendDebugLog(`tool close FORFEIT success branch=${finalOutcome.branch} base=${finalOutcome.baseBranch}`, {
|
|
52
52
|
cwd: workingDir,
|
|
53
53
|
section: "script",
|
|
54
54
|
});
|
|
@@ -22,7 +22,7 @@ export const askToolSchema = z.object({
|
|
|
22
22
|
cwd: z.string().optional(),
|
|
23
23
|
});
|
|
24
24
|
export const closeToolSchema = z.object({
|
|
25
|
-
petition: z.enum(["
|
|
25
|
+
petition: z.enum(["CLAIM", "FORFEIT"]),
|
|
26
26
|
criteriaChecks: z.array(z.string().trim().min(1)).min(1),
|
|
27
27
|
score_precise: z.number().min(0).max(5),
|
|
28
28
|
score_minimal: z.number().min(0).max(5),
|
package/build/utils/git.js
CHANGED
|
@@ -160,20 +160,42 @@ export async function commit(cwd, message) {
|
|
|
160
160
|
throw wrapGitError(args.join(" "), err, cwd);
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
|
-
export async function merge(cwd, branchName, message) {
|
|
163
|
+
export async function merge(cwd, branchName, message, options = {}) {
|
|
164
164
|
const git = createGit(cwd);
|
|
165
|
+
const { squash = true } = options;
|
|
165
166
|
const skipVerify = readBooleanEnv("KEIYAKU_GIT_NO_VERIFY", true);
|
|
166
167
|
const noGpgSign = readBooleanEnv("KEIYAKU_GIT_NO_GPG_SIGN", true);
|
|
167
|
-
|
|
168
|
+
if (!squash) {
|
|
169
|
+
const mergeOptions = [branchName, "--no-ff", "--no-edit", "-m", message];
|
|
170
|
+
if (skipVerify)
|
|
171
|
+
mergeOptions.push("--no-verify");
|
|
172
|
+
if (noGpgSign)
|
|
173
|
+
mergeOptions.push("--no-gpg-sign");
|
|
174
|
+
try {
|
|
175
|
+
await git.merge(mergeOptions);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
throw wrapGitError(`merge ${mergeOptions.join(" ")}`, err, cwd);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const squashArgs = ["merge", "--squash", branchName];
|
|
183
|
+
try {
|
|
184
|
+
await git.raw(squashArgs);
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
throw wrapGitError(squashArgs.join(" "), err, cwd);
|
|
188
|
+
}
|
|
189
|
+
const commitArgs = ["commit", "--allow-empty", "-m", message];
|
|
168
190
|
if (skipVerify)
|
|
169
|
-
|
|
191
|
+
commitArgs.push("--no-verify");
|
|
170
192
|
if (noGpgSign)
|
|
171
|
-
|
|
193
|
+
commitArgs.push("--no-gpg-sign");
|
|
172
194
|
try {
|
|
173
|
-
await git.
|
|
195
|
+
await git.raw(commitArgs);
|
|
174
196
|
}
|
|
175
197
|
catch (err) {
|
|
176
|
-
throw wrapGitError(
|
|
198
|
+
throw wrapGitError(commitArgs.join(" "), err, cwd);
|
|
177
199
|
}
|
|
178
200
|
}
|
|
179
201
|
export async function getUnmergedFiles(cwd) {
|
|
@@ -61,9 +61,9 @@ function truncateForMessage(text, maxChars) {
|
|
|
61
61
|
async function buildActiveKeiyakuStartMessage(cwd, branch) {
|
|
62
62
|
const lines = [
|
|
63
63
|
`active keiyaku already exists (${branch})`,
|
|
64
|
-
"This task is still active. Decide whether to continue or
|
|
64
|
+
"This task is still active. Decide whether to continue or forfeit it.",
|
|
65
65
|
"Continue: run drive on the current keiyaku branch.",
|
|
66
|
-
"
|
|
66
|
+
"Forfeit: run close with petition=FORFEIT to drop the branch.",
|
|
67
67
|
];
|
|
68
68
|
try {
|
|
69
69
|
const keiyakuPath = path.join(cwd, KEIYAKU_FILE);
|
|
@@ -399,7 +399,21 @@ export async function handleAsk(input) {
|
|
|
399
399
|
const prompt = buildAskPrompt(request, context);
|
|
400
400
|
// TODO: enforce read-only access and persist summary to .keiyaku/notes/.
|
|
401
401
|
const summary = await runSubagent(selectSubagent(name), prompt, cwd, 0, signal);
|
|
402
|
-
|
|
402
|
+
let branch;
|
|
403
|
+
let diffStats;
|
|
404
|
+
try {
|
|
405
|
+
branch = await git.getCurrentBranch(cwd);
|
|
406
|
+
if (branch && branch.startsWith("keiyaku/")) {
|
|
407
|
+
const baseBranch = await git.getKeiyakuBase(cwd, branch);
|
|
408
|
+
if (baseBranch) {
|
|
409
|
+
diffStats = await git.getDiffPreviewText(cwd, baseBranch);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
branch = undefined;
|
|
415
|
+
}
|
|
416
|
+
return { summary, branch, diffStats };
|
|
403
417
|
}
|
|
404
418
|
export async function handleClose(input) {
|
|
405
419
|
const { cwd } = input;
|
|
@@ -417,7 +431,7 @@ export async function handleClose(input) {
|
|
|
417
431
|
}
|
|
418
432
|
const title = keiyakuBranch.slice("keiyaku/".length);
|
|
419
433
|
const petition = input.petition;
|
|
420
|
-
if (petition === "
|
|
434
|
+
if (petition === "FORFEIT") {
|
|
421
435
|
let round = 0;
|
|
422
436
|
try {
|
|
423
437
|
const trace = await fs.readFile(path.join(cwd, TRACE_FILE), "utf-8");
|
|
@@ -433,7 +447,7 @@ export async function handleClose(input) {
|
|
|
433
447
|
await git.clearKeiyakuBase(cwd, keiyakuBranch);
|
|
434
448
|
}
|
|
435
449
|
catch (err) {
|
|
436
|
-
throw wrapFlowError(`execute
|
|
450
|
+
throw wrapFlowError(`execute FORFEIT (${keiyakuBranch} -> ${baseBranch})`, err);
|
|
437
451
|
}
|
|
438
452
|
return {
|
|
439
453
|
status: "success",
|
|
@@ -441,12 +455,12 @@ export async function handleClose(input) {
|
|
|
441
455
|
round,
|
|
442
456
|
branch: keiyakuBranch,
|
|
443
457
|
baseBranch,
|
|
444
|
-
diff: "
|
|
458
|
+
diff: "Forfeited without merge.",
|
|
445
459
|
};
|
|
446
460
|
}
|
|
447
461
|
await ensureKeiyakuFiles(cwd);
|
|
448
462
|
const traceContent = await readTraceContent(cwd);
|
|
449
|
-
if (petition === "
|
|
463
|
+
if (petition === "CLAIM") {
|
|
450
464
|
const verdict = await resolveVerdictConfig(cwd);
|
|
451
465
|
const failedCommandments = CLOSE_SCORE_FIELDS.flatMap((field) => {
|
|
452
466
|
const score = input[field];
|
|
@@ -476,15 +490,15 @@ export async function handleClose(input) {
|
|
|
476
490
|
}
|
|
477
491
|
await assertCleanWorkingTree(cwd);
|
|
478
492
|
try {
|
|
479
|
-
const invokeDiffLog = `[
|
|
493
|
+
const invokeDiffLog = `[CLAIM] Collecting diff preview against base '${baseBranch}'`;
|
|
480
494
|
console.error(invokeDiffLog);
|
|
481
495
|
appendDebugLog(invokeDiffLog, { cwd, section: "script" });
|
|
482
|
-
const invokeReadLog = "[
|
|
496
|
+
const invokeReadLog = "[CLAIM] Reading keiyaku protocol files";
|
|
483
497
|
console.error(invokeReadLog);
|
|
484
498
|
appendDebugLog(invokeReadLog, { cwd, section: "script" });
|
|
485
499
|
const keiyakuContent = await fs.readFile(path.join(cwd, KEIYAKU_FILE), "utf-8");
|
|
486
500
|
const message = buildMergeMessage(title, keiyakuContent, traceContent);
|
|
487
|
-
const invokeCleanupLog = "[
|
|
501
|
+
const invokeCleanupLog = "[CLAIM] Removing protocol files and creating cleanup commit";
|
|
488
502
|
console.error(invokeCleanupLog);
|
|
489
503
|
appendDebugLog(invokeCleanupLog, { cwd, section: "script" });
|
|
490
504
|
await fs.unlink(path.join(cwd, KEIYAKU_FILE));
|
|
@@ -492,18 +506,18 @@ export async function handleClose(input) {
|
|
|
492
506
|
await git.addFiles(cwd, "-A");
|
|
493
507
|
await git.commit(cwd, `keiyaku(${title}): cleanup`);
|
|
494
508
|
const diff = await git.getDiffPreviewText(cwd, baseBranch);
|
|
495
|
-
const invokeCheckoutLog = `[
|
|
509
|
+
const invokeCheckoutLog = `[CLAIM] Checking out base branch '${baseBranch}'`;
|
|
496
510
|
console.error(invokeCheckoutLog);
|
|
497
511
|
appendDebugLog(invokeCheckoutLog, { cwd, section: "script" });
|
|
498
512
|
await git.checkoutBranch(cwd, baseBranch);
|
|
499
|
-
const invokeMergeLog = `[
|
|
513
|
+
const invokeMergeLog = `[CLAIM] Merging '${keiyakuBranch}' into '${baseBranch}'`;
|
|
500
514
|
console.error(invokeMergeLog);
|
|
501
515
|
appendDebugLog(invokeMergeLog, { cwd, section: "script" });
|
|
502
516
|
await git.merge(cwd, keiyakuBranch, message);
|
|
503
|
-
const invokeFinalizeLog = `[
|
|
517
|
+
const invokeFinalizeLog = `[CLAIM] Deleting merged branch '${keiyakuBranch}' and clearing metadata`;
|
|
504
518
|
console.error(invokeFinalizeLog);
|
|
505
519
|
appendDebugLog(invokeFinalizeLog, { cwd, section: "script" });
|
|
506
|
-
await git.deleteBranch(cwd, keiyakuBranch);
|
|
520
|
+
await git.deleteBranch(cwd, keiyakuBranch, true);
|
|
507
521
|
await git.clearKeiyakuBase(cwd, keiyakuBranch);
|
|
508
522
|
const round = computeTraceState(traceContent).maxRound;
|
|
509
523
|
return {
|
|
@@ -518,9 +532,9 @@ export async function handleClose(input) {
|
|
|
518
532
|
catch (err) {
|
|
519
533
|
const conflictFiles = await git.getUnmergedFiles(cwd);
|
|
520
534
|
if (conflictFiles.length > 0) {
|
|
521
|
-
throw new FlowError("DONE_MERGE_CONFLICT", `
|
|
535
|
+
throw new FlowError("DONE_MERGE_CONFLICT", `CLAIM merge conflict between ${keiyakuBranch} and ${baseBranch} (CONFLICTS: ${conflictFiles.join(", ")})`, err);
|
|
522
536
|
}
|
|
523
|
-
throw wrapFlowError(`execute
|
|
537
|
+
throw wrapFlowError(`execute CLAIM (merge ${keiyakuBranch} into ${baseBranch})`, err);
|
|
524
538
|
}
|
|
525
539
|
}
|
|
526
540
|
throw new Error(`unsupported close petition: ${petition}`);
|
|
@@ -31,8 +31,33 @@ function buildSection(title, content) {
|
|
|
31
31
|
return "";
|
|
32
32
|
return `[${title}]\n${validLines.join("\n")}`;
|
|
33
33
|
}
|
|
34
|
+
function supportsHeaderColor() {
|
|
35
|
+
if (process.env.NO_COLOR !== undefined)
|
|
36
|
+
return false;
|
|
37
|
+
if (process.env.FORCE_COLOR === "0")
|
|
38
|
+
return false;
|
|
39
|
+
if (process.env.FORCE_COLOR !== undefined)
|
|
40
|
+
return true;
|
|
41
|
+
if (!process.stdout?.isTTY)
|
|
42
|
+
return false;
|
|
43
|
+
const term = process.env.TERM?.toLowerCase();
|
|
44
|
+
return term !== undefined && term !== "dumb";
|
|
45
|
+
}
|
|
46
|
+
function colorizeHeaderTitle(titleLabel) {
|
|
47
|
+
return `\x1b[1;38;5;15;48;5;23m${titleLabel}\x1b[0m`;
|
|
48
|
+
}
|
|
34
49
|
function formatHeader(title) {
|
|
35
|
-
|
|
50
|
+
const headerWidth = 60;
|
|
51
|
+
const normalizedTitle = title.trim().toUpperCase();
|
|
52
|
+
const titleLabel = ` ${normalizedTitle} `;
|
|
53
|
+
if (!supportsHeaderColor()) {
|
|
54
|
+
const plainPrefix = `──${titleLabel}`;
|
|
55
|
+
const divider = "─".repeat(Math.max(0, headerWidth - plainPrefix.length));
|
|
56
|
+
return divider.length > 0 ? `\n${plainPrefix}${divider}` : `\n${plainPrefix}`;
|
|
57
|
+
}
|
|
58
|
+
const coloredTitle = colorizeHeaderTitle(titleLabel);
|
|
59
|
+
const divider = "─".repeat(Math.max(0, headerWidth - titleLabel.length - 1));
|
|
60
|
+
return divider.length > 0 ? `\n${coloredTitle} ${divider}` : `\n${coloredTitle}`;
|
|
36
61
|
}
|
|
37
62
|
function assembleResponse(status, summary, sections, nextHints, infoLines = []) {
|
|
38
63
|
// Main Status Block
|
|
@@ -173,7 +198,11 @@ export function buildAskResponse(result, input) {
|
|
|
173
198
|
];
|
|
174
199
|
const nextHints = renderHints(resolveTermPreset().nextHints.ask);
|
|
175
200
|
const inputSection = buildSection("Input", inputEcho);
|
|
176
|
-
const infoLines = [
|
|
201
|
+
const infoLines = [
|
|
202
|
+
...formatMaybe("CWD", input.cwd, 300),
|
|
203
|
+
...formatMaybe("Current Branch", result.branch, 200),
|
|
204
|
+
...formatMaybe("Diff Stats", result.diffStats, 1000),
|
|
205
|
+
];
|
|
177
206
|
const text = assembleResponse("Answered", result.summary, [inputSection], nextHints, infoLines);
|
|
178
207
|
return {
|
|
179
208
|
content: [{ type: "text", text }],
|
|
@@ -189,13 +218,13 @@ export function buildCloseDoneResponse(result, input) {
|
|
|
189
218
|
const { status: _status, ...resultData } = result;
|
|
190
219
|
const closeToolName = getCloseToolName();
|
|
191
220
|
const inputEcho = [
|
|
192
|
-
`Petition:
|
|
221
|
+
`Petition: CLAIM`,
|
|
193
222
|
...formatList("Criteria Checks", input.criteriaChecks, { maxItems: 10, maxItemChars: 220 }),
|
|
194
223
|
`Scores: precise=${input.score_precise}/5 minimal=${input.score_minimal}/5 isolated=${input.score_isolated}/5 idiomatic=${input.score_idiomatic}/5 cohesive=${input.score_cohesive}/5`,
|
|
195
224
|
...formatMaybe("Oath", input.oath, 220),
|
|
196
225
|
...formatMaybe("CWD", input.cwd, 300),
|
|
197
226
|
];
|
|
198
|
-
const nextHints = renderHints(resolveTermPreset().nextHints.
|
|
227
|
+
const nextHints = renderHints(resolveTermPreset().nextHints.closeClaim);
|
|
199
228
|
const inputSection = buildSection("Input", inputEcho);
|
|
200
229
|
const diffSection = result.diff ? buildSection("Diff", result.diff) : "";
|
|
201
230
|
const infoLines = [
|
|
@@ -203,7 +232,7 @@ export function buildCloseDoneResponse(result, input) {
|
|
|
203
232
|
...formatMaybe("Current Branch", result.branch, 200),
|
|
204
233
|
...formatMaybe("Base Branch", result.baseBranch, 200),
|
|
205
234
|
];
|
|
206
|
-
const text = assembleResponse("Keiyaku Fulfilled (
|
|
235
|
+
const text = assembleResponse("Keiyaku Fulfilled (CLAIM)", `Merged '${result.branch}' into '${result.baseBranch}'. Deleted feature branch.`, [inputSection, diffSection], nextHints, infoLines);
|
|
207
236
|
return {
|
|
208
237
|
content: [{ type: "text", text }],
|
|
209
238
|
structuredContent: buildSuccessStructuredContent(closeToolName, {
|
|
@@ -218,7 +247,7 @@ export function buildCloseDropResponse(result, input) {
|
|
|
218
247
|
const { status: _status, ...resultData } = result;
|
|
219
248
|
const closeToolName = getCloseToolName();
|
|
220
249
|
const inputEcho = [
|
|
221
|
-
`Petition:
|
|
250
|
+
`Petition: FORFEIT`,
|
|
222
251
|
...formatList("Reasons/Checks", input.criteriaChecks, { maxItems: 10, maxItemChars: 220 }),
|
|
223
252
|
`Scores: precise=${input.score_precise}/5 minimal=${input.score_minimal}/5 isolated=${input.score_isolated}/5 idiomatic=${input.score_idiomatic}/5 cohesive=${input.score_cohesive}/5`,
|
|
224
253
|
...formatMaybe("CWD", input.cwd, 300),
|
|
@@ -230,7 +259,7 @@ export function buildCloseDropResponse(result, input) {
|
|
|
230
259
|
...formatMaybe("Current Branch", result.branch, 200),
|
|
231
260
|
...formatMaybe("Base Branch", result.baseBranch, 200),
|
|
232
261
|
];
|
|
233
|
-
const text = assembleResponse("Keiyaku
|
|
262
|
+
const text = assembleResponse("Keiyaku Forfeited (FORFEIT)", `Deleted '${result.branch}'. Switched back to '${result.baseBranch}'.`, [inputSection], nextHints, infoLines);
|
|
234
263
|
return {
|
|
235
264
|
content: [{ type: "text", text }],
|
|
236
265
|
structuredContent: buildSuccessStructuredContent(closeToolName, {
|