@astrosheep/keiyaku 0.1.76 → 0.1.78
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 +88 -96
- package/build/.tsbuildinfo +1 -1
- package/build/config/apply-argument-descriptions.js +1 -1
- package/build/config/base-rules.js +14 -7
- package/build/config/dotenv.js +17 -11
- package/build/config/keiyaku-home.js +9 -0
- package/build/config/settings.js +41 -24
- package/build/config/term-presets/resolver.js +0 -3
- package/build/errno.js +3 -0
- package/build/flow-error.js +2 -0
- package/build/generated/version.js +1 -1
- package/build/git/diff/constants.js +1 -0
- package/build/git/diff/filter.js +3 -18
- package/build/git/diff/parsers.js +149 -61
- package/build/git/diff/preview.js +16 -2
- package/build/git/diff/read.js +32 -20
- package/build/git/snapshot.js +5 -24
- package/build/git/worktree.js +5 -4
- package/build/mcp/responses.js +3 -2
- package/build/mcp/server.js +61 -69
- package/build/protocol/draft-artifacts.js +2 -1
- package/build/protocol/file-guards.js +2 -1
- package/build/protocol/markdown/lex.js +52 -14
- package/build/protocol/markdown/normalization.js +3 -2
- package/build/protocol/markdown/parser.js +2 -2
- package/build/protocol/response-history.js +44 -5
- package/build/protocol/status-previews.js +20 -8
- package/build/protocol/summon-draft.js +3 -2
- package/build/protocol/summon-input.js +1 -0
- package/build/protocol/trace.js +1 -1
- package/build/tools/amend/index.js +11 -21
- package/build/tools/ask/index.js +11 -18
- package/build/tools/ask/persist.js +60 -37
- package/build/tools/ask/run.js +17 -7
- package/build/tools/create-handler.js +31 -0
- package/build/tools/drive/index.js +11 -24
- package/build/tools/drive/run.js +9 -5
- package/build/tools/petition/claim-gates.js +38 -9
- package/build/tools/petition/claim.js +20 -2
- package/build/tools/petition/forfeit.js +4 -1
- package/build/tools/petition/index.js +43 -58
- package/build/tools/petition/run.js +12 -0
- package/build/tools/round/head-guard.js +10 -0
- package/build/tools/round/incremental-diff.js +6 -2
- package/build/tools/round/report.js +24 -2
- package/build/tools/round/run.js +6 -0
- package/build/tools/round/worktree.js +6 -2
- package/build/tools/status/index.js +11 -24
- package/build/tools/status/read.js +6 -4
- package/build/tools/summon/index.js +17 -27
- package/build/tools/summon/run.js +21 -18
- package/package.json +6 -6
- package/build/git/diff/stat.js +0 -9
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { appendDebugLog } from "../../telemetry/debug-log.js";
|
|
2
1
|
import { petitionKeiyaku } from "./run.js";
|
|
3
2
|
import { buildPetitionClaimResponse, buildPetitionForfeitResponse, } from "../../mcp/responses.js";
|
|
4
3
|
import { FlowError } from "../../flow-error.js";
|
|
5
4
|
import { petitionToolSchema } from "../schema.js";
|
|
6
|
-
import {
|
|
5
|
+
import { createToolHandler } from "../create-handler.js";
|
|
7
6
|
function requireClaimField(value, name) {
|
|
8
7
|
if (value === undefined) {
|
|
9
8
|
throw new FlowError("EMPTY_PARAM", `parameter '${name}' is required when intent=CLAIM`);
|
|
@@ -11,23 +10,22 @@ function requireClaimField(value, name) {
|
|
|
11
10
|
return value;
|
|
12
11
|
}
|
|
13
12
|
export function createPetitionHandler() {
|
|
14
|
-
return
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
return createToolHandler({
|
|
14
|
+
logLabel: "tool close",
|
|
15
|
+
doneLog: (result) => `tool close ${result.intent} success branch=${result.outcome.currentBranch} base=${result.outcome.baseBranch}`,
|
|
16
|
+
async run(args, extra) {
|
|
18
17
|
const input = petitionToolSchema.parse(args);
|
|
19
|
-
|
|
20
|
-
workingDir = input.cwd || process.cwd();
|
|
18
|
+
const cwd = input.cwd ?? args.cwd;
|
|
21
19
|
let closeInput;
|
|
22
|
-
let claimInput;
|
|
23
20
|
if (input.intent === "CLAIM") {
|
|
21
|
+
let claimInput;
|
|
24
22
|
if (input.dangerouslyBypassGates) {
|
|
25
23
|
claimInput = {
|
|
26
24
|
intent: "CLAIM",
|
|
27
25
|
title: requireClaimField(input.title, "title"),
|
|
28
26
|
plea: requireClaimField(input.plea, "plea"),
|
|
29
27
|
dangerouslyBypassGates: true,
|
|
30
|
-
cwd
|
|
28
|
+
cwd,
|
|
31
29
|
signal: extra.signal,
|
|
32
30
|
};
|
|
33
31
|
}
|
|
@@ -41,66 +39,53 @@ export function createPetitionHandler() {
|
|
|
41
39
|
containment: requireClaimField(input.containment, "containment"),
|
|
42
40
|
idiomatic: requireClaimField(input.idiomatic, "idiomatic"),
|
|
43
41
|
oath: requireClaimField(input.oath, "oath"),
|
|
44
|
-
cwd
|
|
42
|
+
cwd,
|
|
45
43
|
signal: extra.signal,
|
|
46
44
|
};
|
|
47
45
|
}
|
|
48
46
|
closeInput = claimInput;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
closeInput = {
|
|
55
|
-
intent: "FORFEIT",
|
|
56
|
-
cwd: workingDir,
|
|
57
|
-
signal: extra.signal,
|
|
47
|
+
const outcome = await petitionKeiyaku(closeInput);
|
|
48
|
+
return {
|
|
49
|
+
intent: "CLAIM",
|
|
50
|
+
input: claimInput,
|
|
51
|
+
outcome,
|
|
58
52
|
};
|
|
59
53
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
54
|
+
if (input.dangerouslyBypassGates) {
|
|
55
|
+
throw new FlowError("EMPTY_PARAM", "Parameter 'dangerouslyBypassGates' can only be used when intent=CLAIM.");
|
|
56
|
+
}
|
|
57
|
+
closeInput = {
|
|
58
|
+
intent: "FORFEIT",
|
|
59
|
+
cwd,
|
|
60
|
+
signal: extra.signal,
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
intent: "FORFEIT",
|
|
64
|
+
outcome: await petitionKeiyaku(closeInput),
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
buildResponse(result) {
|
|
68
|
+
if (result.intent === "CLAIM") {
|
|
69
|
+
if (!("result" in result.outcome) || result.outcome.result !== "merged") {
|
|
70
70
|
throw new FlowError("INTERNAL_STATE", "Unexpected CLAIM outcome shape");
|
|
71
71
|
}
|
|
72
|
-
const finalOutcome = outcome;
|
|
73
|
-
appendDebugLog(`tool close CLAIM success branch=${finalOutcome.currentBranch} base=${finalOutcome.baseBranch}`, {
|
|
74
|
-
cwd: workingDir,
|
|
75
|
-
section: "script",
|
|
76
|
-
});
|
|
72
|
+
const finalOutcome = result.outcome;
|
|
77
73
|
return buildPetitionClaimResponse(finalOutcome, {
|
|
78
|
-
title:
|
|
79
|
-
plea:
|
|
80
|
-
dangerouslyBypassGates: "dangerouslyBypassGates" in
|
|
81
|
-
placement: "placement" in
|
|
82
|
-
exactness: "exactness" in
|
|
83
|
-
containment: "containment" in
|
|
84
|
-
idiomatic: "idiomatic" in
|
|
85
|
-
oath: "oath" in
|
|
74
|
+
title: result.input.title,
|
|
75
|
+
plea: result.input.plea,
|
|
76
|
+
dangerouslyBypassGates: "dangerouslyBypassGates" in result.input && result.input.dangerouslyBypassGates === true,
|
|
77
|
+
placement: "placement" in result.input ? result.input.placement : undefined,
|
|
78
|
+
exactness: "exactness" in result.input ? result.input.exactness : undefined,
|
|
79
|
+
containment: "containment" in result.input ? result.input.containment : undefined,
|
|
80
|
+
idiomatic: "idiomatic" in result.input ? result.input.idiomatic : undefined,
|
|
81
|
+
oath: "oath" in result.input ? result.input.oath : undefined,
|
|
86
82
|
});
|
|
87
83
|
}
|
|
88
|
-
if (!("result" in outcome) || outcome.result !== "dropped") {
|
|
84
|
+
if (!("result" in result.outcome) || result.outcome.result !== "dropped") {
|
|
89
85
|
throw new FlowError("INTERNAL_STATE", "Unexpected FORFEIT outcome shape");
|
|
90
86
|
}
|
|
91
|
-
const finalOutcome = outcome;
|
|
92
|
-
appendDebugLog(`tool close FORFEIT success branch=${finalOutcome.currentBranch} base=${finalOutcome.baseBranch}`, {
|
|
93
|
-
cwd: workingDir,
|
|
94
|
-
section: "script",
|
|
95
|
-
});
|
|
87
|
+
const finalOutcome = result.outcome;
|
|
96
88
|
return buildPetitionForfeitResponse(finalOutcome, {});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return handleToolError({
|
|
100
|
-
error: err,
|
|
101
|
-
cwd: workingDir,
|
|
102
|
-
logLabel: "tool close",
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
};
|
|
89
|
+
},
|
|
90
|
+
});
|
|
106
91
|
}
|
|
@@ -14,6 +14,7 @@ import { claimKeiyaku } from "./claim.js";
|
|
|
14
14
|
import { forfeitKeiyaku } from "./forfeit.js";
|
|
15
15
|
const VERDICT_DENIED_CODE = "VERDICT_DENIED";
|
|
16
16
|
const DIMENSION_KEY_SET = new Set(CLAIM_DIMENSIONS);
|
|
17
|
+
const REVIEW_AUDIT_COMMIT_MESSAGE = "keiyaku: review audit trail";
|
|
17
18
|
function clampThreshold(value, fallback) {
|
|
18
19
|
if (typeof value !== "number" || !Number.isFinite(value))
|
|
19
20
|
return fallback;
|
|
@@ -88,6 +89,16 @@ async function appendDeniedVerdictWithAuditCommit(cwd, titleToken, scores, check
|
|
|
88
89
|
logWarn(warning, { cwd, section: "script" });
|
|
89
90
|
}
|
|
90
91
|
}
|
|
92
|
+
async function commitPassedReviewAuditTrail(cwd) {
|
|
93
|
+
try {
|
|
94
|
+
await addFiles(cwd, [TRACE_FILE]);
|
|
95
|
+
await commit(cwd, REVIEW_AUDIT_COMMIT_MESSAGE);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
const warning = `[CLAIM] Failed to commit passed review audit trail: ${error instanceof Error ? error.message : String(error)}`;
|
|
99
|
+
logWarn(warning, { cwd, section: "script" });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
91
102
|
async function loadCloseContext(cwd) {
|
|
92
103
|
const isRepo = await isGitRepo(cwd);
|
|
93
104
|
if (!isRepo) {
|
|
@@ -118,6 +129,7 @@ export async function petitionKeiyaku(input) {
|
|
|
118
129
|
return claimKeiyaku(input, context, {
|
|
119
130
|
resolveClaimGateConfig,
|
|
120
131
|
appendDeniedVerdictWithAuditCommit,
|
|
132
|
+
commitPassedReviewAuditTrail,
|
|
121
133
|
});
|
|
122
134
|
}
|
|
123
135
|
throw new FlowError("INVALID_CLOSE_INTENT", `unsupported close intent: ${intent}`);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { FlowError } from "../../flow-error.js";
|
|
2
|
+
import { getLatestCommitHash } from "../../git/staging.js";
|
|
3
|
+
const CONCURRENT_MODIFICATION_CODE = "CONCURRENT_MODIFICATION";
|
|
4
|
+
export async function assertHeadUnchanged(cwd, expectedHead, operation) {
|
|
5
|
+
const currentHead = await getLatestCommitHash(cwd);
|
|
6
|
+
if (currentHead === expectedHead) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
throw new FlowError(CONCURRENT_MODIFICATION_CODE, `concurrent ${operation} modification detected: repository HEAD changed from ${expectedHead} to ${currentHead} while the ${operation} was running`);
|
|
10
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MAX_DIFF_LINES_PER_FILE, TARGETED_DIFF_WARNING } from "../../git/diff/constants.js";
|
|
2
|
-
import {
|
|
2
|
+
import { filterDiffByCoordinates } from "../../git/diff/filter.js";
|
|
3
|
+
import { truncateTextWithFooter } from "../../git/diff/parsers.js";
|
|
3
4
|
import { readDiff } from "../../git/diff/read.js";
|
|
4
5
|
export async function renderIncrementalDiff({ cwd, range, mode, maxChars, coordinates, }) {
|
|
5
6
|
const statDiff = await readDiff(cwd, range, "stat", { maxChars: maxChars.stat });
|
|
@@ -24,5 +25,8 @@ export async function renderIncrementalDiff({ cwd, range, mode, maxChars, coordi
|
|
|
24
25
|
if (!targetedPatch) {
|
|
25
26
|
return `${statDiff}\n\n${TARGETED_DIFF_WARNING}`;
|
|
26
27
|
}
|
|
27
|
-
|
|
28
|
+
const cappedTargetedPatch = targetedPatch.length <= maxChars.targeted
|
|
29
|
+
? targetedPatch
|
|
30
|
+
: truncateTextWithFooter(targetedPatch, maxChars.targeted, targetedPatch.length - maxChars.targeted);
|
|
31
|
+
return `${statDiff}\n\n${cappedTargetedPatch}`;
|
|
28
32
|
}
|
|
@@ -28,6 +28,18 @@ const SECTION_TITLE_TO_KEY = {
|
|
|
28
28
|
"follow ups": "followUps",
|
|
29
29
|
followups: "followUps",
|
|
30
30
|
};
|
|
31
|
+
const SECTION_KEY_TO_TITLE = {
|
|
32
|
+
outcome: "Outcome",
|
|
33
|
+
changes: "Changes",
|
|
34
|
+
testResults: "Test Results",
|
|
35
|
+
aestheticsGap: "Aesthetics Gap",
|
|
36
|
+
blindspots: "Blindspots",
|
|
37
|
+
criteriaCheck: "Criteria Check",
|
|
38
|
+
rulesCheck: "Rules Check",
|
|
39
|
+
followUps: "Follow-ups",
|
|
40
|
+
diffCoordinates: "Diff Coordinates",
|
|
41
|
+
};
|
|
42
|
+
const MISSING_SECTION_WARNING_PREFIX = "> ⚠️ Missing section: ";
|
|
31
43
|
function createEmptySections() {
|
|
32
44
|
return {
|
|
33
45
|
outcome: null,
|
|
@@ -77,6 +89,11 @@ function toKnownSection(sectionMarkdown) {
|
|
|
77
89
|
function shouldRenderSection(key, policy) {
|
|
78
90
|
return policy.allowedSectionKeys.includes(key);
|
|
79
91
|
}
|
|
92
|
+
function renderMissingSectionWarnings(missingSections, policy) {
|
|
93
|
+
return missingSections
|
|
94
|
+
.filter((key) => shouldRenderSection(key, policy))
|
|
95
|
+
.map((key) => `${MISSING_SECTION_WARNING_PREFIX}${SECTION_KEY_TO_TITLE[key]}`);
|
|
96
|
+
}
|
|
80
97
|
export function parseRoundSummary(raw) {
|
|
81
98
|
const sections = parseMarkdownSections(raw);
|
|
82
99
|
if (sections.length === 0) {
|
|
@@ -124,10 +141,15 @@ export function renderRoundSummary(roundSummary, policy = TOOL_DEFAULT_POLICY) {
|
|
|
124
141
|
return [entry.section.markdown.trim()];
|
|
125
142
|
})
|
|
126
143
|
.filter((entry) => entry.length > 0);
|
|
127
|
-
|
|
144
|
+
const missingSectionWarnings = renderMissingSectionWarnings(roundSummary.missingSections, policy);
|
|
145
|
+
const unknownEntries = roundSummary.entries
|
|
146
|
+
.flatMap((entry) => (entry.kind === "unknown" ? [entry.markdown.trim()] : []))
|
|
147
|
+
.filter((entry) => entry.length > 0);
|
|
148
|
+
const renderedOutput = [...renderedEntries, ...missingSectionWarnings, ...unknownEntries];
|
|
149
|
+
if (renderedOutput.length === 0) {
|
|
128
150
|
return "";
|
|
129
151
|
}
|
|
130
|
-
return
|
|
152
|
+
return renderedOutput.join("\n\n").trim();
|
|
131
153
|
}
|
|
132
154
|
export function renderToolRoundSummary(roundSummary) {
|
|
133
155
|
return renderRoundSummary(roundSummary, TOOL_DEFAULT_POLICY);
|
package/build/tools/round/run.js
CHANGED
|
@@ -127,6 +127,9 @@ export async function runAndRecordRound(cwd, titleToken, round, prompt, options)
|
|
|
127
127
|
const summary = options.failureMode === "round_specific"
|
|
128
128
|
? `Round ${round} completed with subagent execution failure recorded in trace.`
|
|
129
129
|
: "Round 1 completed with subagent execution failure recorded in trace.";
|
|
130
|
+
if (options.beforePersist) {
|
|
131
|
+
await options.beforePersist();
|
|
132
|
+
}
|
|
130
133
|
if (options.beforeRoundWrite) {
|
|
131
134
|
await options.beforeRoundWrite();
|
|
132
135
|
}
|
|
@@ -137,6 +140,9 @@ export async function runAndRecordRound(cwd, titleToken, round, prompt, options)
|
|
|
137
140
|
hints: buildSubagentFailureHints(err),
|
|
138
141
|
});
|
|
139
142
|
}
|
|
143
|
+
if (options.beforePersist) {
|
|
144
|
+
await options.beforePersist();
|
|
145
|
+
}
|
|
140
146
|
if (options.beforeRoundWrite) {
|
|
141
147
|
await options.beforeRoundWrite();
|
|
142
148
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { formatArtifactDirectories, KEIYAKU_ARTIFACT_DIRS } from "../../keiyaku.js";
|
|
2
2
|
import { FlowError } from "../../flow-error.js";
|
|
3
|
-
import { getDirtyFiles, renderDirtyFileStatusLine } from "../../git/worktree.js";
|
|
3
|
+
import { DIRTY_FILE_CATEGORY, getDirtyFiles, renderDirtyFileStatusLine } from "../../git/worktree.js";
|
|
4
4
|
import * as path from "path";
|
|
5
5
|
const DIRTY_WORKTREE_LIST_LIMIT = 10;
|
|
6
6
|
const KEIYAKU_ARTIFACT_DIR_PREFIX = ".keiyaku/";
|
|
@@ -39,8 +39,12 @@ export function filterDirtyFilesByAllowlist(cwd, dirtyFiles, ignorePatterns) {
|
|
|
39
39
|
});
|
|
40
40
|
return { dirtyFiles: filteredDirtyFiles, ignoredPaths };
|
|
41
41
|
}
|
|
42
|
+
export function filterBlockingDirtyFiles(dirtyFiles) {
|
|
43
|
+
return dirtyFiles.filter((file) => file.category !== DIRTY_FILE_CATEGORY.untracked);
|
|
44
|
+
}
|
|
42
45
|
export async function assertCleanWorkingTree(cwd, ignorePatterns) {
|
|
43
|
-
const
|
|
46
|
+
const blockingDirtyFiles = filterBlockingDirtyFiles(await getDirtyFiles(cwd));
|
|
47
|
+
const { dirtyFiles } = filterDirtyFilesByAllowlist(cwd, blockingDirtyFiles, ignorePatterns);
|
|
44
48
|
if (dirtyFiles.length > 0) {
|
|
45
49
|
const hasDirtyKeiyakuArtifacts = dirtyFiles.some((file) => normalizePathForDirtyMatch(cwd, file.path).startsWith(KEIYAKU_ARTIFACT_DIR_PREFIX));
|
|
46
50
|
const shown = dirtyFiles.slice(0, DIRTY_WORKTREE_LIST_LIMIT);
|
|
@@ -1,34 +1,21 @@
|
|
|
1
|
-
import { appendDebugLog } from "../../telemetry/debug-log.js";
|
|
2
1
|
import { resolveTermPreset } from "../../config/term-presets/resolver.js";
|
|
3
2
|
import { readKeiyakuStatus } from "./read.js";
|
|
4
3
|
import { buildStatusResponse } from "../../mcp/responses.js";
|
|
5
|
-
import {
|
|
4
|
+
import { createToolHandler } from "../create-handler.js";
|
|
6
5
|
export function createStatusHandler() {
|
|
7
|
-
return
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const outcome = await readKeiyakuStatus({ cwd: workingDir });
|
|
16
|
-
appendDebugLog(`tool status success active=${outcome.active} round=${outcome.round}`, {
|
|
17
|
-
cwd: workingDir,
|
|
18
|
-
section: "script",
|
|
19
|
-
});
|
|
6
|
+
return createToolHandler({
|
|
7
|
+
logLabel: "tool status",
|
|
8
|
+
doneLog: (outcome) => `tool status success active=${outcome.active} round=${outcome.round}`,
|
|
9
|
+
async run({ cwd }) {
|
|
10
|
+
return readKeiyakuStatus({ cwd });
|
|
11
|
+
},
|
|
12
|
+
buildResponse(outcome) {
|
|
13
|
+
const preset = resolveTermPreset();
|
|
20
14
|
return buildStatusResponse(outcome, {
|
|
21
15
|
start: preset.tools.start.name,
|
|
22
16
|
drive: preset.tools.drive.name,
|
|
23
17
|
close: preset.tools.close.name,
|
|
24
18
|
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return handleToolError({
|
|
28
|
-
error: err,
|
|
29
|
-
cwd: workingDir,
|
|
30
|
-
logLabel: "tool status",
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
};
|
|
19
|
+
},
|
|
20
|
+
});
|
|
34
21
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { formatArtifactDirectories, KEIYAKU_ARTIFACT_DIRS, KEIYAKU_BRANCH_PREFIX, KEIYAKU_DRAFT_DIR, KEIYAKU_FILE, TRACE_FILE, } from "../../keiyaku.js";
|
|
4
|
+
import { isErrnoException } from "../../errno.js";
|
|
4
5
|
import { FlowError } from "../../flow-error.js";
|
|
5
6
|
import { resolveTermPreset } from "../../config/term-presets/resolver.js";
|
|
6
7
|
import { getCurrentBranch, isGitRepo, listLocalKeiyakuBranches } from "../../git/branches.js";
|
|
7
8
|
import { getKeiyakuBase } from "../../git/keiyaku-state.js";
|
|
8
9
|
import { getDirtyFiles, getUntrackedFiles, isPathTracked, renderDirtyFileStatusLine } from "../../git/worktree.js";
|
|
9
10
|
import { computeTraceState } from "../../protocol/trace.js";
|
|
10
|
-
import { filterDirtyFilesByAllowlist } from "../round/worktree.js";
|
|
11
|
+
import { filterBlockingDirtyFiles, filterDirtyFilesByAllowlist } from "../round/worktree.js";
|
|
11
12
|
import { isDraftArtifactPath, readLatestDraftArtifact } from "../../protocol/draft-artifacts.js";
|
|
12
13
|
import { extractDocumentPreviewBySections, extractLastRoundSummary, toStatusPreview } from "../../protocol/status-previews.js";
|
|
13
14
|
const STATUS_BRANCH_CANDIDATE_LIMIT = 3;
|
|
@@ -17,7 +18,7 @@ async function readOptionalFile(cwd, fileName) {
|
|
|
17
18
|
return await fs.readFile(path.join(cwd, fileName), "utf-8");
|
|
18
19
|
}
|
|
19
20
|
catch (error) {
|
|
20
|
-
if (error
|
|
21
|
+
if (isErrnoException(error) && error.code === "ENOENT") {
|
|
21
22
|
return null;
|
|
22
23
|
}
|
|
23
24
|
throw error;
|
|
@@ -29,7 +30,7 @@ async function isPresentFile(cwd, fileName) {
|
|
|
29
30
|
return stats.isFile();
|
|
30
31
|
}
|
|
31
32
|
catch (error) {
|
|
32
|
-
if (error
|
|
33
|
+
if (isErrnoException(error) && error.code === "ENOENT") {
|
|
33
34
|
return false;
|
|
34
35
|
}
|
|
35
36
|
throw error;
|
|
@@ -170,7 +171,8 @@ export async function readKeiyakuStatus(input) {
|
|
|
170
171
|
const [dirtyFiles, untrackedPaths] = await Promise.all([getDirtyFiles(cwd), getUntrackedFiles(cwd)]);
|
|
171
172
|
const rawDirtyPaths = Array.from(new Set(dirtyFiles.map((file) => file.path)));
|
|
172
173
|
const rawArtifactPaths = Array.from(new Set([...rawDirtyPaths, ...untrackedPaths]));
|
|
173
|
-
const
|
|
174
|
+
const blockingDirtyFiles = filterBlockingDirtyFiles(dirtyFiles);
|
|
175
|
+
const startBlockingDirtyFiles = filterDirtyFilesByAllowlist(cwd, blockingDirtyFiles, []).dirtyFiles;
|
|
174
176
|
const roundBlockingDirtyFiles = filterDirtyFilesByAllowlist(cwd, startBlockingDirtyFiles, []).dirtyFiles;
|
|
175
177
|
const dirtyPaths = Array.from(new Set(startBlockingDirtyFiles.map((file) => file.path)));
|
|
176
178
|
const dirtyStatusLines = startBlockingDirtyFiles.map((file) => renderDirtyFileStatusLine(file));
|
|
@@ -1,23 +1,22 @@
|
|
|
1
|
-
import { appendDebugLog } from "../../telemetry/debug-log.js";
|
|
2
1
|
import { summonKeiyaku } from "./run.js";
|
|
3
2
|
import { buildSummonSuccessResponse, } from "../../mcp/responses.js";
|
|
4
|
-
import {
|
|
3
|
+
import { createToolHandler } from "../create-handler.js";
|
|
5
4
|
function normalizeOptionalArg(value) {
|
|
6
5
|
if (value === undefined)
|
|
7
6
|
return undefined;
|
|
8
7
|
return value.trim().length > 0 ? value : undefined;
|
|
9
8
|
}
|
|
10
9
|
export function createSummonHandler() {
|
|
11
|
-
return
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
?
|
|
20
|
-
cwd
|
|
10
|
+
return createToolHandler({
|
|
11
|
+
logLabel: "tool start",
|
|
12
|
+
doneLog: (result) => `tool start success branch=${result.currentBranch} base=${result.baseBranch} round=${result.round}`,
|
|
13
|
+
async run({ from_draft, draft_only, title, goal, context, rules, criteria, cwd }, extra) {
|
|
14
|
+
const normalizedFromDraft = normalizeOptionalArg(from_draft);
|
|
15
|
+
const parsedCriteria = normalizeOptionalArg(criteria);
|
|
16
|
+
const parsedRules = normalizeOptionalArg(rules);
|
|
17
|
+
return normalizedFromDraft
|
|
18
|
+
? summonKeiyaku({
|
|
19
|
+
cwd,
|
|
21
20
|
from_draft: normalizedFromDraft,
|
|
22
21
|
draft_only,
|
|
23
22
|
title: normalizeOptionalArg(title),
|
|
@@ -27,8 +26,8 @@ export function createSummonHandler() {
|
|
|
27
26
|
criteria: parsedCriteria,
|
|
28
27
|
signal: extra.signal,
|
|
29
28
|
})
|
|
30
|
-
:
|
|
31
|
-
cwd
|
|
29
|
+
: summonKeiyaku({
|
|
30
|
+
cwd,
|
|
32
31
|
draft_only,
|
|
33
32
|
title: title ?? "",
|
|
34
33
|
goal: goal ?? "",
|
|
@@ -37,18 +36,9 @@ export function createSummonHandler() {
|
|
|
37
36
|
criteria: parsedCriteria ?? "",
|
|
38
37
|
signal: extra.signal,
|
|
39
38
|
});
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
section: "script",
|
|
43
|
-
});
|
|
39
|
+
},
|
|
40
|
+
buildResponse(result) {
|
|
44
41
|
return buildSummonSuccessResponse(result);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return handleToolError({
|
|
48
|
-
error: err,
|
|
49
|
-
cwd: workingDir,
|
|
50
|
-
logLabel: "tool start",
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
};
|
|
42
|
+
},
|
|
43
|
+
});
|
|
54
44
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "fs/promises";
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import { KEIYAKU_BRANCH_PREFIX, KEIYAKU_FILE, TRACE_FILE, } from "../../keiyaku.js";
|
|
4
|
+
import { isErrnoException } from "../../errno.js";
|
|
4
5
|
import { logInfo, logWarn } from "../../telemetry/logger.js";
|
|
5
6
|
import { FlowError } from "../../flow-error.js";
|
|
6
7
|
import { assertValidBranchName, checkoutBranch, createAndCheckoutBranch, deleteBranch, getCurrentBranch, hasLocalBranch, isGitRepo, listLocalKeiyakuBranches } from "../../git/branches.js";
|
|
@@ -30,7 +31,7 @@ async function fileExists(filePath) {
|
|
|
30
31
|
return true;
|
|
31
32
|
}
|
|
32
33
|
catch (error) {
|
|
33
|
-
if (error
|
|
34
|
+
if (isErrnoException(error) && error.code === "ENOENT") {
|
|
34
35
|
return false;
|
|
35
36
|
}
|
|
36
37
|
throw error;
|
|
@@ -101,10 +102,28 @@ async function initKeiyakuBranch(cwd, resolved, keiyakuContent) {
|
|
|
101
102
|
}
|
|
102
103
|
async function finalizeSummonKeiyaku(input, resolved, setup, baseRules) {
|
|
103
104
|
const { cwd } = input;
|
|
104
|
-
const { branchToken, baseBranch,
|
|
105
|
+
const { branchToken, baseBranch, existingBranches, keiyakuBranch } = setup;
|
|
105
106
|
const identity = selectSubagent().displayName;
|
|
106
107
|
const summary = SUMMON_READY_SUMMARY;
|
|
107
108
|
const diff = SUMMON_READY_DIFF;
|
|
109
|
+
let commit = setup.commit;
|
|
110
|
+
let consumedFromDraft;
|
|
111
|
+
if (resolved.fromDraft && resolved.fromDraftPath) {
|
|
112
|
+
const trackedFromDraftPath = resolveGitRelativePath(cwd, resolved.fromDraftPath);
|
|
113
|
+
const shouldCommitDeletion = trackedFromDraftPath !== null && (await isPathTracked(cwd, trackedFromDraftPath));
|
|
114
|
+
await fs.unlink(resolved.fromDraftPath);
|
|
115
|
+
if (shouldCommitDeletion && trackedFromDraftPath) {
|
|
116
|
+
await addFiles(cwd, trackedFromDraftPath);
|
|
117
|
+
await commitChanges(cwd, `keiyaku(${branchToken}): ${SUMMON_CONSUME_FROM_DRAFT_COMMIT_SUFFIX}`);
|
|
118
|
+
commit = await getLatestCommitHash(cwd);
|
|
119
|
+
}
|
|
120
|
+
consumedFromDraft = resolved.fromDraft;
|
|
121
|
+
logInfo(`[keiyaku] Consumed from_draft and deleted source draft: ${resolved.fromDraft}`, {
|
|
122
|
+
cwd,
|
|
123
|
+
section: "script",
|
|
124
|
+
progressOnly: true,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
108
127
|
let responsePath;
|
|
109
128
|
try {
|
|
110
129
|
responsePath = await persistResponseHistory({
|
|
@@ -128,22 +147,6 @@ async function finalizeSummonKeiyaku(input, resolved, setup, baseRules) {
|
|
|
128
147
|
section: "script",
|
|
129
148
|
});
|
|
130
149
|
}
|
|
131
|
-
let consumedFromDraft;
|
|
132
|
-
if (resolved.fromDraft && resolved.fromDraftPath) {
|
|
133
|
-
const trackedFromDraftPath = resolveGitRelativePath(cwd, resolved.fromDraftPath);
|
|
134
|
-
const shouldCommitDeletion = trackedFromDraftPath !== null && (await isPathTracked(cwd, trackedFromDraftPath));
|
|
135
|
-
await fs.unlink(resolved.fromDraftPath);
|
|
136
|
-
if (shouldCommitDeletion && trackedFromDraftPath) {
|
|
137
|
-
await addFiles(cwd, trackedFromDraftPath);
|
|
138
|
-
await commitChanges(cwd, `keiyaku(${branchToken}): ${SUMMON_CONSUME_FROM_DRAFT_COMMIT_SUFFIX}`);
|
|
139
|
-
}
|
|
140
|
-
consumedFromDraft = resolved.fromDraft;
|
|
141
|
-
logInfo(`[keiyaku] Consumed from_draft and deleted source draft: ${resolved.fromDraft}`, {
|
|
142
|
-
cwd,
|
|
143
|
-
section: "script",
|
|
144
|
-
progressOnly: true,
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
150
|
return {
|
|
148
151
|
status: "success",
|
|
149
152
|
round: 0,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrosheep/keiyaku",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.78",
|
|
4
4
|
"description": "MCP server for running iterative keiyaku workflows with Codex subagents.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -39,14 +39,14 @@
|
|
|
39
39
|
"release": "npm version patch && npm publish"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@modelcontextprotocol/sdk": "
|
|
42
|
+
"@modelcontextprotocol/sdk": "1.26.0",
|
|
43
43
|
"@openai/codex-sdk": "^0.113.0",
|
|
44
44
|
"simple-git": "^3.21.0",
|
|
45
|
-
"zod": "
|
|
45
|
+
"zod": "4.3.6"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@types/node": "
|
|
49
|
-
"tsx": "
|
|
50
|
-
"typescript": "
|
|
48
|
+
"@types/node": "25.2.2",
|
|
49
|
+
"tsx": "4.21.0",
|
|
50
|
+
"typescript": "5.9.3"
|
|
51
51
|
}
|
|
52
52
|
}
|
package/build/git/diff/stat.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { truncateTextWithFooter } from "./parsers.js";
|
|
2
|
-
export function renderStatDiffPreviewText(output, maxChars) {
|
|
3
|
-
const trimmed = output.trim();
|
|
4
|
-
if (!trimmed)
|
|
5
|
-
return "No diff.";
|
|
6
|
-
if (trimmed.length <= maxChars)
|
|
7
|
-
return trimmed;
|
|
8
|
-
return truncateTextWithFooter(trimmed, maxChars, trimmed.length - maxChars);
|
|
9
|
-
}
|