@astrosheep/keiyaku 0.1.15 → 0.1.17
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/config/term-presets.js +3 -3
- package/build/handlers/close.js +28 -31
- package/build/handlers/help.js +6 -1
- package/build/index.js +1 -3
- package/build/types/tooling.js +18 -12
- package/build/utils/git-ops.js +10 -0
- package/build/utils/text-utils.js +34 -0
- package/build/workflow/ask.js +3 -2
- package/build/workflow/drive.js +0 -2
- package/build/workflow/present.js +10 -5
- package/build/workflow/prompts.js +12 -12
- package/build/workflow/response-builders.js +9 -10
- package/build/workflow/start.js +5 -10
- package/package.json +1 -1
|
@@ -19,7 +19,7 @@ export const DEFAULT_PRESET = {
|
|
|
19
19
|
id: 'default',
|
|
20
20
|
identity: 'Servant',
|
|
21
21
|
verdict: DEFAULT_VERDICT_CONFIG,
|
|
22
|
-
usageGuide: '## Workflow\n**You are the Architect.** You use these tools to command the Servants.\n\n
|
|
22
|
+
usageGuide: '## Workflow\n**You are the Architect.** You use these tools to command the Servants.\n\n`${ask}` (anytime) | `${start}` -> [`${drive}` | `${ask}`]* -> `${close}`\n\n## Constraints Protocol\n\nProject-level constraints in `.keiyaku/base-constraints.md` are automatically parsed and injected into every Keiyaku.\n- **Atomic Items**: Use standard Markdown lists (`-`, `*`, `1.`) for individual, actionable constraints.\n- **Header Flattening**: Root-level headers (e.g., `## Title`) are automatically transformed into bolded prefixes (`**Title**: Content`).\n- **Paragraph Splitting**: Multi-paragraph sections are split by blank lines into separate list items, inheriting the parent header.\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.',
|
|
@@ -125,7 +125,7 @@ export const POCKET_PRESET = {
|
|
|
125
125
|
id: 'pocket',
|
|
126
126
|
identity: 'Critter',
|
|
127
127
|
verdict: DEFAULT_VERDICT_CONFIG,
|
|
128
|
-
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`
|
|
128
|
+
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`${ask}` (scan/test) | `${start}` -> [`${drive}` | `${ask}`]* -> `${close}`",
|
|
129
129
|
nextHints: {
|
|
130
130
|
start: [
|
|
131
131
|
'Battle Started: The [Diff] section shows the opening moves.',
|
|
@@ -221,7 +221,7 @@ export const MISCHIEF_PRESET = {
|
|
|
221
221
|
id: 'mischief',
|
|
222
222
|
identity: 'minion',
|
|
223
223
|
verdict: DEFAULT_VERDICT_CONFIG,
|
|
224
|
-
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`
|
|
224
|
+
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`${ask}` (Nn——! disposable/experiment) | `${start}` -> [`${drive}` | `${ask}`]* -> `${close}` (Dayaa!)',
|
|
225
225
|
nextHints: {
|
|
226
226
|
start: [
|
|
227
227
|
"Inspect the Minion's Work: The [Diff] section shows the first step of the plan.",
|
package/build/handlers/close.js
CHANGED
|
@@ -2,32 +2,37 @@ import { appendDebugLog } from "../utils/debug-log.js";
|
|
|
2
2
|
import { presentWork } from "../workflow/present.js";
|
|
3
3
|
import { buildCloseDoneResponse, buildCloseDropResponse, } from "../workflow/response-builders.js";
|
|
4
4
|
import { resolveTermPreset } from "../config/term-presets.js";
|
|
5
|
+
import { closeToolSchema } from "../types/tooling.js";
|
|
5
6
|
import { handleToolError } from "./shared.js";
|
|
6
7
|
export function createCloseHandler() {
|
|
7
|
-
return async (
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
return async (args, extra) => {
|
|
9
|
+
let petition = "UNKNOWN";
|
|
10
|
+
let workingDir = process.cwd();
|
|
11
|
+
let criteriaCheckParts = [];
|
|
12
|
+
let constraintsCheckParts = [];
|
|
13
|
+
let oath;
|
|
14
|
+
let scoreLine;
|
|
11
15
|
try {
|
|
16
|
+
const input = closeToolSchema.parse(args);
|
|
17
|
+
petition = input.petition;
|
|
18
|
+
workingDir = input.cwd || process.cwd();
|
|
19
|
+
if (input.petition === "CLAIM") {
|
|
20
|
+
criteriaCheckParts = input.criteriaChecks;
|
|
21
|
+
constraintsCheckParts = input.constraintsChecks;
|
|
22
|
+
oath = input.oath;
|
|
23
|
+
scoreLine = `Scores: precise=${input.score_precise} minimal=${input.score_minimal} isolated=${input.score_isolated} idiomatic=${input.score_idiomatic} cohesive=${input.score_cohesive}`;
|
|
24
|
+
}
|
|
12
25
|
appendDebugLog(`tool close start petition=${petition} cwd=${workingDir} criteriaChecks=${criteriaCheckParts.length} constraintsChecks=${constraintsCheckParts.length}`, {
|
|
13
26
|
cwd: workingDir,
|
|
14
27
|
section: "script",
|
|
15
28
|
});
|
|
16
29
|
const outcome = await presentWork({
|
|
30
|
+
...input,
|
|
17
31
|
cwd: workingDir,
|
|
18
|
-
petition,
|
|
19
|
-
criteriaChecks: criteriaCheckParts,
|
|
20
|
-
constraintsChecks: constraintsCheckParts,
|
|
21
|
-
score_precise,
|
|
22
|
-
score_minimal,
|
|
23
|
-
score_isolated,
|
|
24
|
-
score_idiomatic,
|
|
25
|
-
score_cohesive,
|
|
26
|
-
oath,
|
|
27
32
|
signal: extra.signal,
|
|
28
33
|
});
|
|
29
|
-
if (petition === "CLAIM") {
|
|
30
|
-
if (!("result" in outcome) || outcome.result !== "
|
|
34
|
+
if (input.petition === "CLAIM") {
|
|
35
|
+
if (!("result" in outcome) || outcome.result !== "merged") {
|
|
31
36
|
throw new Error("Unexpected CLAIM outcome shape");
|
|
32
37
|
}
|
|
33
38
|
const finalOutcome = outcome;
|
|
@@ -38,12 +43,12 @@ export function createCloseHandler() {
|
|
|
38
43
|
return buildCloseDoneResponse(finalOutcome, {
|
|
39
44
|
criteriaChecks: criteriaCheckParts,
|
|
40
45
|
constraintsChecks: constraintsCheckParts,
|
|
41
|
-
score_precise,
|
|
42
|
-
score_minimal,
|
|
43
|
-
score_isolated,
|
|
44
|
-
score_idiomatic,
|
|
45
|
-
score_cohesive,
|
|
46
|
-
oath,
|
|
46
|
+
score_precise: input.score_precise,
|
|
47
|
+
score_minimal: input.score_minimal,
|
|
48
|
+
score_isolated: input.score_isolated,
|
|
49
|
+
score_idiomatic: input.score_idiomatic,
|
|
50
|
+
score_cohesive: input.score_cohesive,
|
|
51
|
+
oath: input.oath,
|
|
47
52
|
cwd: workingDir,
|
|
48
53
|
});
|
|
49
54
|
}
|
|
@@ -56,14 +61,6 @@ export function createCloseHandler() {
|
|
|
56
61
|
section: "script",
|
|
57
62
|
});
|
|
58
63
|
return buildCloseDropResponse(finalOutcome, {
|
|
59
|
-
criteriaChecks: criteriaCheckParts,
|
|
60
|
-
constraintsChecks: constraintsCheckParts,
|
|
61
|
-
score_precise,
|
|
62
|
-
score_minimal,
|
|
63
|
-
score_isolated,
|
|
64
|
-
score_idiomatic,
|
|
65
|
-
score_cohesive,
|
|
66
|
-
oath,
|
|
67
64
|
cwd: workingDir,
|
|
68
65
|
});
|
|
69
66
|
}
|
|
@@ -78,8 +75,8 @@ export function createCloseHandler() {
|
|
|
78
75
|
`Petition: ${petition}`,
|
|
79
76
|
`Criteria checks (${criteriaCheckParts.length}): ${criteriaCheckParts.join("; ")}`,
|
|
80
77
|
`Constraints checks (${constraintsCheckParts.length}): ${constraintsCheckParts.join("; ")}`,
|
|
81
|
-
|
|
82
|
-
...(oath ? [`Oath: ${oath}`] : []),
|
|
78
|
+
...(scoreLine ? [scoreLine] : []),
|
|
79
|
+
...(petition === "CLAIM" && oath ? [`Oath: ${oath}`] : []),
|
|
83
80
|
`Path: ${workingDir}`,
|
|
84
81
|
],
|
|
85
82
|
});
|
package/build/handlers/help.js
CHANGED
|
@@ -5,9 +5,14 @@ export function createHelpHandler(preset) {
|
|
|
5
5
|
"# Keiyaku System Help",
|
|
6
6
|
"",
|
|
7
7
|
"## Core Files (.keiyaku/)",
|
|
8
|
-
"These files define the 'Law' of the project.
|
|
8
|
+
"These files define the 'Law' and configuration of the project.",
|
|
9
9
|
"",
|
|
10
10
|
"- **base-constraints.md**: Mandatory architectural boundaries and coding standards.",
|
|
11
|
+
" - **Purpose**: Global constraints injected into every mission.",
|
|
12
|
+
" - **Logic**: The system prioritize extracting all list items (`-` or `*`) as individual constraints.",
|
|
13
|
+
" - **Formatting**: Use lists for constraints. You can use H3+ headers (`###`) for organization, but they are stripped if the section contains lists.",
|
|
14
|
+
" - **Strict Rule**: DO NOT use H1 (`#`) or H2 (`##`) within constraints or as content if they aren't meant to be item separators, as they will trigger validation errors.",
|
|
15
|
+
"- **settings.json**: Local configuration for the Keiyaku environment.",
|
|
11
16
|
"",
|
|
12
17
|
preset.usageGuide,
|
|
13
18
|
"",
|
package/build/index.js
CHANGED
|
@@ -41,9 +41,7 @@ function registerTools(server) {
|
|
|
41
41
|
const askPreset = renderedPreset.tools.ask;
|
|
42
42
|
const closePreset = renderedPreset.tools.close;
|
|
43
43
|
const helpPreset = renderedPreset.tools.help;
|
|
44
|
-
const dynamicCloseSchema =
|
|
45
|
-
...closePreset.args,
|
|
46
|
-
});
|
|
44
|
+
const dynamicCloseSchema = closeToolSchema;
|
|
47
45
|
const dynamicStartSchema = applyArgumentDescriptions(startToolSchema, {
|
|
48
46
|
...startPreset.args,
|
|
49
47
|
});
|
package/build/types/tooling.js
CHANGED
|
@@ -22,16 +22,22 @@ export const askToolSchema = z.object({
|
|
|
22
22
|
name: z.string().optional(),
|
|
23
23
|
cwd: z.string().optional(),
|
|
24
24
|
});
|
|
25
|
-
export const closeToolSchema = z.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
25
|
+
export const closeToolSchema = z.discriminatedUnion("petition", [
|
|
26
|
+
z.object({
|
|
27
|
+
petition: z.literal("CLAIM"),
|
|
28
|
+
criteriaChecks: z.array(z.string().trim().min(1)).min(1),
|
|
29
|
+
constraintsChecks: z.array(z.string().trim().min(1)).min(1),
|
|
30
|
+
score_precise: z.number().min(0).max(10),
|
|
31
|
+
score_minimal: z.number().min(0).max(10),
|
|
32
|
+
score_isolated: z.number().min(0).max(10),
|
|
33
|
+
score_idiomatic: z.number().min(0).max(10),
|
|
34
|
+
score_cohesive: z.number().min(0).max(10),
|
|
35
|
+
oath: z.string().optional(),
|
|
36
|
+
cwd: z.string().optional(),
|
|
37
|
+
}),
|
|
38
|
+
z.object({
|
|
39
|
+
petition: z.literal("FORFEIT"),
|
|
40
|
+
cwd: z.string().optional(),
|
|
41
|
+
}),
|
|
42
|
+
]);
|
|
37
43
|
export const helpToolSchema = z.object({});
|
package/build/utils/git-ops.js
CHANGED
|
@@ -265,6 +265,16 @@ export async function getKeiyakuBase(cwd, branchName) {
|
|
|
265
265
|
return null;
|
|
266
266
|
}
|
|
267
267
|
}
|
|
268
|
+
export async function getLatestCommitHash(cwd) {
|
|
269
|
+
const git = createGit(cwd);
|
|
270
|
+
try {
|
|
271
|
+
const hash = await git.revparse(["--short", "HEAD"]);
|
|
272
|
+
return hash.trim();
|
|
273
|
+
}
|
|
274
|
+
catch (err) {
|
|
275
|
+
throw wrapGitError("rev-parse --short HEAD", err, cwd);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
268
278
|
export async function clearKeiyakuBase(cwd, branchName) {
|
|
269
279
|
const git = createGit(cwd);
|
|
270
280
|
try {
|
|
@@ -1,9 +1,43 @@
|
|
|
1
|
+
import { parseToAST, renderNodeContent } from "./keiyaku-document.js";
|
|
1
2
|
function isPlainObject(value) {
|
|
2
3
|
if (!value || typeof value !== "object")
|
|
3
4
|
return false;
|
|
4
5
|
const prototype = Object.getPrototypeOf(value);
|
|
5
6
|
return prototype === Object.prototype || prototype === null;
|
|
6
7
|
}
|
|
8
|
+
export function flattenMarkdownList(text) {
|
|
9
|
+
const ast = parseToAST(text, { allowSections: false });
|
|
10
|
+
const items = [];
|
|
11
|
+
let currentTitle;
|
|
12
|
+
const pushItem = (content) => {
|
|
13
|
+
const trimmed = content.trim();
|
|
14
|
+
if (!trimmed)
|
|
15
|
+
return;
|
|
16
|
+
const prefix = currentTitle ? `**${currentTitle.replace(/^\d+\.\s*/, "")}**: ` : "";
|
|
17
|
+
items.push(`${prefix}${trimmed}`);
|
|
18
|
+
};
|
|
19
|
+
for (const node of ast.children) {
|
|
20
|
+
if (node.type === "heading") {
|
|
21
|
+
currentTitle = node.text.trim();
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (node.type === "list") {
|
|
25
|
+
for (const listItem of node.items) {
|
|
26
|
+
pushItem(renderNodeContent(listItem));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
else if (node.type === "text") {
|
|
30
|
+
const paragraphs = node.value.split(/\n\s*\n/);
|
|
31
|
+
for (const p of paragraphs) {
|
|
32
|
+
pushItem(p);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
pushItem(renderNodeContent(node));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return items;
|
|
40
|
+
}
|
|
7
41
|
export function renderTemplate(template, values) {
|
|
8
42
|
return template.replace(/\$\{(?<key>[A-Za-z0-9_]+)\}/g, (match, _captured, _offset, _text, groups) => {
|
|
9
43
|
const key = groups?.key;
|
package/build/workflow/ask.js
CHANGED
|
@@ -5,7 +5,8 @@ import { runSubagent } from "../agents/round-runner.js";
|
|
|
5
5
|
import * as git from "../utils/git.js";
|
|
6
6
|
import { buildAskPrompt } from "./prompts.js";
|
|
7
7
|
import { FlowError } from "../common/errors.js";
|
|
8
|
-
import {
|
|
8
|
+
import { renderMarkdownSections } from "../utils/keiyaku-document.js";
|
|
9
|
+
import { flattenMarkdownList } from "../utils/text-utils.js";
|
|
9
10
|
const BASE_CONSTRAINTS_FILE = path.join(".keiyaku", "base-constraints.md");
|
|
10
11
|
function requireText(name, value) {
|
|
11
12
|
const normalized = value.trim();
|
|
@@ -21,7 +22,7 @@ export async function askServant(input) {
|
|
|
21
22
|
let referenceConstraints;
|
|
22
23
|
try {
|
|
23
24
|
const baseConstraintsRaw = await fs.readFile(path.join(cwd, BASE_CONSTRAINTS_FILE), "utf-8");
|
|
24
|
-
const baseConstraints =
|
|
25
|
+
const baseConstraints = flattenMarkdownList(baseConstraintsRaw);
|
|
25
26
|
if (baseConstraints.length > 0) {
|
|
26
27
|
referenceConstraints = renderMarkdownSections(baseConstraints);
|
|
27
28
|
}
|
package/build/workflow/drive.js
CHANGED
|
@@ -47,7 +47,6 @@ export async function driveServant(input) {
|
|
|
47
47
|
const traceState = computeTraceState(traceContent);
|
|
48
48
|
const title = keiyakuBranch.slice("keiyaku/".length);
|
|
49
49
|
const goal = readGoalFromKeiyaku(keiyakuContent);
|
|
50
|
-
const keiyakuContext = readKeiyakuSection(keiyakuContent, "Context");
|
|
51
50
|
const constraints = readKeiyakuSection(keiyakuContent, "Constraints");
|
|
52
51
|
const criteria = readKeiyakuSection(keiyakuContent, "Acceptance Criteria");
|
|
53
52
|
const normalizedDirective = requireText("directive", directive);
|
|
@@ -74,7 +73,6 @@ export async function driveServant(input) {
|
|
|
74
73
|
diff,
|
|
75
74
|
summary,
|
|
76
75
|
goal,
|
|
77
|
-
context: keiyakuContext,
|
|
78
76
|
constraints,
|
|
79
77
|
criteria,
|
|
80
78
|
currentBranch: keiyakuBranch,
|
|
@@ -97,8 +97,6 @@ function requireChecks(name, values) {
|
|
|
97
97
|
}
|
|
98
98
|
export async function presentWork(input) {
|
|
99
99
|
const { cwd } = input;
|
|
100
|
-
requireChecks("criteriaChecks", input.criteriaChecks);
|
|
101
|
-
requireChecks("constraintsChecks", input.constraintsChecks);
|
|
102
100
|
const isRepo = await git.isGitRepo(cwd);
|
|
103
101
|
if (!isRepo) {
|
|
104
102
|
throw new FlowError("NOT_GIT_REPO", `${cwd} is not a git repository`);
|
|
@@ -135,14 +133,17 @@ export async function presentWork(input) {
|
|
|
135
133
|
status: "success",
|
|
136
134
|
result: "dropped",
|
|
137
135
|
round,
|
|
138
|
-
currentBranch:
|
|
136
|
+
currentBranch: baseBranch,
|
|
139
137
|
baseBranch,
|
|
138
|
+
deletedBranch: keiyakuBranch,
|
|
140
139
|
diff: "Forfeited without merge.",
|
|
141
140
|
};
|
|
142
141
|
}
|
|
143
142
|
await ensureKeiyakuFiles(cwd);
|
|
144
143
|
const traceContent = await readTraceContent(cwd);
|
|
145
144
|
if (petition === "CLAIM") {
|
|
145
|
+
requireChecks("criteriaChecks", input.criteriaChecks);
|
|
146
|
+
requireChecks("constraintsChecks", input.constraintsChecks);
|
|
146
147
|
const verdict = await resolveVerdictConfig(cwd);
|
|
147
148
|
const failedCommandments = CLOSE_SCORE_FIELDS.flatMap((field) => {
|
|
148
149
|
const score = input[field];
|
|
@@ -194,6 +195,7 @@ export async function presentWork(input) {
|
|
|
194
195
|
console.error(invokeMergeLog);
|
|
195
196
|
appendDebugLog(invokeMergeLog, { cwd, section: "script" });
|
|
196
197
|
await git.merge(cwd, keiyakuBranch, message);
|
|
198
|
+
const mergedCommit = await git.getLatestCommitHash(cwd);
|
|
197
199
|
const invokeFinalizeLog = `[CLAIM] Deleting merged branch '${keiyakuBranch}' and clearing metadata`;
|
|
198
200
|
console.error(invokeFinalizeLog);
|
|
199
201
|
appendDebugLog(invokeFinalizeLog, { cwd, section: "script" });
|
|
@@ -202,10 +204,13 @@ export async function presentWork(input) {
|
|
|
202
204
|
const round = computeTraceState(traceContent).maxRound;
|
|
203
205
|
return {
|
|
204
206
|
status: "success",
|
|
205
|
-
result: "
|
|
207
|
+
result: "merged",
|
|
206
208
|
round,
|
|
207
|
-
currentBranch:
|
|
209
|
+
currentBranch: baseBranch,
|
|
208
210
|
baseBranch,
|
|
211
|
+
mergedCommit,
|
|
212
|
+
mergedInto: baseBranch,
|
|
213
|
+
deletedBranch: keiyakuBranch,
|
|
209
214
|
diff,
|
|
210
215
|
};
|
|
211
216
|
}
|
|
@@ -28,12 +28,12 @@ Round: 1 (initial implementation).
|
|
|
28
28
|
- [Logic delta]: [Key architectural or logic shift]
|
|
29
29
|
|
|
30
30
|
## Aesthetics Gap
|
|
31
|
-
Self-critique.
|
|
32
|
-
- precise: [
|
|
33
|
-
- minimal: [
|
|
34
|
-
- isolated: [
|
|
35
|
-
- idiomatic: [
|
|
36
|
-
- cohesive: [
|
|
31
|
+
Self-critique (The Delta). Focus only on what is missing or imperfect relative to the ideal. No scores.
|
|
32
|
+
- precise (Architectural placement): [Gap from exact layer/boundary, zero misplacement]
|
|
33
|
+
- minimal (Economy of change): [Gap from no avoidable lines, no speculative edits, no hidden bloat]
|
|
34
|
+
- isolated (Surgical containment): [Gap from zero unrelated files, zero opportunistic cleanup, zero collateral]
|
|
35
|
+
- idiomatic (Native fluency): [Gap from naming, structure, style indistinguishable from the codebase]
|
|
36
|
+
- cohesive (Single responsibility): [Gap from each unit does one thing, boundaries intact]
|
|
37
37
|
|
|
38
38
|
## Blindspots
|
|
39
39
|
- [Unintentional side effects or untested paths]
|
|
@@ -70,12 +70,12 @@ Round: ${round} (iteration after review).
|
|
|
70
70
|
- [Logic delta]: [Key architectural or logic shift]
|
|
71
71
|
|
|
72
72
|
## Aesthetics Gap
|
|
73
|
-
Self-critique.
|
|
74
|
-
- precise: [
|
|
75
|
-
- minimal: [
|
|
76
|
-
- isolated: [
|
|
77
|
-
- idiomatic: [
|
|
78
|
-
- cohesive: [
|
|
73
|
+
Self-critique (The Delta). Focus only on what is missing or imperfect relative to the ideal. No scores.
|
|
74
|
+
- precise (Architectural placement): [Gap from exact layer/boundary, zero misplacement]
|
|
75
|
+
- minimal (Economy of change): [Gap from no avoidable lines, no speculative edits, no hidden bloat]
|
|
76
|
+
- isolated (Surgical containment): [Gap from zero unrelated files, zero opportunistic cleanup, zero collateral]
|
|
77
|
+
- idiomatic (Native fluency): [Gap from naming, structure, style indistinguishable from the codebase]
|
|
78
|
+
- cohesive (Single responsibility): [Gap from each unit does one thing, boundaries intact]
|
|
79
79
|
|
|
80
80
|
## Blindspots
|
|
81
81
|
- [Unintentional side effects or untested paths]
|
|
@@ -146,7 +146,6 @@ export function buildDriveResponse(result, input) {
|
|
|
146
146
|
const { status: _status, ...resultData } = result;
|
|
147
147
|
const summarySection = buildSection("Summary", result.summary);
|
|
148
148
|
const goalSection = buildSection("Goal", truncateForDisplay(result.goal, 800));
|
|
149
|
-
const contextSection = buildSection("Context", truncateForDisplay(result.context ?? "", 1200));
|
|
150
149
|
const constraintsSection = buildSection("Constraints", truncateForDisplay(result.constraints ?? "", 1200));
|
|
151
150
|
const criteriaSection = buildSection("Criteria", truncateForDisplay(result.criteria ?? "", 1200));
|
|
152
151
|
const diffSection = buildSection("Diff", result.diff);
|
|
@@ -156,7 +155,7 @@ export function buildDriveResponse(result, input) {
|
|
|
156
155
|
...formatMaybe("Current Branch", result.currentBranch, 200),
|
|
157
156
|
...formatMaybe("Base Branch", result.baseBranch, 200),
|
|
158
157
|
];
|
|
159
|
-
const text = assembleResponse(`◆ Driven (Round ${result.round})`, `Updated branch '${result.currentBranch}'.`, [summarySection, goalSection,
|
|
158
|
+
const text = assembleResponse(`◆ Driven (Round ${result.round})`, `Updated branch '${result.currentBranch}'.`, [summarySection, goalSection, constraintsSection, criteriaSection, diffSection].filter((section) => section !== null), infoLines);
|
|
160
159
|
return {
|
|
161
160
|
content: [{ type: "text", text }],
|
|
162
161
|
structuredContent: buildSuccessStructuredContent(getDriveToolName(), resultData),
|
|
@@ -181,6 +180,10 @@ export function buildCloseDoneResponse(result, input) {
|
|
|
181
180
|
const diffSection = result.diff ? buildSection("Diff", result.diff) : "";
|
|
182
181
|
const infoLines = [
|
|
183
182
|
...formatMaybe("Path", input.cwd, 300),
|
|
183
|
+
...formatMaybe("Result", result.result, 100),
|
|
184
|
+
...formatMaybe("Merged Commit", result.mergedCommit, 100),
|
|
185
|
+
...formatMaybe("Merged Into", result.mergedInto, 200),
|
|
186
|
+
...formatMaybe("Deleted Branch", result.deletedBranch, 200),
|
|
184
187
|
...formatMaybe("Current Branch", result.currentBranch, 200),
|
|
185
188
|
...formatMaybe("Base Branch", result.baseBranch, 200),
|
|
186
189
|
...formatList("Criteria Checks", input.criteriaChecks, { maxItems: 10, maxItemChars: 220 }),
|
|
@@ -188,7 +191,7 @@ export function buildCloseDoneResponse(result, input) {
|
|
|
188
191
|
`Scores: precise=${input.score_precise}/10 minimal=${input.score_minimal}/10 isolated=${input.score_isolated}/10 idiomatic=${input.score_idiomatic}/10 cohesive=${input.score_cohesive}/10`,
|
|
189
192
|
...formatMaybe("Oath", input.oath, 220),
|
|
190
193
|
];
|
|
191
|
-
const text = assembleResponse("✓ Keiyaku Fulfilled (CLAIM)", `Merged '${result.
|
|
194
|
+
const text = assembleResponse("✓ Keiyaku Fulfilled (CLAIM)", `Merged '${result.deletedBranch}' into '${result.mergedInto}' (commit: ${result.mergedCommit}). Deleted feature branch.`, [typeof diffSection === "string" ? null : diffSection].filter((section) => section !== null), infoLines);
|
|
192
195
|
return {
|
|
193
196
|
content: [{ type: "text", text }],
|
|
194
197
|
structuredContent: buildSuccessStructuredContent(closeToolName, resultData),
|
|
@@ -199,23 +202,19 @@ export function buildCloseDropResponse(result, input) {
|
|
|
199
202
|
const closeToolName = getCloseToolName();
|
|
200
203
|
const infoLines = [
|
|
201
204
|
...formatMaybe("Path", input.cwd, 300),
|
|
205
|
+
...formatMaybe("Deleted Branch", result.deletedBranch, 200),
|
|
202
206
|
...formatMaybe("Current Branch", result.currentBranch, 200),
|
|
203
207
|
...formatMaybe("Base Branch", result.baseBranch, 200),
|
|
204
|
-
...formatList("Reasons/Checks", input.criteriaChecks, { maxItems: 10, maxItemChars: 220 }),
|
|
205
|
-
...formatList("Constraints Checks", input.constraintsChecks ?? [], { maxItems: 10, maxItemChars: 220 }),
|
|
206
|
-
`Scores: precise=${input.score_precise}/10 minimal=${input.score_minimal}/10 isolated=${input.score_isolated}/10 idiomatic=${input.score_idiomatic}/10 cohesive=${input.score_cohesive}/10`,
|
|
207
208
|
];
|
|
208
|
-
const text = assembleResponse("✗ Keiyaku Forfeited (FORFEIT)", `Deleted '${result.
|
|
209
|
+
const text = assembleResponse("✗ Keiyaku Forfeited (FORFEIT)", `Deleted '${result.deletedBranch}'. Switched back to '${result.baseBranch}'.`, [], infoLines);
|
|
209
210
|
return {
|
|
210
211
|
content: [{ type: "text", text }],
|
|
211
212
|
structuredContent: buildSuccessStructuredContent(closeToolName, resultData),
|
|
212
213
|
};
|
|
213
214
|
}
|
|
214
215
|
export function buildToolErrorResponse(input) {
|
|
215
|
-
const inputEcho = (input.inputEcho ?? []).map((line) => truncateForDisplay(line, 800));
|
|
216
216
|
const shouldRaiseProtocolError = input.errorType === "runtime_error";
|
|
217
|
-
const
|
|
218
|
-
const text = assembleResponse(colorizeErrorStatus("!! FAILED !!"), input.message, [contextSection].filter((section) => section !== null), [`Hint: ${input.hint}`]);
|
|
217
|
+
const text = assembleResponse(colorizeErrorStatus("!! FAILED !!"), input.message, [], [`Hint: ${input.hint}`]);
|
|
219
218
|
const response = {
|
|
220
219
|
content: [{ type: "text", text }],
|
|
221
220
|
structuredContent: {
|
package/build/workflow/start.js
CHANGED
|
@@ -6,9 +6,10 @@ import { FlowError, isFlowError, wrapFlowError } from "../common/errors.js";
|
|
|
6
6
|
import * as git from "../utils/git.js";
|
|
7
7
|
import { computeTraceState, readTraceContent } from "../utils/trace.js";
|
|
8
8
|
import { buildStartPrompt } from "./prompts.js";
|
|
9
|
-
import { parseToAST, renderSectionContent, extractListItems, renderKeiyaku, } from "../utils/keiyaku-document.js";
|
|
9
|
+
import { parseToAST, parseMarkdownStructure, renderSectionContent, extractListItems, renderKeiyaku, } from "../utils/keiyaku-document.js";
|
|
10
10
|
import { resolveTermPreset } from "../config/term-presets.js";
|
|
11
11
|
import { renderRoundSummary, TOOL_DEFAULT_POLICY } from "./round-summary.js";
|
|
12
|
+
import { flattenMarkdownList } from "../utils/text-utils.js";
|
|
12
13
|
import { assertCleanWorkingTree } from "./contract.js";
|
|
13
14
|
import { runAndRecordRound } from "./round.js";
|
|
14
15
|
const BASE_CONSTRAINTS_FILE = path.join(".keiyaku", "base-constraints.md");
|
|
@@ -174,12 +175,6 @@ function truncateForMessage(text, maxChars) {
|
|
|
174
175
|
return text;
|
|
175
176
|
return `${text.slice(0, maxChars)}\n...[truncated ${text.length - maxChars} chars]...`;
|
|
176
177
|
}
|
|
177
|
-
function readKeiyakuSection(content, sectionTitle) {
|
|
178
|
-
const escapedTitle = sectionTitle.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
179
|
-
const sectionMatch = content.match(new RegExp(`(?:^|\\n)## ${escapedTitle}\\s*\\n([\\s\\S]*?)(?=\\n##\\s|$)`));
|
|
180
|
-
const sectionBody = sectionMatch?.[1]?.trim();
|
|
181
|
-
return sectionBody || undefined;
|
|
182
|
-
}
|
|
183
178
|
async function buildActiveKeiyakuStartMessage(cwd, branch) {
|
|
184
179
|
const preset = resolveTermPreset();
|
|
185
180
|
const { drive, close } = preset.tools;
|
|
@@ -347,8 +342,7 @@ async function resolveStartInput(cwd, input) {
|
|
|
347
342
|
async function readBaseConstraints(cwd) {
|
|
348
343
|
try {
|
|
349
344
|
const baseConstraintsRaw = await fs.readFile(path.join(cwd, BASE_CONSTRAINTS_FILE), "utf-8");
|
|
350
|
-
|
|
351
|
-
return normalizedBaseConstraints ? normalizeMarkdownListItems("constraints", [baseConstraintsRaw]) : [];
|
|
345
|
+
return flattenMarkdownList(baseConstraintsRaw);
|
|
352
346
|
}
|
|
353
347
|
catch (error) {
|
|
354
348
|
if (error?.code !== "ENOENT") {
|
|
@@ -454,7 +448,8 @@ export async function startKeiyaku(input) {
|
|
|
454
448
|
// but safe to re-render if somehow undefined.
|
|
455
449
|
keiyakuContent = await renderStartKeiyakuContent(cwd, resolved);
|
|
456
450
|
}
|
|
457
|
-
const
|
|
451
|
+
const parsedKeiyaku = parseMarkdownStructure(keiyakuContent);
|
|
452
|
+
const constraintsSection = parsedKeiyaku.sections.get("constraints")?.join("\n").trim() ?? "";
|
|
458
453
|
await fs.writeFile(path.join(cwd, KEIYAKU_FILE), keiyakuContent);
|
|
459
454
|
await fs.writeFile(path.join(cwd, TRACE_FILE), "# Keiyaku Trace\n");
|
|
460
455
|
await git.addFiles(cwd, [KEIYAKU_FILE, TRACE_FILE]);
|