@astrosheep/keiyaku 0.1.48 → 0.1.49
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/.tsbuildinfo +1 -1
- package/build/common/response-style.js +2 -0
- package/build/config/term-presets/constants.js +1 -1
- package/build/handlers/status.js +1 -1
- package/build/index.js +1 -1
- package/build/utils/draft.js +22 -0
- package/build/utils/git-ops.js +9 -0
- package/build/utils/keiyaku-document/lex.js +27 -0
- package/build/utils/keiyaku-document/parser.js +24 -0
- package/build/utils/keiyaku-document/render.js +4 -0
- package/build/utils/keiyaku-document.test.js +11 -0
- package/build/utils/trace.js +5 -6
- package/build/workflow/ask-execution.js +30 -2
- package/build/workflow/drive.js +2 -2
- package/build/workflow/{contract.js → keiyaku.js} +1 -1
- package/build/workflow/markdown-normalization.js +2 -1
- package/build/workflow/present.js +9 -12
- package/build/workflow/response-builders.js +43 -58
- package/build/workflow/response-renderer.js +2 -13
- package/build/workflow/start.js +1 -1
- package/build/workflow/status.js +77 -44
- package/package.json +1 -1
|
@@ -38,6 +38,10 @@ class MarkdownParser {
|
|
|
38
38
|
blocks.push(this.parseList(token.indent, token.ordered));
|
|
39
39
|
continue;
|
|
40
40
|
}
|
|
41
|
+
if (token.type === "blockquote") {
|
|
42
|
+
blocks.push(this.parseBlockquote());
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
41
45
|
blocks.push(this.parseText(shouldStop));
|
|
42
46
|
}
|
|
43
47
|
return blocks;
|
|
@@ -92,6 +96,24 @@ class MarkdownParser {
|
|
|
92
96
|
text: header.text,
|
|
93
97
|
};
|
|
94
98
|
}
|
|
99
|
+
parseBlockquote() {
|
|
100
|
+
const lines = [];
|
|
101
|
+
let marker = "> ";
|
|
102
|
+
while (!this.isEof()) {
|
|
103
|
+
const token = this.peek();
|
|
104
|
+
if (!token || token.type !== "blockquote")
|
|
105
|
+
break;
|
|
106
|
+
marker = token.marker;
|
|
107
|
+
lines.push(token.body);
|
|
108
|
+
this.consume();
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
type: "blockquote",
|
|
112
|
+
marker,
|
|
113
|
+
lines,
|
|
114
|
+
value: lines.join("\n"),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
95
117
|
parseList(indent, ordered) {
|
|
96
118
|
const items = [];
|
|
97
119
|
while (!this.isEof()) {
|
|
@@ -181,6 +203,8 @@ class MarkdownParser {
|
|
|
181
203
|
break;
|
|
182
204
|
if (token.type === "list_marker" && token.indent <= 3)
|
|
183
205
|
break;
|
|
206
|
+
if (token.type === "blockquote")
|
|
207
|
+
break;
|
|
184
208
|
if (this.options.allowSections && isSectionHeaderToken(token))
|
|
185
209
|
break;
|
|
186
210
|
lines.push(token.raw);
|
|
@@ -21,6 +21,8 @@ export function renderBlock(node) {
|
|
|
21
21
|
return node.items.map((item) => renderListItemFirstLine(item)).join("\n");
|
|
22
22
|
case "code_block":
|
|
23
23
|
return node.lines.join("\n");
|
|
24
|
+
case "blockquote":
|
|
25
|
+
return node.lines.map((line) => `${node.marker}${line}`).join("\n");
|
|
24
26
|
case "text":
|
|
25
27
|
return node.value;
|
|
26
28
|
case "heading":
|
|
@@ -39,6 +41,8 @@ export function renderNodeContent(node) {
|
|
|
39
41
|
return node.children.map((child) => renderBlock(child)).join("\n");
|
|
40
42
|
case "code_block":
|
|
41
43
|
return node.lines.join("\n");
|
|
44
|
+
case "blockquote":
|
|
45
|
+
return node.lines.map((line) => `${node.marker}${line}`).join("\n");
|
|
42
46
|
case "text":
|
|
43
47
|
return node.value;
|
|
44
48
|
case "heading":
|
|
@@ -55,6 +55,17 @@ test("parseToAST keeps fenced code blocks as structured nodes", () => {
|
|
|
55
55
|
assert.equal(section.children[0]?.type === "code_block" ? section.children[0].lines.join("\n") : "", "```md\n## Not A Section\n```");
|
|
56
56
|
assert.equal(section.children[1]?.type === "text" ? section.children[1].value : "", "After fence.");
|
|
57
57
|
});
|
|
58
|
+
test("parseToAST keeps blockquote blocks as structured nodes", () => {
|
|
59
|
+
const markdown = ["## Summary", "> ## Outcome", "> Implemented parser.", ">", "> ## Changes", "> - Added tests."].join("\n");
|
|
60
|
+
const ast = parseToAST(markdown);
|
|
61
|
+
const section = ast.children[0];
|
|
62
|
+
assert.equal(section?.type, "section");
|
|
63
|
+
if (section?.type !== "section")
|
|
64
|
+
return;
|
|
65
|
+
assert.equal(section.children[0]?.type, "blockquote");
|
|
66
|
+
assert.equal(section.children[0]?.type === "blockquote" ? section.children[0].value : "", "## Outcome\nImplemented parser.\n\n## Changes\n- Added tests.");
|
|
67
|
+
assert.equal(section.children[0]?.type === "blockquote" ? renderSectionContent(section) : "", "> ## Outcome\n> Implemented parser.\n> \n> ## Changes\n> - Added tests.");
|
|
68
|
+
});
|
|
58
69
|
test("parseMarkdownListSection flattens list items and ignores source headers", () => {
|
|
59
70
|
const markdown = [
|
|
60
71
|
"## Group A",
|
package/build/utils/trace.js
CHANGED
|
@@ -6,6 +6,8 @@ import { parseToAST } from "./keiyaku-document/index.js";
|
|
|
6
6
|
const DIFF_COORDINATES_SECTION_PREFIX = "Diff Coordinates";
|
|
7
7
|
const DIFF_COORDINATES_FENCE = "```keiyaku-coords";
|
|
8
8
|
const DIFF_COORDINATE_LINE_RE = /^(.*):(\d+)(?:-(\d+))?$/;
|
|
9
|
+
const BLOCKQUOTE_PREFIX = "> ";
|
|
10
|
+
const EMPTY_BLOCKQUOTE_LINE = ">";
|
|
9
11
|
async function fileExists(cwd, filepath) {
|
|
10
12
|
try {
|
|
11
13
|
await fs.access(path.join(cwd, filepath));
|
|
@@ -33,6 +35,7 @@ export async function appendReview(cwd, round, reason) {
|
|
|
33
35
|
export async function appendRoundReport(cwd, report) {
|
|
34
36
|
const summaryText = report.summary.trim().length > 0 ? report.summary : "Unknown error.";
|
|
35
37
|
const tracePath = path.join(cwd, TRACE_FILE);
|
|
38
|
+
const quotedSummary = summaryText.split(/\r?\n/).map((line) => (line.length > 0 ? `${BLOCKQUOTE_PREFIX}${line}` : EMPTY_BLOCKQUOTE_LINE));
|
|
36
39
|
const lines = [
|
|
37
40
|
"",
|
|
38
41
|
"---",
|
|
@@ -41,9 +44,7 @@ export async function appendRoundReport(cwd, report) {
|
|
|
41
44
|
"### Status",
|
|
42
45
|
report.status,
|
|
43
46
|
"### Summary",
|
|
44
|
-
|
|
45
|
-
summaryText,
|
|
46
|
-
"```",
|
|
47
|
+
...quotedSummary,
|
|
47
48
|
];
|
|
48
49
|
if (report.errorMessage) {
|
|
49
50
|
lines.push("### Error", report.errorMessage);
|
|
@@ -62,9 +63,7 @@ export async function appendRoundSystemNote(cwd, round, note) {
|
|
|
62
63
|
"### Status",
|
|
63
64
|
"FAILED",
|
|
64
65
|
"### Summary",
|
|
65
|
-
|
|
66
|
-
"Subagent execution cancelled by user/client.",
|
|
67
|
-
"```",
|
|
66
|
+
`${BLOCKQUOTE_PREFIX}Subagent execution cancelled by user/client.`,
|
|
68
67
|
"### Error",
|
|
69
68
|
note,
|
|
70
69
|
"",
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TRACE_FILE } from "../common/constants.js";
|
|
2
2
|
import { appendAsk } from "../utils/trace.js";
|
|
3
3
|
import { appendDebugLog } from "../utils/debug-log.js";
|
|
4
|
+
import { logWarn } from "../utils/logger.js";
|
|
4
5
|
import { runAsk } from "./ask.js";
|
|
6
|
+
import { assertKeiyakuProtocolFiles } from "./keiyaku.js";
|
|
5
7
|
import { persistAskHistory } from "../utils/ask-history.js";
|
|
8
|
+
import * as git from "../utils/git.js";
|
|
9
|
+
const ASK_TRACE_COMMIT_PREFIX = "ask: ";
|
|
10
|
+
const ASK_TRACE_COMMIT_SUMMARY_MAX_CHARS = 50;
|
|
11
|
+
function buildAskTraceCommitMessage(request) {
|
|
12
|
+
const summary = request.replaceAll(/\s+/g, " ").trim().slice(0, ASK_TRACE_COMMIT_SUMMARY_MAX_CHARS);
|
|
13
|
+
return `${ASK_TRACE_COMMIT_PREFIX}${summary}`;
|
|
14
|
+
}
|
|
6
15
|
export async function runAskAndPersist(input) {
|
|
7
16
|
const result = await runAsk(input);
|
|
8
17
|
let warning;
|
|
@@ -10,7 +19,7 @@ export async function runAskAndPersist(input) {
|
|
|
10
19
|
try {
|
|
11
20
|
let commit;
|
|
12
21
|
try {
|
|
13
|
-
commit = await getLatestCommitHash(input.cwd);
|
|
22
|
+
commit = await git.getLatestCommitHash(input.cwd);
|
|
14
23
|
}
|
|
15
24
|
catch {
|
|
16
25
|
// Ask can run outside git repos; history should still be persisted.
|
|
@@ -41,6 +50,25 @@ export async function runAskAndPersist(input) {
|
|
|
41
50
|
summary: result.summary,
|
|
42
51
|
diffStats: result.diffStats,
|
|
43
52
|
});
|
|
53
|
+
try {
|
|
54
|
+
const activeState = await git.getActiveKeiyakuGitState(input.cwd);
|
|
55
|
+
if (activeState) {
|
|
56
|
+
await assertKeiyakuProtocolFiles(input.cwd);
|
|
57
|
+
try {
|
|
58
|
+
await git.addFiles(input.cwd, TRACE_FILE);
|
|
59
|
+
await git.commit(input.cwd, buildAskTraceCommitMessage(input.request));
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
63
|
+
const commitWarning = `Failed to commit ask trace update: ${message}`;
|
|
64
|
+
logWarn(commitWarning, { cwd: input.cwd, section: "script" });
|
|
65
|
+
warning = warning ? `${warning}\n${commitWarning}` : commitWarning;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Non-active keiyaku or missing protocol files should not block ask.
|
|
71
|
+
}
|
|
44
72
|
}
|
|
45
73
|
catch (askTraceError) {
|
|
46
74
|
const askTraceErrorMessage = askTraceError instanceof Error ? askTraceError.message : String(askTraceError);
|
package/build/workflow/drive.js
CHANGED
|
@@ -7,7 +7,7 @@ import * as git from "../utils/git.js";
|
|
|
7
7
|
import { KeiyakuParseError, parseMarkdownStructure } from "../utils/keiyaku-document/index.js";
|
|
8
8
|
import { logWarn } from "../utils/logger.js";
|
|
9
9
|
import { renderToolRoundSummary } from "./round-summary.js";
|
|
10
|
-
import { assertCleanWorkingTree,
|
|
10
|
+
import { assertCleanWorkingTree, assertKeiyakuProtocolFiles, buildNoActiveKeiyakuGuidance } from "./keiyaku.js";
|
|
11
11
|
import { buildRoundCommitMessage, runAndRecordRound } from "./round.js";
|
|
12
12
|
import { resolveIncrementalDiffMode } from "../config/incremental-diff-mode.js";
|
|
13
13
|
import { buildIteratePlan } from "./iterate-plan.js";
|
|
@@ -58,7 +58,7 @@ export async function driveServant(input) {
|
|
|
58
58
|
throw new FlowError("MISSING_KEIYAKU_BASE", `branch ${keiyakuBranch} is missing base metadata; cannot continue drive`);
|
|
59
59
|
}
|
|
60
60
|
await assertCleanWorkingTree(cwd, [KEIYAKU_DRAFT_FILE]);
|
|
61
|
-
await
|
|
61
|
+
await assertKeiyakuProtocolFiles(cwd);
|
|
62
62
|
const keiyakuContent = await fs.readFile(path.join(cwd, KEIYAKU_FILE), "utf-8");
|
|
63
63
|
const parsedKeiyaku = parseKeiyakuStructure(keiyakuContent);
|
|
64
64
|
const traceContent = await readTraceContent(cwd);
|
|
@@ -7,7 +7,7 @@ import * as git from "../utils/git.js";
|
|
|
7
7
|
import { resolveOath } from "./oath.js";
|
|
8
8
|
export { resolveOath };
|
|
9
9
|
const DIRTY_WORKTREE_LIST_LIMIT = 10;
|
|
10
|
-
export async function
|
|
10
|
+
export async function assertKeiyakuProtocolFiles(cwd) {
|
|
11
11
|
const keiyakuPath = path.join(cwd, KEIYAKU_FILE);
|
|
12
12
|
const tracePath = path.join(cwd, TRACE_FILE);
|
|
13
13
|
try {
|
|
@@ -19,7 +19,7 @@ export function demoteMarkdownHeadings(text, minLevel = 3) {
|
|
|
19
19
|
if (node.type === "heading") {
|
|
20
20
|
shallowest = shallowest === null ? node.level : Math.min(shallowest, node.level);
|
|
21
21
|
}
|
|
22
|
-
if (node.type === "code_block" || node.type === "text")
|
|
22
|
+
if (node.type === "code_block" || node.type === "blockquote" || node.type === "text")
|
|
23
23
|
continue;
|
|
24
24
|
if (node.type === "document" || node.type === "section" || node.type === "list_item") {
|
|
25
25
|
stack.push(...node.children);
|
|
@@ -41,6 +41,7 @@ export function demoteMarkdownHeadings(text, minLevel = 3) {
|
|
|
41
41
|
return nextLevel === node.level ? node : { ...node, level: nextLevel };
|
|
42
42
|
}
|
|
43
43
|
case "code_block":
|
|
44
|
+
case "blockquote":
|
|
44
45
|
case "text":
|
|
45
46
|
return node;
|
|
46
47
|
case "document":
|
|
@@ -5,10 +5,11 @@ import { KEIYAKU_ARCHIVE_TAG_PREFIX, KEIYAKU_BRANCH_PREFIX, KEIYAKU_DRAFT_FILE,
|
|
|
5
5
|
import { resolveTermPreset } from "../config/term-presets/index.js";
|
|
6
6
|
import { logInfo, logWarn } from "../utils/logger.js";
|
|
7
7
|
import { FlowError, wrapFlowError } from "../common/errors.js";
|
|
8
|
+
import { readDraftSnapshot, restoreDraftSnapshot } from "../utils/draft.js";
|
|
8
9
|
import * as git from "../utils/git.js";
|
|
9
10
|
import { appendVerdict, computeTraceState, readTraceContent } from "../utils/trace.js";
|
|
10
11
|
import { oathMatches } from "./oath.js";
|
|
11
|
-
import { assertCleanWorkingTree,
|
|
12
|
+
import { assertCleanWorkingTree, assertKeiyakuProtocolFiles, buildNoActiveKeiyakuGuidance, resolveOath } from "./keiyaku.js";
|
|
12
13
|
const DIMENSIONS = [
|
|
13
14
|
"placement",
|
|
14
15
|
"exactness",
|
|
@@ -212,14 +213,7 @@ export async function presentWork(input) {
|
|
|
212
213
|
}
|
|
213
214
|
const title = keiyakuBranch.slice(KEIYAKU_BRANCH_PREFIX.length);
|
|
214
215
|
const petition = input.petition;
|
|
215
|
-
|
|
216
|
-
try {
|
|
217
|
-
const stat = await fs.stat(path.join(cwd, KEIYAKU_DRAFT_FILE));
|
|
218
|
-
draftKept = stat.isFile();
|
|
219
|
-
}
|
|
220
|
-
catch {
|
|
221
|
-
// ignored
|
|
222
|
-
}
|
|
216
|
+
const draftSnapshot = await readDraftSnapshot(cwd);
|
|
223
217
|
if (petition === "FORFEIT") {
|
|
224
218
|
const archiveTag = `${KEIYAKU_ARCHIVE_TAG_PREFIX}${title}`;
|
|
225
219
|
let round = 0;
|
|
@@ -238,6 +232,9 @@ export async function presentWork(input) {
|
|
|
238
232
|
await git.discardAllWorkingTreeChanges(cwd);
|
|
239
233
|
}
|
|
240
234
|
await git.checkoutBranch(cwd, baseBranch);
|
|
235
|
+
if (draftSnapshot) {
|
|
236
|
+
await restoreDraftSnapshot(cwd, draftSnapshot);
|
|
237
|
+
}
|
|
241
238
|
await git.deleteBranch(cwd, keiyakuBranch, true);
|
|
242
239
|
await git.clearKeiyakuBase(cwd, keiyakuBranch);
|
|
243
240
|
}
|
|
@@ -256,10 +253,10 @@ export async function presentWork(input) {
|
|
|
256
253
|
? `Forfeited without merge. Dropped ${droppedChanges.length} local change(s).`
|
|
257
254
|
: "Forfeited without merge.",
|
|
258
255
|
droppedChanges,
|
|
259
|
-
|
|
256
|
+
draftPath: draftSnapshot?.path ?? null,
|
|
260
257
|
};
|
|
261
258
|
}
|
|
262
|
-
await
|
|
259
|
+
await assertKeiyakuProtocolFiles(cwd);
|
|
263
260
|
let traceContent = await readTraceContent(cwd);
|
|
264
261
|
if (petition === "CLAIM") {
|
|
265
262
|
const driveCommandName = resolveTermPreset().tools.drive.name;
|
|
@@ -340,7 +337,7 @@ export async function presentWork(input) {
|
|
|
340
337
|
mergedInto: baseBranch,
|
|
341
338
|
deletedBranch: keiyakuBranch,
|
|
342
339
|
diff,
|
|
343
|
-
|
|
340
|
+
draftPath: draftSnapshot?.path ?? null,
|
|
344
341
|
};
|
|
345
342
|
}
|
|
346
343
|
catch (err) {
|
|
@@ -6,6 +6,12 @@ const FORMAT_LIST_MAX_ITEMS = 16;
|
|
|
6
6
|
const FORMAT_LIST_MAX_ITEM_CHARS = 400;
|
|
7
7
|
const RULES_SECTION_MAX_CHARS = 5000;
|
|
8
8
|
const BRANCH_LABEL = "Branch";
|
|
9
|
+
const INFO_SECTION_TITLE = "Info";
|
|
10
|
+
const HINTS_SECTION_TITLE = "Hints";
|
|
11
|
+
const STATUS_KEIYAKU_SECTION_TITLE = "Keiyaku";
|
|
12
|
+
const STATUS_BLOCKED_SECTION_TITLE = "Blocked";
|
|
13
|
+
const STATUS_ACTIVE_SUMMARY_PREFIX = "● Active";
|
|
14
|
+
const STATUS_INACTIVE_SUMMARY = "○ Inactive";
|
|
9
15
|
// --- Helpers ---
|
|
10
16
|
function truncateForDisplay(raw, maxChars = DISPLAY_TEXT_MAX_CHARS) {
|
|
11
17
|
const text = raw.trim();
|
|
@@ -47,17 +53,14 @@ function buildSection(title, content) {
|
|
|
47
53
|
return null;
|
|
48
54
|
return { title, body };
|
|
49
55
|
}
|
|
50
|
-
function formatBlockedOperation(label,
|
|
51
|
-
if (
|
|
56
|
+
function formatBlockedOperation(label, blockers) {
|
|
57
|
+
if (blockers.length === 0)
|
|
52
58
|
return [];
|
|
53
59
|
return [
|
|
54
|
-
`${label}
|
|
55
|
-
...formatList(`${label} Blockers`,
|
|
60
|
+
`${label}:`,
|
|
61
|
+
...formatList(`${label} Blockers`, blockers, { maxItems: 10, maxItemChars: 300 }),
|
|
56
62
|
];
|
|
57
63
|
}
|
|
58
|
-
function formatStatusFileSignal(signal) {
|
|
59
|
-
return `${signal.path}: ${signal.present ? "present ✓" : "missing ✕"}`;
|
|
60
|
-
}
|
|
61
64
|
function buildSuccessStructuredContent(data) {
|
|
62
65
|
return {
|
|
63
66
|
status: "success",
|
|
@@ -71,9 +74,9 @@ export function buildKeiyakuSuccessResponse(result, input) {
|
|
|
71
74
|
const responseData = normalizedRules ? { ...resultData, rules: normalizedRules } : { ...resultData };
|
|
72
75
|
const summarySection = buildSection("Summary", result.summary);
|
|
73
76
|
const rulesSection = buildSection("Rules", truncateForDisplay(normalizedRules, RULES_SECTION_MAX_CHARS));
|
|
74
|
-
const hintsSection = buildSection(
|
|
77
|
+
const hintsSection = buildSection(HINTS_SECTION_TITLE, formatHints(result.meta?.hints ?? [], { maxItems: 12, maxItemChars: 400 }));
|
|
75
78
|
const diffSection = buildSection(DIFF_OVERVIEW_SECTION_TITLE, result.diff);
|
|
76
|
-
const
|
|
79
|
+
const infoSection = buildSection(INFO_SECTION_TITLE, [
|
|
77
80
|
...formatMaybe("Identity", input.name, 120),
|
|
78
81
|
...formatMaybe("Consumed from_file and deleted", result.consumedFromFile, 300),
|
|
79
82
|
...formatList("Existing local keiyaku branches (non-blocking)", result.existingBranches ?? [], {
|
|
@@ -83,11 +86,11 @@ export function buildKeiyakuSuccessResponse(result, input) {
|
|
|
83
86
|
...formatMaybe(BRANCH_LABEL, result.currentBranch, 200),
|
|
84
87
|
...formatMaybe("Base Branch", result.baseBranch, 200),
|
|
85
88
|
...formatMaybe("Commit", result.commit, 100),
|
|
86
|
-
];
|
|
89
|
+
]);
|
|
87
90
|
const summaryLine = result.commit
|
|
88
91
|
? `Created branch '${result.currentBranch}' (base: '${result.baseBranch}') [${result.commit}].`
|
|
89
92
|
: `Created branch '${result.currentBranch}' (base: '${result.baseBranch}').`;
|
|
90
|
-
const text = assembleResponse(`${RESPONSE_MARKERS.round} Started (Round ${result.round})`, summaryLine, [summarySection, rulesSection,
|
|
93
|
+
const text = assembleResponse(`${RESPONSE_MARKERS.round} Started (Round ${result.round})`, summaryLine, [summarySection, rulesSection, infoSection, diffSection, hintsSection].filter((section) => section !== null));
|
|
91
94
|
return {
|
|
92
95
|
content: [{ type: "text", text }],
|
|
93
96
|
structuredContent: buildSuccessStructuredContent(responseData),
|
|
@@ -96,18 +99,18 @@ export function buildKeiyakuSuccessResponse(result, input) {
|
|
|
96
99
|
export function buildDriveResponse(result, input) {
|
|
97
100
|
const { status: _status, ...resultData } = result;
|
|
98
101
|
const summarySection = buildSection("Summary", result.summary);
|
|
99
|
-
const hintsSection = buildSection(
|
|
102
|
+
const hintsSection = buildSection(HINTS_SECTION_TITLE, formatHints(result.meta?.hints ?? [], { maxItems: 12, maxItemChars: 400 }));
|
|
100
103
|
const diffSection = buildSection(DIFF_OVERVIEW_SECTION_TITLE, result.diff);
|
|
101
|
-
const
|
|
104
|
+
const infoSection = buildSection(INFO_SECTION_TITLE, [
|
|
102
105
|
...formatMaybe("Identity", input.name, 120),
|
|
103
106
|
...formatMaybe(BRANCH_LABEL, result.currentBranch, 200),
|
|
104
107
|
...formatMaybe("Base Branch", result.baseBranch, 200),
|
|
105
108
|
...formatMaybe("Commit", result.commit, 100),
|
|
106
|
-
];
|
|
109
|
+
]);
|
|
107
110
|
const summaryLine = result.commit
|
|
108
111
|
? `Updated branch '${result.currentBranch}' [${result.commit}].`
|
|
109
112
|
: `Updated branch '${result.currentBranch}'.`;
|
|
110
|
-
const text = assembleResponse(`${RESPONSE_MARKERS.round} Driven (Round ${result.round})`, summaryLine, [summarySection,
|
|
113
|
+
const text = assembleResponse(`${RESPONSE_MARKERS.round} Driven (Round ${result.round})`, summaryLine, [summarySection, infoSection, diffSection, hintsSection].filter((section) => section !== null));
|
|
111
114
|
return {
|
|
112
115
|
content: [{ type: "text", text }],
|
|
113
116
|
structuredContent: buildSuccessStructuredContent(resultData),
|
|
@@ -149,7 +152,8 @@ export function buildCloseDoneResponse(result, input) {
|
|
|
149
152
|
`Scores: placement=${input.placement} exactness=${input.exactness} containment=${input.containment} idiomatic=${input.idiomatic} cohesive=${input.cohesive} [total: ${input.placement + input.exactness + input.containment + input.idiomatic + input.cohesive}/50]`,
|
|
150
153
|
...formatMaybe("Oath", input.oath, 220),
|
|
151
154
|
];
|
|
152
|
-
const
|
|
155
|
+
const draftPathMessage = result.draftPath ? ` Draft kept at '${result.draftPath}'.` : "";
|
|
156
|
+
const text = assembleResponse(`${RESPONSE_MARKERS.claim} Keiyaku Fulfilled (Claim)`, `Merged '${result.deletedBranch}' into '${result.mergedInto}'${result.commit ? ` [${result.commit}]` : ''}. Deleted feature branch.${draftPathMessage}`, [diffSection].filter((section) => section !== null), infoLines);
|
|
153
157
|
return {
|
|
154
158
|
content: [{ type: "text", text }],
|
|
155
159
|
structuredContent: buildSuccessStructuredContent(resultData),
|
|
@@ -165,7 +169,8 @@ export function buildCloseDropResponse(result, _input) {
|
|
|
165
169
|
...formatMaybe(BRANCH_LABEL, result.currentBranch, 200),
|
|
166
170
|
...formatMaybe("Base Branch", result.baseBranch, 200),
|
|
167
171
|
];
|
|
168
|
-
const
|
|
172
|
+
const draftPathMessage = result.draftPath ? ` Draft kept at '${result.draftPath}'.` : "";
|
|
173
|
+
const text = assembleResponse(`${RESPONSE_MARKERS.forfeit} Keiyaku Forfeited (Forfeit)`, `Deleted '${result.deletedBranch}'. Switched back to '${result.baseBranch}'.${result.archiveTag ? ` Archived at '${result.archiveTag}'.` : ""}${droppedChanges.length > 0 ? ` Dropped ${droppedChanges.length} local change(s).` : ''}${draftPathMessage}`, [warningSection].filter((section) => section !== null), infoLines);
|
|
169
174
|
return {
|
|
170
175
|
content: [{ type: "text", text }],
|
|
171
176
|
structuredContent: buildSuccessStructuredContent(resultData),
|
|
@@ -202,66 +207,46 @@ export function buildHelpResponse(input) {
|
|
|
202
207
|
};
|
|
203
208
|
}
|
|
204
209
|
export function buildStatusResponse(result, labels) {
|
|
205
|
-
const { active: _active, branch: _branch, protocolFiles: _protocolFiles, keiyakuPreview: _keiyakuPreview, lastRoundPreview: _lastRoundPreview, ...resultData } = result;
|
|
206
210
|
const blockedItems = {};
|
|
207
|
-
if (result.
|
|
208
|
-
blockedItems[labels.start] = result.
|
|
211
|
+
if (result.blockedByOperation.start.length > 0) {
|
|
212
|
+
blockedItems[labels.start] = result.blockedByOperation.start;
|
|
209
213
|
}
|
|
210
|
-
if (result.
|
|
211
|
-
blockedItems[labels.drive] = result.
|
|
214
|
+
if (result.blockedByOperation.drive.length > 0) {
|
|
215
|
+
blockedItems[labels.drive] = result.blockedByOperation.drive;
|
|
212
216
|
}
|
|
213
|
-
if (result.
|
|
214
|
-
blockedItems[labels.
|
|
217
|
+
if (result.blockedByOperation.present.length > 0) {
|
|
218
|
+
blockedItems[labels.close] = result.blockedByOperation.present;
|
|
215
219
|
}
|
|
216
|
-
const operationReadiness = {
|
|
217
|
-
[labels.start]: result.operationReadiness.start,
|
|
218
|
-
[labels.drive]: result.operationReadiness.drive,
|
|
219
|
-
[labels.present]: result.operationReadiness.present,
|
|
220
|
-
};
|
|
221
220
|
const summaryLine = result.branch
|
|
222
|
-
?
|
|
223
|
-
:
|
|
224
|
-
const draftFileInfo = result.protocolFiles.draft.present
|
|
225
|
-
? [
|
|
226
|
-
`present`,
|
|
227
|
-
`tracked=${result.protocolFiles.draft.tracked ? "yes" : "no"}`,
|
|
228
|
-
`dirty=${result.protocolFiles.draft.dirty ? "yes" : "no"}`,
|
|
229
|
-
].join(", ")
|
|
230
|
-
: "missing";
|
|
221
|
+
? `${STATUS_ACTIVE_SUMMARY_PREFIX} — ${result.branch} (round ${result.round})`
|
|
222
|
+
: STATUS_INACTIVE_SUMMARY;
|
|
231
223
|
const stateLines = [
|
|
232
|
-
...formatMaybe("
|
|
233
|
-
...formatMaybe("Base Branch", result.baseBranch, 200),
|
|
234
|
-
`Round: ${result.round}`,
|
|
224
|
+
...formatMaybe("Keiyaku Preview", result.keiyakuPreview, 400),
|
|
235
225
|
...formatMaybe("Last Round Preview", result.lastRoundPreview, 400),
|
|
236
|
-
|
|
237
|
-
const protocolLines = [
|
|
238
|
-
formatStatusFileSignal(result.protocolFiles.keiyaku),
|
|
239
|
-
formatStatusFileSignal(result.protocolFiles.trace),
|
|
240
|
-
`${result.protocolFiles.draft.path}: ${draftFileInfo}`,
|
|
226
|
+
...formatMaybe("Draft Preview", result.draftPreview, 400),
|
|
241
227
|
];
|
|
242
228
|
const readinessLines = [
|
|
243
|
-
...formatBlockedOperation(labels.start, result.
|
|
244
|
-
...formatBlockedOperation(labels.drive, result.
|
|
245
|
-
...formatBlockedOperation(labels.
|
|
229
|
+
...formatBlockedOperation(labels.start, result.blockedByOperation.start),
|
|
230
|
+
...formatBlockedOperation(labels.drive, result.blockedByOperation.drive),
|
|
231
|
+
...formatBlockedOperation(labels.close, result.blockedByOperation.present),
|
|
246
232
|
];
|
|
247
|
-
if (readinessLines.length === 0) {
|
|
248
|
-
readinessLines.push("No blocked operations detected from repository/protocol state.");
|
|
249
|
-
}
|
|
250
233
|
const hintLines = formatHints(result.hints, { maxItems: 12, maxItemChars: 400 });
|
|
251
234
|
const text = assembleResponse(`${RESPONSE_MARKERS.round} Status`, summaryLine, [
|
|
252
|
-
buildSection(
|
|
253
|
-
buildSection(
|
|
254
|
-
buildSection(
|
|
255
|
-
buildSection("Info", protocolLines),
|
|
235
|
+
buildSection(STATUS_KEIYAKU_SECTION_TITLE, stateLines),
|
|
236
|
+
buildSection(STATUS_BLOCKED_SECTION_TITLE, readinessLines),
|
|
237
|
+
buildSection(HINTS_SECTION_TITLE, hintLines),
|
|
256
238
|
].filter((section) => section !== null));
|
|
257
239
|
return {
|
|
258
240
|
content: [{ type: "text", text }],
|
|
259
241
|
structuredContent: buildSuccessStructuredContent({
|
|
260
|
-
|
|
242
|
+
active: result.active,
|
|
243
|
+
round: result.round,
|
|
244
|
+
baseBranch: result.baseBranch,
|
|
245
|
+
hints: result.hints,
|
|
261
246
|
currentKeiyakuBranch: result.branch ?? null,
|
|
262
247
|
keiyakuPreview: result.keiyakuPreview ?? null,
|
|
263
248
|
lastRoundPreview: result.lastRoundPreview ?? null,
|
|
264
|
-
draftPreview: result.
|
|
249
|
+
draftPreview: result.draftPreview ?? null,
|
|
265
250
|
blockedItems,
|
|
266
251
|
}),
|
|
267
252
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SECTION_ICONS } from "../common/response-style.js";
|
|
2
2
|
const HEADER_MAX_WIDTH = 40;
|
|
3
3
|
const HEADER_DASH = "┈";
|
|
4
4
|
function supportsHeaderColor() {
|
|
@@ -50,21 +50,10 @@ export function assembleResponse(status, summary, sections, infoLines = []) {
|
|
|
50
50
|
if (summaryLine) {
|
|
51
51
|
statusLines.push(`↳ ${summaryLine}`);
|
|
52
52
|
}
|
|
53
|
-
const
|
|
54
|
-
const diffSections = [];
|
|
55
|
-
for (const section of sections) {
|
|
56
|
-
if (section.title.trim().toLowerCase() === DIFF_OVERVIEW_SECTION_TITLE.toLowerCase()) {
|
|
57
|
-
diffSections.push(section);
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
normalSections.push(section);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
const fullSections = [...normalSections];
|
|
53
|
+
const fullSections = [...sections];
|
|
64
54
|
if (infoLines.length > 0) {
|
|
65
55
|
fullSections.push({ title: "Info", body: infoLines.join("\n") });
|
|
66
56
|
}
|
|
67
|
-
fullSections.push(...diffSections);
|
|
68
57
|
const renderedSections = fullSections
|
|
69
58
|
.map((section) => {
|
|
70
59
|
const body = section.body.trim();
|
package/build/workflow/start.js
CHANGED
|
@@ -7,7 +7,7 @@ import * as git from "../utils/git.js";
|
|
|
7
7
|
import { parseDiffCoordinates } from "../utils/trace.js";
|
|
8
8
|
import { buildStartPrompt } from "./prompts.js";
|
|
9
9
|
import { renderToolRoundSummary } from "./round-summary.js";
|
|
10
|
-
import { assertCleanWorkingTree } from "./
|
|
10
|
+
import { assertCleanWorkingTree } from "./keiyaku.js";
|
|
11
11
|
import { runAndRecordRound } from "./round.js";
|
|
12
12
|
import { renderKeiyaku } from "./keiyaku-document-builder.js";
|
|
13
13
|
import { parseAndValidateKeiyakuDraft, } from "./keiyaku-draft.js";
|