@astrosheep/keiyaku 0.1.47 → 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/agents/round-runner.js +1 -1
- package/build/agents/selector.js +1 -1
- package/build/common/constants.js +6 -1
- package/build/common/errors.js +1 -1
- package/build/common/response-style.js +4 -1
- package/build/config/term-presets/constants.js +24 -0
- package/build/config/term-presets/default-preset.js +119 -0
- package/build/config/term-presets/index.js +5 -0
- package/build/config/term-presets/mischief-preset.js +105 -0
- package/build/config/term-presets/pocket-preset.js +105 -0
- package/build/config/term-presets/resolver.js +52 -0
- package/build/config/term-presets/types.js +1 -0
- package/build/handlers/ask.js +10 -120
- package/build/handlers/close.js +2 -9
- package/build/handlers/drive.js +1 -15
- package/build/handlers/shared.js +1 -2
- package/build/handlers/start.js +0 -17
- package/build/handlers/status.js +2 -6
- package/build/index.js +2 -2
- package/build/types/git-diff.js +1 -0
- package/build/utils/ask-history.js +75 -0
- package/build/utils/draft.js +22 -0
- package/build/utils/git-diff/constants.js +9 -0
- package/build/utils/git-diff/filter.js +70 -0
- package/build/utils/git-diff/incremental.js +111 -0
- package/build/utils/git-diff/index.js +3 -0
- package/build/utils/git-diff/parsers.js +160 -0
- package/build/utils/git-diff/preview.js +157 -0
- package/build/utils/git-diff/stat.js +27 -0
- package/build/utils/git-diff/types.js +1 -0
- package/build/utils/git-ops.js +9 -0
- package/build/utils/git.js +1 -1
- package/build/utils/keiyaku-document/index.js +13 -0
- package/build/utils/keiyaku-document/lex.js +178 -0
- package/build/utils/keiyaku-document/parser.js +242 -0
- package/build/utils/keiyaku-document/render.js +68 -0
- package/build/utils/keiyaku-document/sections.js +105 -0
- package/build/utils/keiyaku-document/types.js +6 -0
- package/build/utils/keiyaku-document.test.js +12 -1
- package/build/utils/trace.js +6 -7
- package/build/workflow/ask-execution.js +81 -0
- package/build/workflow/drive.js +10 -5
- package/build/workflow/iterate-plan.js +1 -1
- package/build/workflow/keiyaku-draft.js +1 -1
- package/build/workflow/{contract.js → keiyaku.js} +2 -2
- package/build/workflow/markdown-normalization.js +3 -2
- package/build/workflow/present.js +68 -13
- package/build/workflow/response-builders.js +75 -44
- package/build/workflow/response-meta.js +15 -0
- package/build/workflow/response-renderer.js +2 -13
- package/build/workflow/round-summary.js +1 -1
- package/build/workflow/start.js +8 -3
- package/build/workflow/status.js +129 -62
- package/package.json +1 -1
- package/build/config/term-presets.js +0 -398
- package/build/utils/git-diff.js +0 -519
- package/build/utils/keiyaku-document.js +0 -539
|
@@ -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();
|
|
@@ -31,6 +37,15 @@ function formatList(label, items, opts) {
|
|
|
31
37
|
const tail = items.length > maxItems ? [`...(+${items.length - maxItems} more)`] : [];
|
|
32
38
|
return [...head, ...shown, ...tail];
|
|
33
39
|
}
|
|
40
|
+
function formatHints(hints, opts) {
|
|
41
|
+
const maxItems = opts?.maxItems ?? FORMAT_LIST_MAX_ITEMS;
|
|
42
|
+
const maxItemChars = opts?.maxItemChars ?? FORMAT_LIST_MAX_ITEM_CHARS;
|
|
43
|
+
if (hints.length === 0)
|
|
44
|
+
return [];
|
|
45
|
+
const shown = hints.slice(0, maxItems).map((hint) => `• ${truncateForDisplay(hint, maxItemChars)}`);
|
|
46
|
+
const tail = hints.length > maxItems ? [`...(+${hints.length - maxItems} more)`] : [];
|
|
47
|
+
return [...shown, ...tail];
|
|
48
|
+
}
|
|
34
49
|
function buildSection(title, content) {
|
|
35
50
|
const body = Array.isArray(content) ? content.join("\n") : content;
|
|
36
51
|
const normalized = body.trim();
|
|
@@ -38,10 +53,12 @@ function buildSection(title, content) {
|
|
|
38
53
|
return null;
|
|
39
54
|
return { title, body };
|
|
40
55
|
}
|
|
41
|
-
function
|
|
56
|
+
function formatBlockedOperation(label, blockers) {
|
|
57
|
+
if (blockers.length === 0)
|
|
58
|
+
return [];
|
|
42
59
|
return [
|
|
43
|
-
`${label}
|
|
44
|
-
...formatList(`${label} Blockers`,
|
|
60
|
+
`${label}:`,
|
|
61
|
+
...formatList(`${label} Blockers`, blockers, { maxItems: 10, maxItemChars: 300 }),
|
|
45
62
|
];
|
|
46
63
|
}
|
|
47
64
|
function buildSuccessStructuredContent(data) {
|
|
@@ -57,8 +74,9 @@ export function buildKeiyakuSuccessResponse(result, input) {
|
|
|
57
74
|
const responseData = normalizedRules ? { ...resultData, rules: normalizedRules } : { ...resultData };
|
|
58
75
|
const summarySection = buildSection("Summary", result.summary);
|
|
59
76
|
const rulesSection = buildSection("Rules", truncateForDisplay(normalizedRules, RULES_SECTION_MAX_CHARS));
|
|
77
|
+
const hintsSection = buildSection(HINTS_SECTION_TITLE, formatHints(result.meta?.hints ?? [], { maxItems: 12, maxItemChars: 400 }));
|
|
60
78
|
const diffSection = buildSection(DIFF_OVERVIEW_SECTION_TITLE, result.diff);
|
|
61
|
-
const
|
|
79
|
+
const infoSection = buildSection(INFO_SECTION_TITLE, [
|
|
62
80
|
...formatMaybe("Identity", input.name, 120),
|
|
63
81
|
...formatMaybe("Consumed from_file and deleted", result.consumedFromFile, 300),
|
|
64
82
|
...formatList("Existing local keiyaku branches (non-blocking)", result.existingBranches ?? [], {
|
|
@@ -68,12 +86,11 @@ export function buildKeiyakuSuccessResponse(result, input) {
|
|
|
68
86
|
...formatMaybe(BRANCH_LABEL, result.currentBranch, 200),
|
|
69
87
|
...formatMaybe("Base Branch", result.baseBranch, 200),
|
|
70
88
|
...formatMaybe("Commit", result.commit, 100),
|
|
71
|
-
|
|
72
|
-
];
|
|
89
|
+
]);
|
|
73
90
|
const summaryLine = result.commit
|
|
74
91
|
? `Created branch '${result.currentBranch}' (base: '${result.baseBranch}') [${result.commit}].`
|
|
75
92
|
: `Created branch '${result.currentBranch}' (base: '${result.baseBranch}').`;
|
|
76
|
-
const text = assembleResponse(`${RESPONSE_MARKERS.round} Started (Round ${result.round})`, summaryLine, [summarySection, rulesSection, diffSection].filter((section) => section !== null)
|
|
93
|
+
const text = assembleResponse(`${RESPONSE_MARKERS.round} Started (Round ${result.round})`, summaryLine, [summarySection, rulesSection, infoSection, diffSection, hintsSection].filter((section) => section !== null));
|
|
77
94
|
return {
|
|
78
95
|
content: [{ type: "text", text }],
|
|
79
96
|
structuredContent: buildSuccessStructuredContent(responseData),
|
|
@@ -82,18 +99,18 @@ export function buildKeiyakuSuccessResponse(result, input) {
|
|
|
82
99
|
export function buildDriveResponse(result, input) {
|
|
83
100
|
const { status: _status, ...resultData } = result;
|
|
84
101
|
const summarySection = buildSection("Summary", result.summary);
|
|
102
|
+
const hintsSection = buildSection(HINTS_SECTION_TITLE, formatHints(result.meta?.hints ?? [], { maxItems: 12, maxItemChars: 400 }));
|
|
85
103
|
const diffSection = buildSection(DIFF_OVERVIEW_SECTION_TITLE, result.diff);
|
|
86
|
-
const
|
|
104
|
+
const infoSection = buildSection(INFO_SECTION_TITLE, [
|
|
87
105
|
...formatMaybe("Identity", input.name, 120),
|
|
88
106
|
...formatMaybe(BRANCH_LABEL, result.currentBranch, 200),
|
|
89
107
|
...formatMaybe("Base Branch", result.baseBranch, 200),
|
|
90
108
|
...formatMaybe("Commit", result.commit, 100),
|
|
91
|
-
|
|
92
|
-
];
|
|
109
|
+
]);
|
|
93
110
|
const summaryLine = result.commit
|
|
94
111
|
? `Updated branch '${result.currentBranch}' [${result.commit}].`
|
|
95
112
|
: `Updated branch '${result.currentBranch}'.`;
|
|
96
|
-
const text = assembleResponse(`${RESPONSE_MARKERS.round} Driven (Round ${result.round})`, summaryLine, [summarySection, diffSection].filter((section) => section !== null)
|
|
113
|
+
const text = assembleResponse(`${RESPONSE_MARKERS.round} Driven (Round ${result.round})`, summaryLine, [summarySection, infoSection, diffSection, hintsSection].filter((section) => section !== null));
|
|
97
114
|
return {
|
|
98
115
|
content: [{ type: "text", text }],
|
|
99
116
|
structuredContent: buildSuccessStructuredContent(resultData),
|
|
@@ -135,13 +152,14 @@ export function buildCloseDoneResponse(result, input) {
|
|
|
135
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]`,
|
|
136
153
|
...formatMaybe("Oath", input.oath, 220),
|
|
137
154
|
];
|
|
138
|
-
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);
|
|
139
157
|
return {
|
|
140
158
|
content: [{ type: "text", text }],
|
|
141
159
|
structuredContent: buildSuccessStructuredContent(resultData),
|
|
142
160
|
};
|
|
143
161
|
}
|
|
144
|
-
export function buildCloseDropResponse(result,
|
|
162
|
+
export function buildCloseDropResponse(result, _input) {
|
|
145
163
|
const { status: _status, ...resultData } = result;
|
|
146
164
|
const droppedChanges = result.droppedChanges ?? [];
|
|
147
165
|
const warningSection = buildSection("Warning", formatList("Dropped Local Changes", droppedChanges, { maxItems: 40, maxItemChars: 400 }));
|
|
@@ -151,7 +169,8 @@ export function buildCloseDropResponse(result, input) {
|
|
|
151
169
|
...formatMaybe(BRANCH_LABEL, result.currentBranch, 200),
|
|
152
170
|
...formatMaybe("Base Branch", result.baseBranch, 200),
|
|
153
171
|
];
|
|
154
|
-
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);
|
|
155
174
|
return {
|
|
156
175
|
content: [{ type: "text", text }],
|
|
157
176
|
structuredContent: buildSuccessStructuredContent(resultData),
|
|
@@ -167,7 +186,7 @@ export function buildToolErrorResponse(input) {
|
|
|
167
186
|
error: {
|
|
168
187
|
code: input.errorCode,
|
|
169
188
|
message: input.message,
|
|
170
|
-
suggestion: input.
|
|
189
|
+
suggestion: input.hints[0] ?? "",
|
|
171
190
|
},
|
|
172
191
|
},
|
|
173
192
|
};
|
|
@@ -178,9 +197,8 @@ export function buildToolErrorResponse(input) {
|
|
|
178
197
|
}
|
|
179
198
|
function buildToolErrorText(input) {
|
|
180
199
|
const errorSection = buildSection("Error", input.message);
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
return assembleResponse(colorizeErrorStatus(`${RESPONSE_MARKERS.failure} Failed [${input.errorCode}]`), "Request could not be completed.", [errorSection, hintSection, contextSection].filter((section) => section !== null));
|
|
200
|
+
const hintsSection = buildSection("Hints", formatHints(input.hints, { maxItems: 12, maxItemChars: 400 }));
|
|
201
|
+
return assembleResponse(colorizeErrorStatus(`${RESPONSE_MARKERS.failure} Failed [${input.errorCode}]`), "Request could not be completed.", [errorSection, hintsSection].filter((section) => section !== null));
|
|
184
202
|
}
|
|
185
203
|
export function buildHelpResponse(input) {
|
|
186
204
|
return {
|
|
@@ -189,34 +207,47 @@ export function buildHelpResponse(input) {
|
|
|
189
207
|
};
|
|
190
208
|
}
|
|
191
209
|
export function buildStatusResponse(result, labels) {
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
].
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
`${result.protocolFiles.trace.path}: ${result.protocolFiles.trace.present ? "present" : "missing"}`,
|
|
210
|
-
`${result.protocolFiles.draft.path}: ${draftFileInfo}`,
|
|
211
|
-
...formatOperationReadiness(labels.start, result.operationReadiness.start),
|
|
212
|
-
...formatOperationReadiness(labels.drive, result.operationReadiness.drive),
|
|
213
|
-
...formatOperationReadiness(labels.present, result.operationReadiness.present),
|
|
214
|
-
...formatMaybe("Last Round Outcome", result.lastRoundSummary, 400),
|
|
215
|
-
...formatList("Hints", result.hints, { maxItems: 12, maxItemChars: 400 }),
|
|
210
|
+
const blockedItems = {};
|
|
211
|
+
if (result.blockedByOperation.start.length > 0) {
|
|
212
|
+
blockedItems[labels.start] = result.blockedByOperation.start;
|
|
213
|
+
}
|
|
214
|
+
if (result.blockedByOperation.drive.length > 0) {
|
|
215
|
+
blockedItems[labels.drive] = result.blockedByOperation.drive;
|
|
216
|
+
}
|
|
217
|
+
if (result.blockedByOperation.present.length > 0) {
|
|
218
|
+
blockedItems[labels.close] = result.blockedByOperation.present;
|
|
219
|
+
}
|
|
220
|
+
const summaryLine = result.branch
|
|
221
|
+
? `${STATUS_ACTIVE_SUMMARY_PREFIX} — ${result.branch} (round ${result.round})`
|
|
222
|
+
: STATUS_INACTIVE_SUMMARY;
|
|
223
|
+
const stateLines = [
|
|
224
|
+
...formatMaybe("Keiyaku Preview", result.keiyakuPreview, 400),
|
|
225
|
+
...formatMaybe("Last Round Preview", result.lastRoundPreview, 400),
|
|
226
|
+
...formatMaybe("Draft Preview", result.draftPreview, 400),
|
|
216
227
|
];
|
|
217
|
-
const
|
|
228
|
+
const readinessLines = [
|
|
229
|
+
...formatBlockedOperation(labels.start, result.blockedByOperation.start),
|
|
230
|
+
...formatBlockedOperation(labels.drive, result.blockedByOperation.drive),
|
|
231
|
+
...formatBlockedOperation(labels.close, result.blockedByOperation.present),
|
|
232
|
+
];
|
|
233
|
+
const hintLines = formatHints(result.hints, { maxItems: 12, maxItemChars: 400 });
|
|
234
|
+
const text = assembleResponse(`${RESPONSE_MARKERS.round} Status`, summaryLine, [
|
|
235
|
+
buildSection(STATUS_KEIYAKU_SECTION_TITLE, stateLines),
|
|
236
|
+
buildSection(STATUS_BLOCKED_SECTION_TITLE, readinessLines),
|
|
237
|
+
buildSection(HINTS_SECTION_TITLE, hintLines),
|
|
238
|
+
].filter((section) => section !== null));
|
|
218
239
|
return {
|
|
219
240
|
content: [{ type: "text", text }],
|
|
220
|
-
structuredContent: buildSuccessStructuredContent(
|
|
241
|
+
structuredContent: buildSuccessStructuredContent({
|
|
242
|
+
active: result.active,
|
|
243
|
+
round: result.round,
|
|
244
|
+
baseBranch: result.baseBranch,
|
|
245
|
+
hints: result.hints,
|
|
246
|
+
currentKeiyakuBranch: result.branch ?? null,
|
|
247
|
+
keiyakuPreview: result.keiyakuPreview ?? null,
|
|
248
|
+
lastRoundPreview: result.lastRoundPreview ?? null,
|
|
249
|
+
draftPreview: result.draftPreview ?? null,
|
|
250
|
+
blockedItems,
|
|
251
|
+
}),
|
|
221
252
|
};
|
|
222
253
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { resolveTermPreset } from "../config/term-presets/index.js";
|
|
2
|
+
import { renderPreset } from "../utils/text-utils.js";
|
|
3
|
+
function renderPresetHints() {
|
|
4
|
+
const preset = resolveTermPreset();
|
|
5
|
+
return renderPreset(preset.hints, {
|
|
6
|
+
start: preset.tools.start.name,
|
|
7
|
+
drive: preset.tools.drive.name,
|
|
8
|
+
ask: preset.tools.ask.name,
|
|
9
|
+
close: preset.tools.close.name,
|
|
10
|
+
identity: preset.identity,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export function resolveWorkflowHints(kind) {
|
|
14
|
+
return renderPresetHints()[kind];
|
|
15
|
+
}
|
|
@@ -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
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import * as fs from "fs/promises";
|
|
2
2
|
import * as path from "path";
|
|
3
|
-
import { KEIYAKU_BRANCH_PREFIX, KEIYAKU_DRAFT_FILE, KEIYAKU_FILE, TRACE_FILE } from "../common/constants.js";
|
|
3
|
+
import { INCREMENTAL_DIFF_MAX_CHARS, KEIYAKU_BRANCH_PREFIX, KEIYAKU_DRAFT_FILE, KEIYAKU_FILE, TRACE_FILE, } from "../common/constants.js";
|
|
4
4
|
import { logInfo, logWarn } from "../utils/logger.js";
|
|
5
5
|
import { FlowError, isFlowError, requireText, wrapFlowError } from "../common/errors.js";
|
|
6
6
|
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";
|
|
14
14
|
import { resolveIncrementalDiffMode } from "../config/incremental-diff-mode.js";
|
|
15
15
|
import { readBaseRules } from "./base-rules.js";
|
|
16
|
+
import { resolveWorkflowHints } from "./response-meta.js";
|
|
16
17
|
const INTERNAL_START_DIRTY_ALLOWLIST = [];
|
|
17
18
|
function normalizeOptionalText(value) {
|
|
18
19
|
if (value === undefined)
|
|
@@ -289,7 +290,10 @@ export async function startKeiyaku(input) {
|
|
|
289
290
|
coordinates = undefined;
|
|
290
291
|
}
|
|
291
292
|
}
|
|
292
|
-
const diff = await git.getIncrementalDiff(cwd, incrementalDiffMode,
|
|
293
|
+
const diff = await git.getIncrementalDiff(cwd, incrementalDiffMode, {
|
|
294
|
+
coordinates,
|
|
295
|
+
maxChars: INCREMENTAL_DIFF_MAX_CHARS,
|
|
296
|
+
});
|
|
293
297
|
if (resolved.fromFile && resolved.fromFilePath) {
|
|
294
298
|
await fs.unlink(resolved.fromFilePath);
|
|
295
299
|
consumedFromFile = resolved.fromFile;
|
|
@@ -310,6 +314,7 @@ export async function startKeiyaku(input) {
|
|
|
310
314
|
currentBranch: keiyakuBranch,
|
|
311
315
|
baseBranch,
|
|
312
316
|
commit,
|
|
317
|
+
meta: { hints: resolveWorkflowHints("start") },
|
|
313
318
|
};
|
|
314
319
|
}
|
|
315
320
|
catch (err) {
|
package/build/workflow/status.js
CHANGED
|
@@ -2,7 +2,7 @@ import * as fs from "node:fs/promises";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { KEIYAKU_BRANCH_PREFIX, KEIYAKU_DRAFT_FILE, KEIYAKU_FILE, TRACE_FILE, } from "../common/constants.js";
|
|
4
4
|
import { FlowError } from "../common/errors.js";
|
|
5
|
-
import { parseToAST, renderNodeContent } from "../utils/keiyaku-document.js";
|
|
5
|
+
import { parseToAST, renderNodeContent } from "../utils/keiyaku-document/index.js";
|
|
6
6
|
import * as git from "../utils/git.js";
|
|
7
7
|
import { computeTraceState } from "../utils/trace.js";
|
|
8
8
|
import { parseRoundSummary } from "./round-summary.js";
|
|
@@ -16,45 +16,41 @@ function parseRoundSectionNumber(title) {
|
|
|
16
16
|
function normalizeHeading(value) {
|
|
17
17
|
return value.trim().toLowerCase();
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
function renderSummarySection(summaryNodes) {
|
|
25
|
-
if (summaryNodes.length === 0)
|
|
19
|
+
const PREVIEW_SECTION_TITLES = ["goal", "context"];
|
|
20
|
+
const PREVIEW_SECTION_TITLE_SET = new Set(PREVIEW_SECTION_TITLES);
|
|
21
|
+
function renderNodesForPreview(nodes) {
|
|
22
|
+
if (nodes.length === 0)
|
|
26
23
|
return "";
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (body.length > 0) {
|
|
32
|
-
return body;
|
|
24
|
+
return nodes
|
|
25
|
+
.map((node) => {
|
|
26
|
+
if (node.type === "blockquote") {
|
|
27
|
+
return node.value.trim();
|
|
33
28
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
.
|
|
37
|
-
.filter((node) => node.length > 0)
|
|
29
|
+
return renderNodeContent(node).trim();
|
|
30
|
+
})
|
|
31
|
+
.filter((text) => text.length > 0)
|
|
38
32
|
.join("\n")
|
|
39
33
|
.trim();
|
|
40
|
-
return unwrapMarkdownFence(rendered);
|
|
41
34
|
}
|
|
42
|
-
function
|
|
43
|
-
let
|
|
44
|
-
const
|
|
45
|
-
for (const child of
|
|
35
|
+
function extractHeadingBody(section, heading) {
|
|
36
|
+
let capture = false;
|
|
37
|
+
const captured = [];
|
|
38
|
+
for (const child of section.children) {
|
|
46
39
|
if (child.type === "heading" && child.level === 3) {
|
|
47
|
-
if (
|
|
40
|
+
if (capture) {
|
|
48
41
|
break;
|
|
49
42
|
}
|
|
50
|
-
|
|
43
|
+
capture = normalizeHeading(child.text) === heading;
|
|
51
44
|
continue;
|
|
52
45
|
}
|
|
53
|
-
if (!
|
|
46
|
+
if (!capture)
|
|
54
47
|
continue;
|
|
55
|
-
|
|
48
|
+
captured.push(child);
|
|
56
49
|
}
|
|
57
|
-
|
|
50
|
+
return captured;
|
|
51
|
+
}
|
|
52
|
+
function extractRoundRawSummary(roundSection) {
|
|
53
|
+
const summary = renderNodesForPreview(extractHeadingBody(roundSection, "summary"));
|
|
58
54
|
return summary.length > 0 ? summary : undefined;
|
|
59
55
|
}
|
|
60
56
|
function extractLastRoundSummary(traceContent) {
|
|
@@ -86,9 +82,57 @@ function extractLastRoundSummary(traceContent) {
|
|
|
86
82
|
}
|
|
87
83
|
return rawSummary;
|
|
88
84
|
}
|
|
89
|
-
|
|
85
|
+
function extractDocumentPreviewBySections(content, fallbackToDocument) {
|
|
86
|
+
const ast = parseToAST(content, { allowSections: true });
|
|
87
|
+
const sectionBodies = new Map();
|
|
88
|
+
for (const node of ast.children) {
|
|
89
|
+
if (node.type !== "section" || node.level !== 2)
|
|
90
|
+
continue;
|
|
91
|
+
const normalizedTitle = normalizeHeading(node.title);
|
|
92
|
+
if (!PREVIEW_SECTION_TITLE_SET.has(normalizedTitle))
|
|
93
|
+
continue;
|
|
94
|
+
if (sectionBodies.has(normalizedTitle))
|
|
95
|
+
continue;
|
|
96
|
+
const body = renderNodesForPreview(node.children);
|
|
97
|
+
if (body.length === 0)
|
|
98
|
+
continue;
|
|
99
|
+
sectionBodies.set(normalizedTitle, body);
|
|
100
|
+
}
|
|
101
|
+
const preferredBody = PREVIEW_SECTION_TITLES
|
|
102
|
+
.map((title) => sectionBodies.get(title))
|
|
103
|
+
.filter((value) => Boolean(value))
|
|
104
|
+
.join("\n")
|
|
105
|
+
.trim();
|
|
106
|
+
if (preferredBody.length > 0) {
|
|
107
|
+
return preferredBody;
|
|
108
|
+
}
|
|
109
|
+
if (!fallbackToDocument)
|
|
110
|
+
return undefined;
|
|
111
|
+
const fullDocument = renderNodeContent(ast).trim();
|
|
112
|
+
return fullDocument.length > 0 ? fullDocument : undefined;
|
|
113
|
+
}
|
|
114
|
+
function toStatusPreview(text) {
|
|
115
|
+
if (!text)
|
|
116
|
+
return undefined;
|
|
117
|
+
const MAX_LINES = 2;
|
|
118
|
+
const MAX_LINE_CHARS = 180;
|
|
119
|
+
const lines = text
|
|
120
|
+
.split(/\r?\n/)
|
|
121
|
+
.map((line) => line.trim())
|
|
122
|
+
.filter((line) => line.length > 0);
|
|
123
|
+
if (lines.length === 0)
|
|
124
|
+
return undefined;
|
|
125
|
+
const previewLines = lines
|
|
126
|
+
.slice(0, MAX_LINES)
|
|
127
|
+
.map((line) => (line.length > MAX_LINE_CHARS ? `${line.slice(0, MAX_LINE_CHARS - 1)}…` : line));
|
|
128
|
+
if (lines.length > MAX_LINES) {
|
|
129
|
+
previewLines.push("…");
|
|
130
|
+
}
|
|
131
|
+
return previewLines.join("\n");
|
|
132
|
+
}
|
|
133
|
+
async function readOptionalFile(cwd, fileName) {
|
|
90
134
|
try {
|
|
91
|
-
return await fs.readFile(path.join(cwd,
|
|
135
|
+
return await fs.readFile(path.join(cwd, fileName), "utf-8");
|
|
92
136
|
}
|
|
93
137
|
catch (error) {
|
|
94
138
|
if (error?.code === "ENOENT") {
|
|
@@ -124,19 +168,35 @@ const STATUS_BLOCKERS = {
|
|
|
124
168
|
missingBaseMetadata: "Active keiyaku branch is missing base metadata (branch.<keiyaku-branch>.keiyakuBase).",
|
|
125
169
|
missingProtocolFiles: "Active keiyaku branch is missing KEIYAKU.md or KEIYAKU_TRACE.md; restore protocol files before continuing.",
|
|
126
170
|
};
|
|
127
|
-
function
|
|
171
|
+
function toGitStatusLine(file) {
|
|
172
|
+
const indexCode = file.index.length > 0 ? file.index : " ";
|
|
173
|
+
const worktreeCode = file.working_dir.length > 0 ? file.working_dir : " ";
|
|
174
|
+
return `${indexCode}${worktreeCode} ${file.path}`;
|
|
175
|
+
}
|
|
176
|
+
function formatDirtyWorktreeBlocker(dirtyStatusLines) {
|
|
177
|
+
if (dirtyStatusLines.length === 0) {
|
|
178
|
+
return STATUS_BLOCKERS.dirtyWorktree;
|
|
179
|
+
}
|
|
180
|
+
const MAX_ITEMS = 5;
|
|
181
|
+
const shownLines = dirtyStatusLines.slice(0, MAX_ITEMS);
|
|
182
|
+
const overflowLine = dirtyStatusLines.length > MAX_ITEMS ? [`... (+${dirtyStatusLines.length - MAX_ITEMS} more)`] : [];
|
|
183
|
+
return [STATUS_BLOCKERS.dirtyWorktree, ...shownLines, ...overflowLine].join("\n");
|
|
184
|
+
}
|
|
185
|
+
function buildBlockedByOperation(input) {
|
|
128
186
|
const startBlockers = [];
|
|
129
187
|
const driveBlockers = [];
|
|
130
188
|
const presentBlockers = [];
|
|
131
189
|
const hasDirtyWorktree = input.dirtyPaths.length > 0;
|
|
132
190
|
const hasBlockingDirtyForRoundOps = input.dirtyPaths.some((dirtyPath) => dirtyPath !== KEIYAKU_DRAFT_FILE);
|
|
133
191
|
const missingProtocolFiles = !input.protocolFiles.keiyaku.present || !input.protocolFiles.trace.present;
|
|
192
|
+
const startDirtyBlocker = formatDirtyWorktreeBlocker(input.dirtyStatusLines);
|
|
193
|
+
const roundOpsDirtyBlocker = formatDirtyWorktreeBlocker(input.blockingDirtyStatusLines);
|
|
134
194
|
if (hasDirtyWorktree) {
|
|
135
|
-
startBlockers.push(
|
|
195
|
+
startBlockers.push(startDirtyBlocker);
|
|
136
196
|
}
|
|
137
197
|
if (hasBlockingDirtyForRoundOps) {
|
|
138
|
-
driveBlockers.push(
|
|
139
|
-
presentBlockers.push(
|
|
198
|
+
driveBlockers.push(roundOpsDirtyBlocker);
|
|
199
|
+
presentBlockers.push(roundOpsDirtyBlocker);
|
|
140
200
|
}
|
|
141
201
|
if (input.active) {
|
|
142
202
|
startBlockers.push(STATUS_BLOCKERS.startActiveBranch);
|
|
@@ -156,14 +216,10 @@ function buildOperationReadiness(input) {
|
|
|
156
216
|
startBlockers.push(STATUS_BLOCKERS.startProtocolOutsideActive);
|
|
157
217
|
}
|
|
158
218
|
}
|
|
159
|
-
const readiness = (blockers) => ({
|
|
160
|
-
available: blockers.length === 0,
|
|
161
|
-
blockers,
|
|
162
|
-
});
|
|
163
219
|
return {
|
|
164
|
-
start:
|
|
165
|
-
drive:
|
|
166
|
-
present:
|
|
220
|
+
start: startBlockers,
|
|
221
|
+
drive: driveBlockers,
|
|
222
|
+
present: presentBlockers,
|
|
167
223
|
};
|
|
168
224
|
}
|
|
169
225
|
function buildStatusHints(input) {
|
|
@@ -178,12 +234,7 @@ function buildStatusHints(input) {
|
|
|
178
234
|
if (!input.baseBranch) {
|
|
179
235
|
hints.push("Base branch metadata is missing; set branch.<keiyaku-branch>.keiyakuBase to avoid close-time failures.");
|
|
180
236
|
}
|
|
181
|
-
|
|
182
|
-
hints.push("Pending review detected; run the next round to address the latest review feedback.");
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
hints.push("No pending review detected; if criteria are done, proceed to close with CLAIM.");
|
|
186
|
-
}
|
|
237
|
+
hints.push("Run another round with drive, or close with CLAIM when criteria and rules checks are complete.");
|
|
187
238
|
}
|
|
188
239
|
else if (input.localKeiyakuBranches.length > 0) {
|
|
189
240
|
hints.push(`No active keiyaku branch; checkout one to resume: ${formatBranchCandidates(input.localKeiyakuBranches)}.`);
|
|
@@ -209,7 +260,12 @@ export async function readKeiyakuStatus(input) {
|
|
|
209
260
|
isPresentFile(cwd, TRACE_FILE),
|
|
210
261
|
isPresentFile(cwd, KEIYAKU_DRAFT_FILE),
|
|
211
262
|
]);
|
|
212
|
-
const
|
|
263
|
+
const dirtyFiles = await git.getDirtyFiles(cwd);
|
|
264
|
+
const dirtyPaths = Array.from(new Set(dirtyFiles.map((file) => file.path)));
|
|
265
|
+
const dirtyStatusLines = dirtyFiles.map((file) => toGitStatusLine(file));
|
|
266
|
+
const blockingDirtyStatusLines = dirtyFiles
|
|
267
|
+
.filter((file) => file.path !== KEIYAKU_DRAFT_FILE)
|
|
268
|
+
.map((file) => toGitStatusLine(file));
|
|
213
269
|
const draftDirty = dirtyPaths.includes(KEIYAKU_DRAFT_FILE);
|
|
214
270
|
const draftTracked = draftPresent ? await git.isPathTracked(cwd, KEIYAKU_DRAFT_FILE) : undefined;
|
|
215
271
|
const protocolFiles = {
|
|
@@ -223,34 +279,46 @@ export async function readKeiyakuStatus(input) {
|
|
|
223
279
|
};
|
|
224
280
|
const currentBranch = await git.getCurrentBranch(cwd);
|
|
225
281
|
if (!currentBranch.startsWith(KEIYAKU_BRANCH_PREFIX)) {
|
|
282
|
+
const [keiyakuContent, draftContent] = await Promise.all([
|
|
283
|
+
readOptionalFile(cwd, KEIYAKU_FILE),
|
|
284
|
+
readOptionalFile(cwd, KEIYAKU_DRAFT_FILE),
|
|
285
|
+
]);
|
|
226
286
|
const localKeiyakuBranches = await git.listLocalKeiyakuBranches(cwd);
|
|
227
|
-
const
|
|
287
|
+
const blockedByOperation = buildBlockedByOperation({
|
|
228
288
|
active: false,
|
|
229
289
|
dirtyPaths,
|
|
290
|
+
dirtyStatusLines,
|
|
291
|
+
blockingDirtyStatusLines,
|
|
230
292
|
protocolFiles,
|
|
231
293
|
});
|
|
232
294
|
return {
|
|
233
295
|
active: false,
|
|
234
296
|
round: 0,
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
297
|
+
keiyakuPreview: toStatusPreview(keiyakuContent ? extractDocumentPreviewBySections(keiyakuContent, false) : undefined),
|
|
298
|
+
draftPreview: toStatusPreview(draftContent ? extractDocumentPreviewBySections(draftContent, true) : undefined),
|
|
299
|
+
blockedByOperation,
|
|
238
300
|
hints: buildStatusHints({
|
|
239
301
|
active: false,
|
|
240
|
-
pendingReview: false,
|
|
241
302
|
protocolFiles,
|
|
242
303
|
localKeiyakuBranches,
|
|
243
304
|
}),
|
|
244
305
|
};
|
|
245
306
|
}
|
|
246
|
-
const traceContent = await
|
|
307
|
+
const [traceContent, keiyakuContent, draftContent] = await Promise.all([
|
|
308
|
+
readOptionalFile(cwd, TRACE_FILE),
|
|
309
|
+
readOptionalFile(cwd, KEIYAKU_FILE),
|
|
310
|
+
readOptionalFile(cwd, KEIYAKU_DRAFT_FILE),
|
|
311
|
+
]);
|
|
247
312
|
const traceState = traceContent ? computeTraceState(traceContent) : { maxRound: 0, pendingReviewRound: null };
|
|
248
313
|
const baseBranch = await git.getKeiyakuBase(cwd, currentBranch);
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
const
|
|
314
|
+
const keiyakuPreview = toStatusPreview(keiyakuContent ? extractDocumentPreviewBySections(keiyakuContent, false) : undefined);
|
|
315
|
+
const lastRoundPreview = toStatusPreview(traceContent ? extractLastRoundSummary(traceContent) : undefined);
|
|
316
|
+
const draftPreview = toStatusPreview(draftContent ? extractDocumentPreviewBySections(draftContent, true) : undefined);
|
|
317
|
+
const blockedByOperation = buildBlockedByOperation({
|
|
252
318
|
active: true,
|
|
253
319
|
dirtyPaths,
|
|
320
|
+
dirtyStatusLines,
|
|
321
|
+
blockingDirtyStatusLines,
|
|
254
322
|
baseBranch: baseBranch ?? undefined,
|
|
255
323
|
protocolFiles,
|
|
256
324
|
});
|
|
@@ -259,13 +327,12 @@ export async function readKeiyakuStatus(input) {
|
|
|
259
327
|
branch: currentBranch,
|
|
260
328
|
baseBranch: baseBranch ?? undefined,
|
|
261
329
|
round: traceState.maxRound,
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
330
|
+
keiyakuPreview,
|
|
331
|
+
lastRoundPreview,
|
|
332
|
+
draftPreview,
|
|
333
|
+
blockedByOperation,
|
|
266
334
|
hints: buildStatusHints({
|
|
267
335
|
active: true,
|
|
268
|
-
pendingReview,
|
|
269
336
|
baseBranch: baseBranch ?? undefined,
|
|
270
337
|
protocolFiles,
|
|
271
338
|
localKeiyakuBranches: [],
|