@astrosheep/keiyaku 0.1.12 → 0.1.14
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 +4 -0
- package/build/common/errors.js +7 -5
- package/build/config/term-presets.js +10 -13
- package/build/handlers/ask.js +1 -1
- package/build/handlers/close.js +8 -3
- package/build/handlers/drive.js +1 -1
- package/build/handlers/help.js +1 -5
- package/build/handlers/start.js +1 -1
- package/build/index.js +1 -1
- package/build/types/{tool-schemas.js → tooling.js} +2 -3
- package/build/utils/git-diff.js +2 -2
- package/build/utils/text-utils.js +3 -5
- package/build/workflow/ask.js +18 -1
- package/build/workflow/contract.js +5 -7
- package/build/workflow/drive.js +14 -2
- package/build/workflow/present.js +8 -0
- package/build/workflow/prompts.js +5 -1
- package/build/workflow/response-builders.js +50 -52
- package/build/workflow/summon.js +10 -16
- package/package.json +1 -1
- /package/build/types/{workflow-types.js → workflow.js} +0 -0
package/README.md
CHANGED
|
@@ -90,6 +90,10 @@ 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
|
+
Project-level law lives in:
|
|
94
|
+
- `.keiyaku/base-constraints.md`: Global constraints inherited by every keiyaku.
|
|
95
|
+
- `ask` also injects `.keiyaku/base-constraints.md` as **reference context** (it may be unrelated to some asks).
|
|
96
|
+
|
|
93
97
|
*Note: These files are automatically cleaned up (or committed) when you `present` (or preset equivalent) the keiyaku.*
|
|
94
98
|
|
|
95
99
|
---
|
package/build/common/errors.js
CHANGED
|
@@ -29,13 +29,15 @@ export function wrapFlowError(action, err) {
|
|
|
29
29
|
return new Error(`${action} failed: ${asMessage(err)}`);
|
|
30
30
|
}
|
|
31
31
|
function hintForFlowCode(code, message) {
|
|
32
|
+
const preset = resolveTermPreset();
|
|
33
|
+
const { drive, close } = preset.tools;
|
|
32
34
|
switch (code) {
|
|
33
35
|
case "NOT_GIT_REPO":
|
|
34
36
|
return "The provided `cwd` is not a git repository.";
|
|
35
37
|
case "ACTIVE_KEIYAKU_EXISTS":
|
|
36
|
-
return
|
|
38
|
+
return message;
|
|
37
39
|
case "EXISTING_KEIYAKU_BRANCH_FOUND":
|
|
38
|
-
return
|
|
40
|
+
return `\`${preset.tools.start.name}\` blocked: Existing branches detected. \`git switch\` to one and \`${close.name}\` it, or choose a different \`title\`.`;
|
|
39
41
|
case "EMPTY_PARAM":
|
|
40
42
|
return "One or more required parameters are empty.";
|
|
41
43
|
case "DIRTY_WORKTREE":
|
|
@@ -43,13 +45,13 @@ function hintForFlowCode(code, message) {
|
|
|
43
45
|
case "NOT_ACTIVE_KEIYAKU_BRANCH":
|
|
44
46
|
return message;
|
|
45
47
|
case "MISSING_KEIYAKU_BASE":
|
|
46
|
-
return
|
|
48
|
+
return `Current branch is missing \`keiyakuBase\` metadata. This branch might not have been created via \`${preset.tools.start.name}\`.`;
|
|
47
49
|
case "MISSING_PROTOCOL_FILES":
|
|
48
50
|
return "Required protocol files are missing (`KEIYAKU.md` or `KEIYAKU_TRACE.md`).";
|
|
49
51
|
case "DONE_MERGE_CONFLICT":
|
|
50
|
-
return
|
|
52
|
+
return `Merge conflict detected while merging into the base branch during \`CLAIM\`. Resolve the conflicts, commit the changes, and then run \`${close.name}\` again.`;
|
|
51
53
|
case "CLOSE_QUALITY_GATE_FAILED":
|
|
52
|
-
return
|
|
54
|
+
return `CLAIM denied: Inadequate work. Scores are below the required standard. Use \`${drive.name}\` to refine the mission until it is worthy of merging.`;
|
|
53
55
|
case "OATH_MISMATCH":
|
|
54
56
|
return message;
|
|
55
57
|
case "SUBAGENT_DID_NOT_ADVANCE_ROUND":
|
|
@@ -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
|
|
22
|
+
usageGuide: '## Workflow\n**You are the Architect.** You use these tools to command the Servants.\n\n`ask` (anytime) | `summon` -> [`drive` | `ask`]* -> `present`\n\n## 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.**',
|
|
23
23
|
nextHints: {
|
|
24
24
|
start: [
|
|
25
25
|
'Keiyaku signed. The Servant is bound to this branch until release.',
|
|
@@ -84,7 +84,7 @@ export const DEFAULT_PRESET = {
|
|
|
84
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
|
-
context: 'REQUIRED. Relevant background or data the servant needs to execute the request.',
|
|
87
|
+
context: 'REQUIRED. Relevant background or data the servant needs to execute the request. Note: .keiyaku/base-constraints.md is also injected as reference context (may be unrelated).',
|
|
88
88
|
name: 'Optional ${identity} profile to perform this task. Available: ${available_names}.',
|
|
89
89
|
cwd: "Optional repository path. Defaults to the server's current working directory.",
|
|
90
90
|
},
|
|
@@ -102,6 +102,7 @@ export const DEFAULT_PRESET = {
|
|
|
102
102
|
args: {
|
|
103
103
|
petition: 'REQUIRED. CLAIM declares fulfillment; FORFEIT concedes failure.\nREQUIRES AN ACTIVE KEIYAKU (started via ${start}).\nIf any score wavers, do not claim—return to ${drive}.',
|
|
104
104
|
criteriaChecks: 'REQUIRED. For CLAIM: evidence that each criterion is met. For FORFEIT: honest account of what remains unfinished.',
|
|
105
|
+
constraintsChecks: 'REQUIRED. For CLAIM: evidence that each constraint stayed compliant. For FORFEIT: list known violations or unresolved risk.',
|
|
105
106
|
score_precise: 'REQUIRED (0-5). Architectural placement. 5 = exact layer, exact boundary, zero misplacement.',
|
|
106
107
|
score_minimal: 'REQUIRED (0-5). Economy of change. 5 = no avoidable lines, no speculative edits, no hidden bloat.',
|
|
107
108
|
score_isolated: 'REQUIRED (0-5). Surgical containment. 5 = zero unrelated files, zero opportunistic cleanup, zero collateral.',
|
|
@@ -115,9 +116,7 @@ export const DEFAULT_PRESET = {
|
|
|
115
116
|
name: 'help',
|
|
116
117
|
title: 'Protocol Codex',
|
|
117
118
|
description: 'Consult the Architect\'s Codex. Clarify the Laws of the Keiyaku and the standard Workflow.\nUse this to understand the constraints of the reality you command.',
|
|
118
|
-
args: {
|
|
119
|
-
question: 'REQUIRED. The specific Protocol, Law, or Workflow detail to clarify.',
|
|
120
|
-
},
|
|
119
|
+
args: {},
|
|
121
120
|
},
|
|
122
121
|
},
|
|
123
122
|
};
|
|
@@ -186,7 +185,7 @@ export const POCKET_PRESET = {
|
|
|
186
185
|
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.',
|
|
187
186
|
args: {
|
|
188
187
|
request: 'REQUIRED. What should the Dex analyze, compare, or execute.',
|
|
189
|
-
context: 'REQUIRED. Context entries so the action targets the right ecosystem.',
|
|
188
|
+
context: 'REQUIRED. Context entries so the action targets the right ecosystem. Note: .keiyaku/base-constraints.md is also injected as reference context (may be unrelated).',
|
|
190
189
|
name: 'Optional ${identity} doing the work. Available: ${available_names}.',
|
|
191
190
|
cwd: 'Optional battlefield path (repository root). Defaults to current arena.',
|
|
192
191
|
},
|
|
@@ -198,6 +197,7 @@ export const POCKET_PRESET = {
|
|
|
198
197
|
args: {
|
|
199
198
|
petition: 'REQUIRED. CLAIM seeks Badge; FORFEIT forfeits the match.\nREQUIRES AN ACTIVE BATTLE (started via ${start}).\nIf stats are low, continue with ${drive}.',
|
|
200
199
|
criteriaChecks: 'REQUIRED. Badge-by-badge proof for CLAIM, or reason for Forfeit.',
|
|
200
|
+
constraintsChecks: 'REQUIRED. Rule-by-rule proof that constraints were respected, or known breaks when forfeiting.',
|
|
201
201
|
score_precise: 'REQUIRED score (0-5). 5 means a Critical Hit: exact layer, exact target, zero meaningful misplacement.',
|
|
202
202
|
score_minimal: 'REQUIRED score (0-5). 5 means Max Efficiency: no wasted PP, no extra motion.',
|
|
203
203
|
score_isolated: 'REQUIRED score (0-5). 5 means 1v1 Focus: zero side-quests, zero unrelated damage.',
|
|
@@ -211,9 +211,7 @@ export const POCKET_PRESET = {
|
|
|
211
211
|
name: 'help',
|
|
212
212
|
title: 'Trainer Guide',
|
|
213
213
|
description: 'Consult the League Rules. Clarify the Battle System and Turn Structure.',
|
|
214
|
-
args: {
|
|
215
|
-
question: 'REQUIRED. The Pocket Battle System question you want answered.',
|
|
216
|
-
},
|
|
214
|
+
args: {},
|
|
217
215
|
},
|
|
218
216
|
},
|
|
219
217
|
};
|
|
@@ -282,7 +280,7 @@ export const MISCHIEF_PRESET = {
|
|
|
282
280
|
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.',
|
|
283
281
|
args: {
|
|
284
282
|
request: 'REQUIRED. The intel to gather or the dirty work to execute.',
|
|
285
|
-
context: 'REQUIRED. World-state details needed for a sharp strike.',
|
|
283
|
+
context: 'REQUIRED. World-state details needed for a sharp strike. Note: .keiyaku/base-constraints.md is also injected as reference context (may be unrelated).',
|
|
286
284
|
name: 'Optional ${identity} to handle this business. Available: ${available_names}.',
|
|
287
285
|
cwd: 'Optional lair path (repository root). Defaults to current command chamber.',
|
|
288
286
|
},
|
|
@@ -294,6 +292,7 @@ export const MISCHIEF_PRESET = {
|
|
|
294
292
|
args: {
|
|
295
293
|
petition: 'REQUIRED. CLAIM demands rule; FORFEIT admits defeat.\nREQUIRES AN ACTIVE SCHEME (started via ${start}).\nIf the plan is weak, improve it with ${drive}.',
|
|
296
294
|
criteriaChecks: 'REQUIRED. Proof of conquest for CLAIM, or reason for self-destruct.',
|
|
295
|
+
constraintsChecks: 'REQUIRED. Constraint-by-constraint compliance proof for CLAIM, or confession of violations for FORFEIT.',
|
|
297
296
|
score_precise: 'REQUIRED (0-5). Architectural placement. 5 = exact layer, exact boundary, zero misplacement.',
|
|
298
297
|
score_minimal: 'REQUIRED (0-5). Economy of change. 5 = no avoidable lines, no speculative edits, no hidden bloat.',
|
|
299
298
|
score_isolated: 'REQUIRED (0-5). Surgical containment. 5 = zero unrelated files, zero opportunistic cleanup, zero collateral.',
|
|
@@ -307,9 +306,7 @@ export const MISCHIEF_PRESET = {
|
|
|
307
306
|
name: 'help',
|
|
308
307
|
title: 'Nani?!',
|
|
309
308
|
description: 'Consult the Evil Overlord List. Review the Laws of the Lair.',
|
|
310
|
-
args: {
|
|
311
|
-
question: 'REQUIRED. The law or protocol detail you want the realm to explain.',
|
|
312
|
-
},
|
|
309
|
+
args: {},
|
|
313
310
|
},
|
|
314
311
|
},
|
|
315
312
|
};
|
package/build/handlers/ask.js
CHANGED
package/build/handlers/close.js
CHANGED
|
@@ -4,11 +4,12 @@ import { buildCloseDoneResponse, buildCloseDropResponse, } from "../workflow/res
|
|
|
4
4
|
import { resolveTermPreset } from "../config/term-presets.js";
|
|
5
5
|
import { handleToolError } from "./shared.js";
|
|
6
6
|
export function createCloseHandler() {
|
|
7
|
-
return async ({ petition, criteriaChecks, score_precise, score_minimal, score_isolated, score_idiomatic, score_cohesive, oath, cwd, }, extra) => {
|
|
7
|
+
return async ({ petition, criteriaChecks, constraintsChecks, 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;
|
|
10
|
+
const constraintsCheckParts = constraintsChecks ?? [];
|
|
10
11
|
try {
|
|
11
|
-
appendDebugLog(`tool close start petition=${petition} cwd=${workingDir} criteriaChecks=${criteriaCheckParts.length}`, {
|
|
12
|
+
appendDebugLog(`tool close start petition=${petition} cwd=${workingDir} criteriaChecks=${criteriaCheckParts.length} constraintsChecks=${constraintsCheckParts.length}`, {
|
|
12
13
|
cwd: workingDir,
|
|
13
14
|
section: "script",
|
|
14
15
|
});
|
|
@@ -16,6 +17,7 @@ export function createCloseHandler() {
|
|
|
16
17
|
cwd: workingDir,
|
|
17
18
|
petition,
|
|
18
19
|
criteriaChecks: criteriaCheckParts,
|
|
20
|
+
constraintsChecks: constraintsCheckParts,
|
|
19
21
|
score_precise,
|
|
20
22
|
score_minimal,
|
|
21
23
|
score_isolated,
|
|
@@ -35,6 +37,7 @@ export function createCloseHandler() {
|
|
|
35
37
|
});
|
|
36
38
|
return buildCloseDoneResponse(finalOutcome, {
|
|
37
39
|
criteriaChecks: criteriaCheckParts,
|
|
40
|
+
constraintsChecks: constraintsCheckParts,
|
|
38
41
|
score_precise,
|
|
39
42
|
score_minimal,
|
|
40
43
|
score_isolated,
|
|
@@ -54,6 +57,7 @@ export function createCloseHandler() {
|
|
|
54
57
|
});
|
|
55
58
|
return buildCloseDropResponse(finalOutcome, {
|
|
56
59
|
criteriaChecks: criteriaCheckParts,
|
|
60
|
+
constraintsChecks: constraintsCheckParts,
|
|
57
61
|
score_precise,
|
|
58
62
|
score_minimal,
|
|
59
63
|
score_isolated,
|
|
@@ -73,9 +77,10 @@ export function createCloseHandler() {
|
|
|
73
77
|
inputEcho: [
|
|
74
78
|
`Petition: ${petition}`,
|
|
75
79
|
`Criteria checks (${criteriaCheckParts.length}): ${criteriaCheckParts.join("; ")}`,
|
|
80
|
+
`Constraints checks (${constraintsCheckParts.length}): ${constraintsCheckParts.join("; ")}`,
|
|
76
81
|
`Scores: precise=${score_precise} minimal=${score_minimal} isolated=${score_isolated} idiomatic=${score_idiomatic} cohesive=${score_cohesive}`,
|
|
77
82
|
...(oath ? [`Oath: ${oath}`] : []),
|
|
78
|
-
`
|
|
83
|
+
`Path: ${workingDir}`,
|
|
79
84
|
],
|
|
80
85
|
});
|
|
81
86
|
}
|
package/build/handlers/drive.js
CHANGED
package/build/handlers/help.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { buildHelpResponse } from "../workflow/response-builders.js";
|
|
2
2
|
export function createHelpHandler(preset) {
|
|
3
|
-
return async ({
|
|
3
|
+
return async ({}) => {
|
|
4
4
|
const helpContent = [
|
|
5
5
|
"# Keiyaku System Help",
|
|
6
6
|
"",
|
|
7
7
|
"## Core Files (.keiyaku/)",
|
|
8
8
|
"These files define the 'Law' of the project. **CRITICAL**: Use Markdown level 2 headers (## header).",
|
|
9
9
|
"",
|
|
10
|
-
"- **base-criteria.md**: Universal standards for task completion. Automatically inherited.",
|
|
11
10
|
"- **base-constraints.md**: Mandatory architectural boundaries and coding standards.",
|
|
12
11
|
"",
|
|
13
12
|
preset.usageGuide,
|
|
@@ -15,12 +14,9 @@ export function createHelpHandler(preset) {
|
|
|
15
14
|
"## Protocol Files",
|
|
16
15
|
"- **KEIYAKU.md**: The immutable mission definition for the current task.",
|
|
17
16
|
"- **KEIYAKU_TRACE.md**: The audit log of all rounds and reviews.",
|
|
18
|
-
"",
|
|
19
|
-
`Regarding: \"${question}\"`,
|
|
20
17
|
].join("\n");
|
|
21
18
|
return buildHelpResponse({
|
|
22
19
|
tool: preset.tools.help.name,
|
|
23
|
-
question,
|
|
24
20
|
text: helpContent,
|
|
25
21
|
});
|
|
26
22
|
};
|
package/build/handlers/start.js
CHANGED
|
@@ -49,7 +49,7 @@ export function createStartHandler() {
|
|
|
49
49
|
...(context ? [`Context: ${context}`] : []),
|
|
50
50
|
...(constraints ? [`Constraints: ${constraints}`] : []),
|
|
51
51
|
...(name ? [`${preset.identity}: ${name}`] : []),
|
|
52
|
-
`
|
|
52
|
+
`Path: ${workingDir}`,
|
|
53
53
|
],
|
|
54
54
|
});
|
|
55
55
|
}
|
package/build/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
5
5
|
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
|
|
6
6
|
import { readFileSync } from "node:fs";
|
|
7
7
|
import { resolveOath } from "./workflow/contract.js";
|
|
8
|
-
import { askToolSchema, startToolSchema, driveToolSchema, closeToolSchema, helpToolSchema, } from "./types/
|
|
8
|
+
import { askToolSchema, startToolSchema, driveToolSchema, closeToolSchema, helpToolSchema, } from "./types/tooling.js";
|
|
9
9
|
import { listTermPresets, resolveTermPreset, getAvailableNamesForPreset } from "./config/term-presets.js";
|
|
10
10
|
import { renderPreset } from "./utils/text-utils.js";
|
|
11
11
|
import { applyArgumentDescriptions } from "./utils/schema-utils.js";
|
|
@@ -24,6 +24,7 @@ export const askToolSchema = z.object({
|
|
|
24
24
|
export const closeToolSchema = z.object({
|
|
25
25
|
petition: z.enum(["CLAIM", "FORFEIT"]),
|
|
26
26
|
criteriaChecks: z.array(z.string().trim().min(1)).min(1),
|
|
27
|
+
constraintsChecks: z.array(z.string().trim().min(1)).min(1),
|
|
27
28
|
score_precise: z.number().min(0).max(5),
|
|
28
29
|
score_minimal: z.number().min(0).max(5),
|
|
29
30
|
score_isolated: z.number().min(0).max(5),
|
|
@@ -32,6 +33,4 @@ export const closeToolSchema = z.object({
|
|
|
32
33
|
oath: z.string().optional(),
|
|
33
34
|
cwd: z.string().optional(),
|
|
34
35
|
});
|
|
35
|
-
export const helpToolSchema = z.object({
|
|
36
|
-
question: z.string(),
|
|
37
|
-
});
|
|
36
|
+
export const helpToolSchema = z.object({});
|
package/build/utils/git-diff.js
CHANGED
|
@@ -183,8 +183,8 @@ export async function getIncrementalDiff(cwd) {
|
|
|
183
183
|
if (sections.length === 0)
|
|
184
184
|
return "No changes in last round.";
|
|
185
185
|
const filePreviews = [];
|
|
186
|
-
const MAX_TOTAL_CHARS =
|
|
187
|
-
const MAX_LINES_PER_FILE =
|
|
186
|
+
const MAX_TOTAL_CHARS = 15000;
|
|
187
|
+
const MAX_LINES_PER_FILE = 50;
|
|
188
188
|
let omittedFiles = 0;
|
|
189
189
|
for (let i = 0; i < sections.length; i += 1) {
|
|
190
190
|
const section = sections[i];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const BASE_CONSTRAINTS_SOURCE = ".keiyaku/base-constraints.md";
|
|
1
2
|
function isMarkdownHeaderLine(line) {
|
|
2
3
|
return /^\s*#{2,}\s+/.test(line);
|
|
3
4
|
}
|
|
@@ -97,17 +98,14 @@ export function parseMarkdownHeaders(text) {
|
|
|
97
98
|
}
|
|
98
99
|
return sections.filter((section) => section.length > 0);
|
|
99
100
|
}
|
|
100
|
-
export function renderKeiyaku(title, goal, context, baseConstraints, taskConstraints,
|
|
101
|
+
export function renderKeiyaku(title, goal, context, baseConstraints, taskConstraints, taskCriteria) {
|
|
101
102
|
let content = `# ${title}\n\n## Context\n${context}\n\n## Goal\n${goal}`;
|
|
102
103
|
content += "\n\n## Constraints";
|
|
103
104
|
if (baseConstraints.length > 0) {
|
|
104
|
-
content += `\n\n###
|
|
105
|
+
content += `\n\n### Project Constraints\nLoaded from: \`${BASE_CONSTRAINTS_SOURCE}\`\n\n${renderMarkdownSections(baseConstraints)}`;
|
|
105
106
|
}
|
|
106
107
|
content += `\n\n### Task Constraints\n${renderMarkdownSections(taskConstraints)}`;
|
|
107
108
|
content += "\n\n## Acceptance Criteria";
|
|
108
|
-
if (baseCriteria.length > 0) {
|
|
109
|
-
content += `\n\n### Base Criteria\n${renderMarkdownSections(baseCriteria)}`;
|
|
110
|
-
}
|
|
111
109
|
content += `\n\n### Task Criteria\n${renderMarkdownSections(taskCriteria)}`;
|
|
112
110
|
return content;
|
|
113
111
|
}
|
package/build/workflow/ask.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import * as path from "path";
|
|
1
3
|
import { selectSubagent } from "../agents/selector.js";
|
|
2
4
|
import { runSubagent } from "../agents/round-runner.js";
|
|
3
5
|
import * as git from "../utils/git.js";
|
|
4
6
|
import { buildAskPrompt } from "./prompts.js";
|
|
5
7
|
import { FlowError } from "../common/errors.js";
|
|
8
|
+
import { parseMarkdownHeaders, renderMarkdownSections } from "../utils/text-utils.js";
|
|
9
|
+
const BASE_CONSTRAINTS_FILE = path.join(".keiyaku", "base-constraints.md");
|
|
6
10
|
function requireText(name, value) {
|
|
7
11
|
const normalized = value.trim();
|
|
8
12
|
if (!normalized) {
|
|
@@ -14,7 +18,20 @@ export async function askServant(input) {
|
|
|
14
18
|
const { cwd, signal, name } = input;
|
|
15
19
|
const request = requireText("request", input.request);
|
|
16
20
|
const context = requireText("context", input.context);
|
|
17
|
-
|
|
21
|
+
let referenceConstraints;
|
|
22
|
+
try {
|
|
23
|
+
const baseConstraintsRaw = await fs.readFile(path.join(cwd, BASE_CONSTRAINTS_FILE), "utf-8");
|
|
24
|
+
const baseConstraints = parseMarkdownHeaders(baseConstraintsRaw);
|
|
25
|
+
if (baseConstraints.length > 0) {
|
|
26
|
+
referenceConstraints = renderMarkdownSections(baseConstraints);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error?.code !== "ENOENT") {
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const prompt = buildAskPrompt(request, context, referenceConstraints);
|
|
18
35
|
// TODO: enforce read-only access and persist summary to .keiyaku/notes/.
|
|
19
36
|
const summary = await runSubagent(selectSubagent(name), prompt, cwd, 0, signal);
|
|
20
37
|
let branch;
|
|
@@ -26,7 +26,7 @@ export async function assertCleanWorkingTree(cwd) {
|
|
|
26
26
|
return ` ${status} ${f.path}`;
|
|
27
27
|
})
|
|
28
28
|
.join("\n");
|
|
29
|
-
throw new FlowError("DIRTY_WORKTREE", `
|
|
29
|
+
throw new FlowError("DIRTY_WORKTREE", `Uncommitted changes detected in working tree:\n${list}\n\nPlease commit or stash them before proceeding to ensure a stable state.`);
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
export async function buildNoActiveKeiyakuGuidance(cwd, toolName) {
|
|
@@ -34,18 +34,16 @@ export async function buildNoActiveKeiyakuGuidance(cwd, toolName) {
|
|
|
34
34
|
const localKeiyakuBranches = await git.listLocalKeiyakuBranches(cwd).catch(() => []);
|
|
35
35
|
const preset = resolveTermPreset();
|
|
36
36
|
const startCmd = preset.tools.start.name;
|
|
37
|
-
const currentCmd = preset.tools[toolName].name;
|
|
38
37
|
const parts = [
|
|
39
|
-
`
|
|
40
|
-
`if this task is not using keiyaku workflow, skip ${currentCmd}`,
|
|
38
|
+
`Current branch '${currentBranch}' is not an active Keiyaku branch.`,
|
|
41
39
|
];
|
|
42
40
|
if (localKeiyakuBranches.length > 0) {
|
|
43
41
|
const listedBranches = localKeiyakuBranches.slice(0, 3).join(", ");
|
|
44
42
|
const branchList = `${listedBranches}${localKeiyakuBranches.length > 3 ? ", ..." : ""}`;
|
|
45
|
-
parts.push(`switch to an active branch (${branchList}) or run
|
|
43
|
+
parts.push(`Use \`git switch\` to an active branch (${branchList}) or run \`${startCmd}\` to start a new mission.`);
|
|
46
44
|
}
|
|
47
45
|
else {
|
|
48
|
-
parts.push(`
|
|
46
|
+
parts.push(`No local keiyaku/* branches found. Run \`${startCmd}\` to begin.`);
|
|
49
47
|
}
|
|
50
|
-
return parts.join("
|
|
48
|
+
return parts.join(" ");
|
|
51
49
|
}
|
package/build/workflow/drive.js
CHANGED
|
@@ -14,9 +14,14 @@ function requireText(name, value) {
|
|
|
14
14
|
}
|
|
15
15
|
return normalized;
|
|
16
16
|
}
|
|
17
|
+
function readKeiyakuSection(content, sectionTitle) {
|
|
18
|
+
const escapedTitle = sectionTitle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
19
|
+
const sectionMatch = content.match(new RegExp(`(?:^|\\n)## ${escapedTitle}\\s*\\n([\\s\\S]*?)(?=\\n##\\s|$)`));
|
|
20
|
+
const sectionBody = sectionMatch?.[1]?.trim();
|
|
21
|
+
return sectionBody || undefined;
|
|
22
|
+
}
|
|
17
23
|
function readGoalFromKeiyaku(content) {
|
|
18
|
-
const
|
|
19
|
-
const goal = goalMatch?.[1]?.trim();
|
|
24
|
+
const goal = readKeiyakuSection(content, "Goal");
|
|
20
25
|
if (!goal) {
|
|
21
26
|
throw new FlowError("EMPTY_PARAM", "parameter 'goal' cannot be empty");
|
|
22
27
|
}
|
|
@@ -42,6 +47,12 @@ export async function driveServant(input) {
|
|
|
42
47
|
const traceState = computeTraceState(traceContent);
|
|
43
48
|
const title = keiyakuBranch.slice("keiyaku/".length);
|
|
44
49
|
const goal = readGoalFromKeiyaku(keiyakuContent);
|
|
50
|
+
const keiyakuSections = {
|
|
51
|
+
goal,
|
|
52
|
+
context: readKeiyakuSection(keiyakuContent, "Context"),
|
|
53
|
+
constraints: readKeiyakuSection(keiyakuContent, "Constraints"),
|
|
54
|
+
criteria: readKeiyakuSection(keiyakuContent, "Acceptance Criteria"),
|
|
55
|
+
};
|
|
45
56
|
const normalizedDirective = requireText("directive", directive);
|
|
46
57
|
try {
|
|
47
58
|
const plan = buildIteratePlan(title, traceState, traceContent, goal, normalizedDirective, context);
|
|
@@ -68,6 +79,7 @@ export async function driveServant(input) {
|
|
|
68
79
|
diff,
|
|
69
80
|
summary,
|
|
70
81
|
roundSummary,
|
|
82
|
+
keiyakuSections,
|
|
71
83
|
branch: keiyakuBranch,
|
|
72
84
|
baseBranch,
|
|
73
85
|
};
|
|
@@ -89,8 +89,16 @@ async function resolveVerdictConfig(cwd) {
|
|
|
89
89
|
function buildMergeMessage(title, keiyakuContent, reportContent) {
|
|
90
90
|
return `keiyaku(${title}): done\n\n---\n${keiyakuContent}\n---\n${reportContent}\n---\n`;
|
|
91
91
|
}
|
|
92
|
+
function requireChecks(name, values) {
|
|
93
|
+
if (values.length === 0) {
|
|
94
|
+
throw new FlowError("EMPTY_PARAM", `parameter '${name}' cannot be empty`);
|
|
95
|
+
}
|
|
96
|
+
return values;
|
|
97
|
+
}
|
|
92
98
|
export async function presentWork(input) {
|
|
93
99
|
const { cwd } = input;
|
|
100
|
+
requireChecks("criteriaChecks", input.criteriaChecks);
|
|
101
|
+
requireChecks("constraintsChecks", input.constraintsChecks);
|
|
94
102
|
const isRepo = await git.isGitRepo(cwd);
|
|
95
103
|
if (!isRepo) {
|
|
96
104
|
throw new FlowError("NOT_GIT_REPO", `${cwd} is not a git repository`);
|
|
@@ -88,7 +88,10 @@ Self-critique.
|
|
|
88
88
|
|
|
89
89
|
No git commands. Do not edit KEIYAKU_TRACE.md.`;
|
|
90
90
|
}
|
|
91
|
-
export function buildAskPrompt(request, context) {
|
|
91
|
+
export function buildAskPrompt(request, context, referenceConstraints) {
|
|
92
|
+
const referenceBlock = referenceConstraints
|
|
93
|
+
? `\nReference Constraints (project-level; use only if relevant to this ask):\n${referenceConstraints}\n`
|
|
94
|
+
: "";
|
|
92
95
|
return `Ask mode. Stateless. No git commands.
|
|
93
96
|
|
|
94
97
|
Request:
|
|
@@ -96,6 +99,7 @@ ${request}
|
|
|
96
99
|
|
|
97
100
|
Context:
|
|
98
101
|
${context}
|
|
102
|
+
${referenceBlock}
|
|
99
103
|
|
|
100
104
|
Provide a clear final response.`;
|
|
101
105
|
}
|
|
@@ -26,11 +26,11 @@ function formatList(label, items, opts) {
|
|
|
26
26
|
return [...head, ...shown, ...tail];
|
|
27
27
|
}
|
|
28
28
|
function buildSection(title, content) {
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
if (
|
|
32
|
-
return
|
|
33
|
-
return
|
|
29
|
+
const body = Array.isArray(content) ? content.join("\n") : content;
|
|
30
|
+
const normalized = body.trim();
|
|
31
|
+
if (!normalized)
|
|
32
|
+
return null;
|
|
33
|
+
return { title, body };
|
|
34
34
|
}
|
|
35
35
|
function supportsHeaderColor() {
|
|
36
36
|
if (process.env.NO_COLOR !== undefined)
|
|
@@ -65,40 +65,38 @@ function formatHeader(title) {
|
|
|
65
65
|
const divider = "─".repeat(Math.max(0, headerWidth - titleLabel.length - 1));
|
|
66
66
|
return divider.length > 0 ? `\n${coloredTitle} ${divider}` : `\n${coloredTitle}`;
|
|
67
67
|
}
|
|
68
|
-
function assembleResponse(status, summary, sections,
|
|
68
|
+
function assembleResponse(status, summary, sections, infoLines = [], nextHints = []) {
|
|
69
69
|
// Main Status Block
|
|
70
70
|
const statusBlock = `
|
|
71
71
|
${status}
|
|
72
72
|
${"─".repeat(Math.max(status.length, 60))}
|
|
73
73
|
${summary}
|
|
74
74
|
`.trim();
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return `${formatHeader(bracketMatch[1])}\n${bracketMatch[2]}`;
|
|
75
|
+
const normalSections = [];
|
|
76
|
+
const diffSections = [];
|
|
77
|
+
for (const section of sections) {
|
|
78
|
+
if (section.title.trim().toLowerCase() === "diff") {
|
|
79
|
+
diffSections.push(section);
|
|
81
80
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return `${formatHeader(hashMatch[1])}\n${hashMatch[2]}`;
|
|
81
|
+
else {
|
|
82
|
+
normalSections.push(section);
|
|
85
83
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
84
|
+
}
|
|
85
|
+
const fullSections = [...normalSections];
|
|
86
|
+
if (infoLines.length > 0) {
|
|
87
|
+
fullSections.push({ title: "Info", body: infoLines.join("\n") });
|
|
88
|
+
}
|
|
89
|
+
fullSections.push(...diffSections);
|
|
90
|
+
if (nextHints.length > 0) {
|
|
91
|
+
fullSections.push({ title: "Hints", body: nextHints.map((hint) => `› ${hint}`).join("\n") });
|
|
92
|
+
}
|
|
93
|
+
const renderedSections = fullSections.map((section) => `${formatHeader(section.title)}\n${section.body}`);
|
|
96
94
|
return [
|
|
97
95
|
statusBlock,
|
|
98
|
-
...
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
96
|
+
...renderedSections,
|
|
97
|
+
]
|
|
98
|
+
.filter((chunk) => chunk.trim() !== "")
|
|
99
|
+
.join("\n");
|
|
102
100
|
}
|
|
103
101
|
function buildSuccessStructuredContent(tool, data) {
|
|
104
102
|
return {
|
|
@@ -146,18 +144,17 @@ export function buildKeiyakuSuccessResponse(result, input) {
|
|
|
146
144
|
...formatList("Criteria", input.criteria, { maxItems: 10, maxItemChars: 200 }),
|
|
147
145
|
...formatMaybe("Context", input.context, 600),
|
|
148
146
|
...formatMaybe("Constraints", input.constraints, 600),
|
|
149
|
-
...formatMaybe("Identity", input.name, 120),
|
|
150
|
-
...formatMaybe("CWD", input.cwd, 300),
|
|
151
147
|
];
|
|
152
148
|
const nextHints = renderHints(resolveTermPreset().nextHints.start);
|
|
153
|
-
const
|
|
149
|
+
const keiyakuSection = buildSection("KEIYAKU", result.keiyaku.trim());
|
|
154
150
|
const diffSection = buildSection("Diff", result.diff);
|
|
155
151
|
const infoLines = [
|
|
156
|
-
...formatMaybe("
|
|
152
|
+
...formatMaybe("Identity", input.name, 120),
|
|
153
|
+
...formatMaybe("Path", input.cwd, 300),
|
|
157
154
|
...formatMaybe("Current Branch", result.branch, 200),
|
|
158
155
|
...formatMaybe("Base Branch", result.baseBranch, 200),
|
|
159
156
|
];
|
|
160
|
-
const text = assembleResponse(`Started (Round ${result.round})`, `Created branch '${result.branch}' (base: '${result.baseBranch}').\n\n${renderRoundSummary(result.roundSummary, TOOL_DEFAULT_POLICY)}`, [
|
|
157
|
+
const text = assembleResponse(`Started (Round ${result.round})`, `Created branch '${result.branch}' (base: '${result.baseBranch}').\n\n${renderRoundSummary(result.roundSummary, TOOL_DEFAULT_POLICY)}`, [keiyakuSection, diffSection].filter((section) => section !== null), infoLines, nextHints);
|
|
161
158
|
return {
|
|
162
159
|
content: [{ type: "text", text }],
|
|
163
160
|
structuredContent: buildSuccessStructuredContent(getStartToolName(), {
|
|
@@ -173,18 +170,21 @@ export function buildDriveResponse(result, input) {
|
|
|
173
170
|
const inputEcho = [
|
|
174
171
|
`Directive: ${truncateForDisplay(input.directive, 600)}`,
|
|
175
172
|
...formatMaybe("Context", input.context, 600),
|
|
176
|
-
...formatMaybe("Identity", input.name, 120),
|
|
177
|
-
...formatMaybe("CWD", input.cwd, 300),
|
|
178
173
|
];
|
|
179
174
|
const nextHints = renderHints(resolveTermPreset().nextHints.drive);
|
|
180
|
-
const
|
|
175
|
+
const goalSection = buildSection("Goal", truncateForDisplay(result.keiyakuSections?.goal ?? "", 800));
|
|
176
|
+
const contextSection = buildSection("Context", truncateForDisplay(result.keiyakuSections?.context ?? "", 1200));
|
|
177
|
+
const constraintsSection = buildSection("Constraints", truncateForDisplay(result.keiyakuSections?.constraints ?? "", 1200));
|
|
178
|
+
const criteriaSection = buildSection("Criteria", truncateForDisplay(result.keiyakuSections?.criteria ?? "", 1200));
|
|
179
|
+
const directiveSection = buildSection("Directive", inputEcho);
|
|
181
180
|
const diffSection = buildSection("Diff", result.diff);
|
|
182
181
|
const infoLines = [
|
|
183
|
-
...formatMaybe("
|
|
182
|
+
...formatMaybe("Identity", input.name, 120),
|
|
183
|
+
...formatMaybe("Path", input.cwd, 300),
|
|
184
184
|
...formatMaybe("Current Branch", result.branch, 200),
|
|
185
185
|
...formatMaybe("Base Branch", result.baseBranch, 200),
|
|
186
186
|
];
|
|
187
|
-
const text = assembleResponse(`Driven (Round ${result.round})`, `Updated branch '${result.branch}'.\n\n${renderRoundSummary(result.roundSummary, TOOL_DEFAULT_POLICY)}`, [
|
|
187
|
+
const text = assembleResponse(`Driven (Round ${result.round})`, `Updated branch '${result.branch}'.\n\n${renderRoundSummary(result.roundSummary, TOOL_DEFAULT_POLICY)}`, [goalSection, contextSection, constraintsSection, criteriaSection, directiveSection, diffSection].filter((section) => section !== null), infoLines, nextHints);
|
|
188
188
|
return {
|
|
189
189
|
content: [{ type: "text", text }],
|
|
190
190
|
structuredContent: buildSuccessStructuredContent(getDriveToolName(), {
|
|
@@ -200,16 +200,15 @@ export function buildAskResponse(result, input) {
|
|
|
200
200
|
`Request: ${truncateForDisplay(input.request, 600)}`,
|
|
201
201
|
`Context: ${truncateForDisplay(input.context, 800)}`,
|
|
202
202
|
...formatMaybe("Identity", input.name, 120),
|
|
203
|
-
...formatMaybe("CWD", input.cwd, 300),
|
|
204
203
|
];
|
|
205
204
|
const nextHints = renderHints(resolveTermPreset().nextHints.ask);
|
|
206
205
|
const inputSection = buildSection("Input", inputEcho);
|
|
207
206
|
const infoLines = [
|
|
208
|
-
...formatMaybe("
|
|
207
|
+
...formatMaybe("Path", input.cwd, 300),
|
|
209
208
|
...formatMaybe("Current Branch", result.branch, 200),
|
|
210
209
|
...formatMaybe("Diff Stats", result.diffStats, 1000),
|
|
211
210
|
];
|
|
212
|
-
const text = assembleResponse("Answered", result.summary, [inputSection],
|
|
211
|
+
const text = assembleResponse("Answered", result.summary, [inputSection].filter((section) => section !== null), infoLines, nextHints);
|
|
213
212
|
return {
|
|
214
213
|
content: [{ type: "text", text }],
|
|
215
214
|
structuredContent: buildSuccessStructuredContent(getAskToolName(), {
|
|
@@ -226,19 +225,19 @@ export function buildCloseDoneResponse(result, input) {
|
|
|
226
225
|
const inputEcho = [
|
|
227
226
|
`Petition: CLAIM`,
|
|
228
227
|
...formatList("Criteria Checks", input.criteriaChecks, { maxItems: 10, maxItemChars: 220 }),
|
|
228
|
+
...formatList("Constraints Checks", input.constraintsChecks ?? [], { maxItems: 10, maxItemChars: 220 }),
|
|
229
229
|
`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`,
|
|
230
230
|
...formatMaybe("Oath", input.oath, 220),
|
|
231
|
-
...formatMaybe("CWD", input.cwd, 300),
|
|
232
231
|
];
|
|
233
232
|
const nextHints = renderHints(resolveTermPreset().nextHints.closeClaim);
|
|
234
233
|
const inputSection = buildSection("Input", inputEcho);
|
|
235
234
|
const diffSection = result.diff ? buildSection("Diff", result.diff) : "";
|
|
236
235
|
const infoLines = [
|
|
237
|
-
...formatMaybe("
|
|
236
|
+
...formatMaybe("Path", input.cwd, 300),
|
|
238
237
|
...formatMaybe("Current Branch", result.branch, 200),
|
|
239
238
|
...formatMaybe("Base Branch", result.baseBranch, 200),
|
|
240
239
|
];
|
|
241
|
-
const text = assembleResponse("Keiyaku Fulfilled (CLAIM)", `Merged '${result.branch}' into '${result.baseBranch}'. Deleted feature branch.`, [inputSection, diffSection],
|
|
240
|
+
const text = assembleResponse("Keiyaku Fulfilled (CLAIM)", `Merged '${result.branch}' into '${result.baseBranch}'. Deleted feature branch.`, [inputSection, typeof diffSection === "string" ? null : diffSection].filter((section) => section !== null), infoLines, nextHints);
|
|
242
241
|
return {
|
|
243
242
|
content: [{ type: "text", text }],
|
|
244
243
|
structuredContent: buildSuccessStructuredContent(closeToolName, {
|
|
@@ -255,17 +254,17 @@ export function buildCloseDropResponse(result, input) {
|
|
|
255
254
|
const inputEcho = [
|
|
256
255
|
`Petition: FORFEIT`,
|
|
257
256
|
...formatList("Reasons/Checks", input.criteriaChecks, { maxItems: 10, maxItemChars: 220 }),
|
|
257
|
+
...formatList("Constraints Checks", input.constraintsChecks ?? [], { maxItems: 10, maxItemChars: 220 }),
|
|
258
258
|
`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`,
|
|
259
|
-
...formatMaybe("CWD", input.cwd, 300),
|
|
260
259
|
];
|
|
261
260
|
const nextHints = renderHints(resolveTermPreset().nextHints.closeDrop);
|
|
262
261
|
const inputSection = buildSection("Input", inputEcho);
|
|
263
262
|
const infoLines = [
|
|
264
|
-
...formatMaybe("
|
|
263
|
+
...formatMaybe("Path", input.cwd, 300),
|
|
265
264
|
...formatMaybe("Current Branch", result.branch, 200),
|
|
266
265
|
...formatMaybe("Base Branch", result.baseBranch, 200),
|
|
267
266
|
];
|
|
268
|
-
const text = assembleResponse("Keiyaku Forfeited (FORFEIT)", `Deleted '${result.branch}'. Switched back to '${result.baseBranch}'.`, [inputSection],
|
|
267
|
+
const text = assembleResponse("Keiyaku Forfeited (FORFEIT)", `Deleted '${result.branch}'. Switched back to '${result.baseBranch}'.`, [inputSection].filter((section) => section !== null), infoLines, nextHints);
|
|
269
268
|
return {
|
|
270
269
|
content: [{ type: "text", text }],
|
|
271
270
|
structuredContent: buildSuccessStructuredContent(closeToolName, {
|
|
@@ -279,7 +278,8 @@ export function buildCloseDropResponse(result, input) {
|
|
|
279
278
|
export function buildToolErrorResponse(input) {
|
|
280
279
|
const inputEcho = (input.inputEcho ?? []).map((line) => truncateForDisplay(line, 800));
|
|
281
280
|
const shouldRaiseProtocolError = input.errorType === "runtime_error";
|
|
282
|
-
const
|
|
281
|
+
const contextSection = buildSection("Context", inputEcho);
|
|
282
|
+
const text = assembleResponse(colorizeErrorStatus("!! FAILED !!"), input.message, [contextSection].filter((section) => section !== null), [], [input.hint]);
|
|
283
283
|
const response = {
|
|
284
284
|
content: [{ type: "text", text }],
|
|
285
285
|
structuredContent: {
|
|
@@ -300,8 +300,6 @@ export function buildToolErrorResponse(input) {
|
|
|
300
300
|
export function buildHelpResponse(input) {
|
|
301
301
|
return {
|
|
302
302
|
content: [{ type: "text", text: input.text }],
|
|
303
|
-
structuredContent: buildSuccessStructuredContent(input.tool, {
|
|
304
|
-
question: input.question,
|
|
305
|
-
}),
|
|
303
|
+
structuredContent: buildSuccessStructuredContent(input.tool, {}),
|
|
306
304
|
};
|
|
307
305
|
}
|
package/build/workflow/summon.js
CHANGED
|
@@ -7,10 +7,10 @@ import * as git from "../utils/git.js";
|
|
|
7
7
|
import { computeTraceState, readTraceContent } from "../utils/trace.js";
|
|
8
8
|
import { buildStartPrompt } from "./prompts.js";
|
|
9
9
|
import { parseMarkdownHeaders, renderKeiyaku } from "../utils/text-utils.js";
|
|
10
|
+
import { resolveTermPreset } from "../config/term-presets.js";
|
|
10
11
|
import { renderRoundSummary, TOOL_DEFAULT_POLICY } from "./round-summary.js";
|
|
11
12
|
import { assertCleanWorkingTree } from "./contract.js";
|
|
12
13
|
import { runAndRecordRound } from "./round.js";
|
|
13
|
-
const BASE_CRITERIA_FILE = path.join(".keiyaku", "base-criteria.md");
|
|
14
14
|
const BASE_CONSTRAINTS_FILE = path.join(".keiyaku", "base-constraints.md");
|
|
15
15
|
const ACTIVE_KEIYAKU_PREVIEW_MAX_CHARS = 8000;
|
|
16
16
|
function requireText(name, value) {
|
|
@@ -26,11 +26,14 @@ function truncateForMessage(text, maxChars) {
|
|
|
26
26
|
return `${text.slice(0, maxChars)}\n...[truncated ${text.length - maxChars} chars]...`;
|
|
27
27
|
}
|
|
28
28
|
async function buildActiveKeiyakuStartMessage(cwd, branch) {
|
|
29
|
+
const preset = resolveTermPreset();
|
|
30
|
+
const { drive, close } = preset.tools;
|
|
29
31
|
const lines = [
|
|
30
|
-
`active
|
|
31
|
-
"This task is still
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
`An active Keiyaku is already in progress on branch '${branch}'.`,
|
|
33
|
+
"This task is still pulsing. Decide its fate before starting anew:",
|
|
34
|
+
`1. Continue: Issue a \`${drive.name}\` to advance the work.`,
|
|
35
|
+
`2. Fulfill: Use \`${close.name}\` (\`CLAIM\`) to merge your progress.`,
|
|
36
|
+
`3. Forfeit: Use \`${close.name}\` (\`FORFEIT\`) to discard this effort.`,
|
|
34
37
|
];
|
|
35
38
|
try {
|
|
36
39
|
const keiyakuPath = path.join(cwd, KEIYAKU_FILE);
|
|
@@ -126,16 +129,6 @@ export async function summonServant(input) {
|
|
|
126
129
|
const switchedLog = `Switched to branch: ${keiyakuBranch} (base: ${baseBranch})`;
|
|
127
130
|
console.error(switchedLog);
|
|
128
131
|
appendDebugLog(switchedLog, { cwd, section: "script" });
|
|
129
|
-
let baseCriteria = [];
|
|
130
|
-
try {
|
|
131
|
-
const baseCriteriaRaw = await fs.readFile(path.join(cwd, BASE_CRITERIA_FILE), "utf-8");
|
|
132
|
-
baseCriteria = parseMarkdownHeaders(baseCriteriaRaw);
|
|
133
|
-
}
|
|
134
|
-
catch (error) {
|
|
135
|
-
if (error?.code !== "ENOENT") {
|
|
136
|
-
throw error;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
132
|
let baseConstraints = [];
|
|
140
133
|
try {
|
|
141
134
|
const baseConstraintsRaw = await fs.readFile(path.join(cwd, BASE_CONSTRAINTS_FILE), "utf-8");
|
|
@@ -146,7 +139,7 @@ export async function summonServant(input) {
|
|
|
146
139
|
throw error;
|
|
147
140
|
}
|
|
148
141
|
}
|
|
149
|
-
const keiyakuContent = renderKeiyaku(finalTitle, finalGoal, finalContext, baseConstraints, normalizedTaskConstraints,
|
|
142
|
+
const keiyakuContent = renderKeiyaku(finalTitle, finalGoal, finalContext, baseConstraints, normalizedTaskConstraints, normalizedTaskCriteria);
|
|
150
143
|
await fs.writeFile(path.join(cwd, KEIYAKU_FILE), keiyakuContent);
|
|
151
144
|
await fs.writeFile(path.join(cwd, TRACE_FILE), "# Keiyaku Trace\n");
|
|
152
145
|
await git.addFiles(cwd, [KEIYAKU_FILE, TRACE_FILE]);
|
|
@@ -165,6 +158,7 @@ export async function summonServant(input) {
|
|
|
165
158
|
round: 1,
|
|
166
159
|
trace,
|
|
167
160
|
diff,
|
|
161
|
+
keiyaku: keiyakuContent,
|
|
168
162
|
summary,
|
|
169
163
|
roundSummary,
|
|
170
164
|
branch: keiyakuBranch,
|
package/package.json
CHANGED
|
File without changes
|