@astrosheep/keiyaku 0.1.87 → 1.0.1
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 +74 -65
- package/build/.tsbuildinfo +1 -1
- package/build/agents/effective-policy.js +32 -0
- package/build/agents/harness/event-channel.js +178 -0
- package/build/agents/harness/execution-handle.js +117 -0
- package/build/agents/harness/index.js +622 -0
- package/build/agents/harness/pump.js +152 -0
- package/build/agents/harness/runtime.js +68 -0
- package/build/agents/index.js +104 -213
- package/build/agents/opencode-sdk.js +12 -18
- package/build/agents/providers/claude-agent-sdk.js +529 -0
- package/build/agents/providers/codex-app-server.js +656 -0
- package/build/agents/providers/codex-sdk.js +193 -0
- package/build/agents/providers/opencode-sdk.js +257 -0
- package/build/agents/providers/pi.js +345 -0
- package/build/agents/selector.js +15 -6
- package/build/agents/session.js +29 -0
- package/build/agents/types.js +5 -11
- package/build/cli/commands/akuma.js +79 -0
- package/build/cli/completion.js +71 -0
- package/build/cli/draft-artifact.js +50 -0
- package/build/cli/flags.js +96 -0
- package/build/cli/help.js +119 -0
- package/build/cli/index.js +204 -912
- package/build/cli/parse.js +280 -0
- package/build/cli/render/arc.js +63 -0
- package/build/{responses/tool-errors.js → cli/render/errors.js} +15 -16
- package/build/cli/render/format.js +199 -0
- package/build/cli/render/misc.js +40 -0
- package/build/cli/render/petition.js +32 -0
- package/build/{responses → cli/render}/response-style.js +4 -4
- package/build/cli/render/shared.js +122 -0
- package/build/cli/render/status.js +69 -0
- package/build/cli/render/summon.js +17 -0
- package/build/cli/render/tell.js +11 -0
- package/build/cli/skills-install.js +237 -0
- package/build/cli/subagent-guard.js +6 -3
- package/build/config/akuma-loader.js +268 -0
- package/build/config/env-keys.js +0 -10
- package/build/config/{schema.js → env.js} +117 -23
- package/build/config/settings.js +137 -260
- package/build/core/amend.js +120 -0
- package/build/core/arc.js +225 -0
- package/build/core/atomic-publish.js +64 -0
- package/build/core/bind.js +281 -0
- package/build/core/claim.js +360 -0
- package/build/core/command-io.js +174 -0
- package/build/core/context.js +108 -0
- package/build/core/contract.js +14 -0
- package/build/core/draft.js +82 -0
- package/build/core/entry.js +203 -0
- package/build/core/forfeit.js +60 -0
- package/build/core/hints.js +108 -0
- package/build/core/ids.js +70 -0
- package/build/core/ledger.js +117 -0
- package/build/core/log.js +24 -0
- package/build/{protocol → core}/markdown/parser.js +68 -0
- package/build/{protocol → core}/markdown/sections.js +2 -28
- package/build/core/markdown/titles.js +9 -0
- package/build/{tools/petition/claim-gates.js → core/petition-claim-gates.js} +43 -29
- package/build/core/petition-claim.js +116 -0
- package/build/core/petition-forfeit.js +39 -0
- package/build/{tools/round/head-guard.js → core/petition-head-guard.js} +2 -2
- package/build/core/petition-run.js +81 -0
- package/build/core/petition.js +96 -0
- package/build/core/places.js +82 -0
- package/build/core/projection-core.js +541 -0
- package/build/core/projection-mint.js +79 -0
- package/build/core/projection-status.js +179 -0
- package/build/core/projection-wake.js +252 -0
- package/build/core/queue.js +73 -0
- package/build/core/ref-log.js +94 -0
- package/build/core/registry.js +96 -0
- package/build/core/render.js +326 -0
- package/build/core/renew.js +195 -0
- package/build/core/scope.js +163 -0
- package/build/core/seal.js +195 -0
- package/build/core/status.js +476 -0
- package/build/{tools/summon/persist.js → core/summon-persist.js} +10 -9
- package/build/core/summon.js +377 -0
- package/build/{protocol/response-history.js → core/transcripts.js} +83 -39
- package/build/core/verdict.js +96 -0
- package/build/core/worktree-path.js +134 -0
- package/build/flow-error.js +80 -4
- package/build/generated/version.js +1 -1
- package/build/git/branches.js +8 -9
- package/build/git/core.js +1 -1
- package/build/git/diff/pathspec.js +2 -3
- package/build/git/diff/preview.js +7 -2
- package/build/git/diff/read.js +4 -3
- package/build/git/staging.js +1 -1
- package/build/index.js +12 -6
- package/build/keiyaku.js +17 -13
- package/build/telemetry/debug-log.js +1 -1
- package/package.json +16 -9
- package/skills/keiyaku/SKILL.md +16 -13
- package/build/agents/claude-agent-sdk-exec.js +0 -352
- package/build/agents/codex-cli-exec.js +0 -237
- package/build/agents/codex-sdk-exec.js +0 -291
- package/build/agents/gemini-cli-core-exec.js +0 -499
- package/build/agents/opencode-exec.js +0 -297
- package/build/agents/process-runner.js +0 -239
- package/build/agents/progress-reporter.js +0 -137
- package/build/agents/round-runner.js +0 -53
- package/build/config/apply-argument-descriptions.js +0 -16
- package/build/config/architect-hints.js +0 -374
- package/build/config/base-rules.js +0 -23
- package/build/config/config.js +0 -50
- package/build/config/dotenv.js +0 -61
- package/build/config/keiyaku-home.js +0 -9
- package/build/config/path-utils.js +0 -11
- package/build/config/render-template.js +0 -8
- package/build/git/diff/filter.js +0 -67
- package/build/git/keiyaku-state.js +0 -58
- package/build/git/snapshot.js +0 -41
- package/build/git/stash.js +0 -62
- package/build/protocol/bind-draft.js +0 -195
- package/build/protocol/draft-artifacts.js +0 -99
- package/build/protocol/file-guards.js +0 -24
- package/build/protocol/keiyaku-draft.js +0 -168
- package/build/protocol/ledger.js +0 -374
- package/build/protocol/markdown/index.js +0 -14
- package/build/protocol/markdown/normalization.js +0 -71
- package/build/protocol/markdown/render.js +0 -68
- package/build/protocol/oath.js +0 -36
- package/build/protocol/render-keiyaku.js +0 -37
- package/build/protocol/review.js +0 -1
- package/build/protocol/status-previews.js +0 -68
- package/build/responses/responses.js +0 -515
- package/build/tools/amend/index.js +0 -18
- package/build/tools/amend/run.js +0 -44
- package/build/tools/bind/index.js +0 -44
- package/build/tools/bind/run.js +0 -237
- package/build/tools/contracts.js +0 -1
- package/build/tools/create-handler.js +0 -31
- package/build/tools/help.js +0 -9
- package/build/tools/petition/claim.js +0 -174
- package/build/tools/petition/forfeit.js +0 -100
- package/build/tools/petition/index.js +0 -83
- package/build/tools/petition/run.js +0 -66
- package/build/tools/round/guidance.js +0 -7
- package/build/tools/round/prompt.js +0 -70
- package/build/tools/round/report.js +0 -153
- package/build/tools/round/worktree.js +0 -55
- package/build/tools/round-close/index.js +0 -19
- package/build/tools/round-close/run.js +0 -98
- package/build/tools/round-open/index.js +0 -20
- package/build/tools/round-open/run.js +0 -77
- package/build/tools/schema.js +0 -69
- package/build/tools/status/index.js +0 -16
- package/build/tools/status/read.js +0 -155
- package/build/tools/summon/index.js +0 -27
- package/build/tools/summon/run.js +0 -80
- /package/build/{config/toml.js → agents/harness/events.js} +0 -0
- /package/build/{git/diff/contracts.js → cli/render/types.js} +0 -0
- /package/build/{protocol/bind-input.js → cli/types.js} +0 -0
- /package/build/{protocol → core}/markdown/lex.js +0 -0
- /package/build/{protocol → core}/markdown/types.js +0 -0
package/build/git/diff/filter.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { isDeletedDiffSection, parseDiffPathFromHeader, parseUnifiedHunks, splitDiffByFile, truncateDiffSectionByLineCount, } from "./parsers.js";
|
|
2
|
-
const NOT_SHOWN_IN_DETAIL_PREFIX = "--- not shown in detail: ";
|
|
3
|
-
function rangesOverlap(aStart, aEnd, bStart, bEnd) {
|
|
4
|
-
return aStart <= bEnd && bStart <= aEnd;
|
|
5
|
-
}
|
|
6
|
-
function buildCoordinateIndex(coordinates) {
|
|
7
|
-
const index = new Map();
|
|
8
|
-
for (const coordinate of coordinates) {
|
|
9
|
-
const existing = index.get(coordinate.path) ?? [];
|
|
10
|
-
existing.push(coordinate);
|
|
11
|
-
index.set(coordinate.path, existing);
|
|
12
|
-
}
|
|
13
|
-
return index;
|
|
14
|
-
}
|
|
15
|
-
export function filterDiffByCoordinates(rawPatch, coordinates, maxLinesPerFile = Number.POSITIVE_INFINITY) {
|
|
16
|
-
const sections = splitDiffByFile(rawPatch);
|
|
17
|
-
if (sections.length === 0)
|
|
18
|
-
return "";
|
|
19
|
-
const byPath = buildCoordinateIndex(coordinates);
|
|
20
|
-
const selectedSectionsByPath = new Map();
|
|
21
|
-
const deletedSections = [];
|
|
22
|
-
const nonDeletedPathsInPatch = [];
|
|
23
|
-
const sectionByPath = new Map();
|
|
24
|
-
for (const section of sections) {
|
|
25
|
-
const path = parseDiffPathFromHeader(section[0] ?? "");
|
|
26
|
-
if (!path)
|
|
27
|
-
continue;
|
|
28
|
-
if (isDeletedDiffSection(section)) {
|
|
29
|
-
deletedSections.push(section.join("\n"));
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
if (!sectionByPath.has(path)) {
|
|
33
|
-
nonDeletedPathsInPatch.push(path);
|
|
34
|
-
sectionByPath.set(path, section);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
for (const coordinate of coordinates) {
|
|
38
|
-
const section = sectionByPath.get(coordinate.path);
|
|
39
|
-
if (!section || selectedSectionsByPath.has(coordinate.path))
|
|
40
|
-
continue;
|
|
41
|
-
const fileCoordinates = byPath.get(coordinate.path);
|
|
42
|
-
if (!fileCoordinates || fileCoordinates.length === 0)
|
|
43
|
-
continue;
|
|
44
|
-
const { prelude, hunks } = parseUnifiedHunks(section);
|
|
45
|
-
if (hunks.length === 0)
|
|
46
|
-
continue;
|
|
47
|
-
const matchingHunks = hunks.filter((hunk) => fileCoordinates.some((coord) => rangesOverlap(hunk.newStart, hunk.newEnd, coord.start, coord.end)));
|
|
48
|
-
if (matchingHunks.length === 0)
|
|
49
|
-
continue;
|
|
50
|
-
const rendered = [
|
|
51
|
-
...prelude,
|
|
52
|
-
...matchingHunks.flatMap((hunk) => [hunk.header, ...hunk.lines]),
|
|
53
|
-
].join("\n");
|
|
54
|
-
selectedSectionsByPath.set(coordinate.path, truncateDiffSectionByLineCount(rendered, maxLinesPerFile));
|
|
55
|
-
}
|
|
56
|
-
const selectedSections = [
|
|
57
|
-
...selectedSectionsByPath.values(),
|
|
58
|
-
...deletedSections.map((section) => truncateDiffSectionByLineCount(section, maxLinesPerFile)),
|
|
59
|
-
];
|
|
60
|
-
if (selectedSections.length === 0)
|
|
61
|
-
return "";
|
|
62
|
-
const filteredOutPaths = nonDeletedPathsInPatch.filter((path) => !selectedSectionsByPath.has(path));
|
|
63
|
-
if (filteredOutPaths.length > 0) {
|
|
64
|
-
selectedSections.push(`${NOT_SHOWN_IN_DETAIL_PREFIX}${filteredOutPaths.join(", ")}`);
|
|
65
|
-
}
|
|
66
|
-
return selectedSections.join("\n");
|
|
67
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { KEIYAKU_BRANCH_PREFIX } from "../keiyaku.js";
|
|
2
|
-
import { MISSING_GIT_CONFIG_PATTERNS, createGit, errorContainsAnyPattern, wrapGitError, } from "./core.js";
|
|
3
|
-
import { getCurrentBranch } from "./branches.js";
|
|
4
|
-
export async function getActiveKeiyakuGitState(cwd) {
|
|
5
|
-
const branch = await getCurrentBranch(cwd);
|
|
6
|
-
if (!branch.startsWith(KEIYAKU_BRANCH_PREFIX))
|
|
7
|
-
return null;
|
|
8
|
-
const baseBranch = await getKeiyakuBase(cwd, branch);
|
|
9
|
-
if (!baseBranch)
|
|
10
|
-
return null;
|
|
11
|
-
return { branch, baseBranch };
|
|
12
|
-
}
|
|
13
|
-
function branchBaseKey(branchName) {
|
|
14
|
-
return `branch.${branchName}.keiyakuBase`;
|
|
15
|
-
}
|
|
16
|
-
export async function setKeiyakuBase(cwd, branchName, baseBranch) {
|
|
17
|
-
const git = createGit(cwd);
|
|
18
|
-
try {
|
|
19
|
-
await git.raw(["config", branchBaseKey(branchName), baseBranch]);
|
|
20
|
-
}
|
|
21
|
-
catch (err) {
|
|
22
|
-
throw wrapGitError(`config ${branchBaseKey(branchName)} ${baseBranch}`, err, cwd);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
export async function getKeiyakuBase(cwd, branchName) {
|
|
26
|
-
const git = createGit(cwd);
|
|
27
|
-
try {
|
|
28
|
-
const value = await git.raw(["config", "--default", "", "--get", branchBaseKey(branchName)]);
|
|
29
|
-
return value.trim() || null;
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
throw wrapGitError(`config --default '' --get ${branchBaseKey(branchName)}`, error, cwd);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
export async function clearKeiyakuBase(cwd, branchName) {
|
|
36
|
-
const git = createGit(cwd);
|
|
37
|
-
const key = branchBaseKey(branchName);
|
|
38
|
-
let currentValue = "";
|
|
39
|
-
try {
|
|
40
|
-
currentValue = (await git.raw(["config", "--default", "", "--get", key])).trim();
|
|
41
|
-
}
|
|
42
|
-
catch (error) {
|
|
43
|
-
throw wrapGitError(`config --default '' --get ${key}`, error, cwd);
|
|
44
|
-
}
|
|
45
|
-
if (!currentValue) {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
try {
|
|
49
|
-
await git.raw(["config", "--unset", key]);
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
if (errorContainsAnyPattern(error, MISSING_GIT_CONFIG_PATTERNS)) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
throw wrapGitError(`config --unset ${key}`, error, cwd);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
export { branchBaseKey };
|
package/build/git/snapshot.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs/promises";
|
|
2
|
-
import * as os from "os";
|
|
3
|
-
import * as path from "path";
|
|
4
|
-
import { NOT_GIT_REPOSITORY_PATTERNS, createGit, errorContainsAnyPattern, wrapGitError } from "./core.js";
|
|
5
|
-
async function withTempIndex(cwd, run) {
|
|
6
|
-
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "keiyaku-git-index-"));
|
|
7
|
-
const tempIndexPath = path.join(tempDir, "index");
|
|
8
|
-
const git = createGit(cwd);
|
|
9
|
-
git.env("GIT_INDEX_FILE", tempIndexPath);
|
|
10
|
-
try {
|
|
11
|
-
return await run(git);
|
|
12
|
-
}
|
|
13
|
-
finally {
|
|
14
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
export async function createWorkspaceSnapshot(cwd) {
|
|
18
|
-
try {
|
|
19
|
-
return await withTempIndex(cwd, async (git) => {
|
|
20
|
-
await git.raw(["add", "-A"]);
|
|
21
|
-
const tree = await git.raw(["write-tree"]);
|
|
22
|
-
return tree.trim() || undefined;
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
catch (error) {
|
|
26
|
-
if (errorContainsAnyPattern(error, NOT_GIT_REPOSITORY_PATTERNS)) {
|
|
27
|
-
return undefined;
|
|
28
|
-
}
|
|
29
|
-
throw wrapGitError("create workspace snapshot", error, cwd);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
export async function getChangedFilesBetweenSnapshots(cwd, beforeSha, afterSha) {
|
|
33
|
-
const git = createGit(cwd);
|
|
34
|
-
try {
|
|
35
|
-
const output = await git.raw(["diff", "--name-status", beforeSha, afterSha]);
|
|
36
|
-
return output.trim();
|
|
37
|
-
}
|
|
38
|
-
catch (err) {
|
|
39
|
-
throw wrapGitError(`diff --name-status ${beforeSha} ${afterSha}`, err, cwd);
|
|
40
|
-
}
|
|
41
|
-
}
|
package/build/git/stash.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { createGit, wrapGitError } from "./core.js";
|
|
2
|
-
function parseStashRefs(raw) {
|
|
3
|
-
return raw
|
|
4
|
-
.split(/\r?\n/)
|
|
5
|
-
.map((line) => line.trim())
|
|
6
|
-
.filter((line) => line.length > 0);
|
|
7
|
-
}
|
|
8
|
-
async function listStashRefs(cwd) {
|
|
9
|
-
const git = createGit(cwd);
|
|
10
|
-
try {
|
|
11
|
-
const output = await git.raw(["stash", "list", "--format=%gd"]);
|
|
12
|
-
return parseStashRefs(output);
|
|
13
|
-
}
|
|
14
|
-
catch (err) {
|
|
15
|
-
throw wrapGitError("stash list --format=%gd", err, cwd);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
export async function createSafetyStash(cwd, message) {
|
|
19
|
-
const before = await listStashRefs(cwd);
|
|
20
|
-
const git = createGit(cwd);
|
|
21
|
-
try {
|
|
22
|
-
await git.raw(["stash", "push", "--include-untracked", "--message", message]);
|
|
23
|
-
}
|
|
24
|
-
catch (err) {
|
|
25
|
-
throw wrapGitError(`stash push --include-untracked --message ${message}`, err, cwd);
|
|
26
|
-
}
|
|
27
|
-
const after = await listStashRefs(cwd);
|
|
28
|
-
if (after.length === before.length) {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
return after[0] ?? null;
|
|
32
|
-
}
|
|
33
|
-
export async function applyStash(cwd, stashRef) {
|
|
34
|
-
const git = createGit(cwd);
|
|
35
|
-
try {
|
|
36
|
-
await git.raw(["stash", "apply", stashRef]);
|
|
37
|
-
}
|
|
38
|
-
catch (err) {
|
|
39
|
-
throw wrapGitError(`stash apply ${stashRef}`, err, cwd);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
export async function dropStash(cwd, stashRef) {
|
|
43
|
-
const git = createGit(cwd);
|
|
44
|
-
try {
|
|
45
|
-
await git.raw(["stash", "drop", stashRef]);
|
|
46
|
-
}
|
|
47
|
-
catch (err) {
|
|
48
|
-
throw wrapGitError(`stash drop ${stashRef}`, err, cwd);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
export async function discardAllWorkingTreeChanges(cwd) {
|
|
52
|
-
await resetHardHead(cwd);
|
|
53
|
-
}
|
|
54
|
-
export async function resetHardHead(cwd) {
|
|
55
|
-
const git = createGit(cwd);
|
|
56
|
-
try {
|
|
57
|
-
await git.raw(["reset", "--hard", "HEAD"]);
|
|
58
|
-
}
|
|
59
|
-
catch (err) {
|
|
60
|
-
throw wrapGitError("reset --hard HEAD", err, cwd);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs/promises";
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
import { isErrnoException } from "../errno.js";
|
|
4
|
-
import { buildBindDraftRecoveryHints, buildBindDraftSaveFailureHint, } from "../config/architect-hints.js";
|
|
5
|
-
import { FlowError, isFlowError, requireText, wrapFlowError } from "../flow-error.js";
|
|
6
|
-
import { writeDraftArtifact, listDraftArtifacts } from "./draft-artifacts.js";
|
|
7
|
-
import { logInfo } from "../telemetry/logger.js";
|
|
8
|
-
import { parseAndValidateKeiyakuDraft } from "./keiyaku-draft.js";
|
|
9
|
-
const INTERNAL_BIND_DIRTY_ALLOWLIST = [];
|
|
10
|
-
const REQUIRED_BIND_FIELDS = ["title", "goal", "context", "criteria"];
|
|
11
|
-
function normalizeOptionalText(value) {
|
|
12
|
-
if (value === undefined)
|
|
13
|
-
return undefined;
|
|
14
|
-
const normalized = value.trim();
|
|
15
|
-
return normalized.length > 0 ? normalized : undefined;
|
|
16
|
-
}
|
|
17
|
-
function resolveFromDraftPath(cwd, fromDraft) {
|
|
18
|
-
return path.isAbsolute(fromDraft) ? fromDraft : path.join(cwd, fromDraft);
|
|
19
|
-
}
|
|
20
|
-
export function resolveGitRelativePath(cwd, filePath) {
|
|
21
|
-
const relativePath = path.relative(cwd, filePath);
|
|
22
|
-
if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
return relativePath.split(path.sep).join("/");
|
|
26
|
-
}
|
|
27
|
-
function normalizeOptionalMarkdownText(name, value) {
|
|
28
|
-
if (value === undefined)
|
|
29
|
-
return undefined;
|
|
30
|
-
if (typeof value !== "string") {
|
|
31
|
-
throw new FlowError("INVALID_KEIYAKU_DRAFT", `invalid keiyaku draft: field '${name}' must be a markdown string`);
|
|
32
|
-
}
|
|
33
|
-
const normalized = value.trim();
|
|
34
|
-
return normalized.length > 0 ? normalized : undefined;
|
|
35
|
-
}
|
|
36
|
-
function assertRequiredBindFields(values) {
|
|
37
|
-
for (const field of REQUIRED_BIND_FIELDS) {
|
|
38
|
-
if (!values[field]) {
|
|
39
|
-
throw new FlowError("INVALID_KEIYAKU_DRAFT", `invalid keiyaku draft: missing required field '${field}'`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
export async function resolveBindInput(cwd, input) {
|
|
44
|
-
const fromDraft = normalizeOptionalText(input.from_draft);
|
|
45
|
-
const dirtyAllowlist = [...INTERNAL_BIND_DIRTY_ALLOWLIST];
|
|
46
|
-
const provided = {
|
|
47
|
-
title: normalizeOptionalText(input.title),
|
|
48
|
-
goal: normalizeOptionalText(input.goal),
|
|
49
|
-
context: normalizeOptionalText(input.context),
|
|
50
|
-
criteria: normalizeOptionalMarkdownText("criteria", input.criteria),
|
|
51
|
-
rules: normalizeOptionalMarkdownText("rules", input.rules),
|
|
52
|
-
};
|
|
53
|
-
if (!fromDraft) {
|
|
54
|
-
assertRequiredBindFields({
|
|
55
|
-
title: provided.title,
|
|
56
|
-
goal: provided.goal,
|
|
57
|
-
context: provided.context,
|
|
58
|
-
criteria: provided.criteria,
|
|
59
|
-
});
|
|
60
|
-
return {
|
|
61
|
-
title: requireText("title", provided.title),
|
|
62
|
-
goal: requireText("goal", provided.goal),
|
|
63
|
-
context: requireText("context", provided.context),
|
|
64
|
-
criteria: requireText("criteria", provided.criteria),
|
|
65
|
-
rules: provided.rules ?? "",
|
|
66
|
-
fromDraft: undefined,
|
|
67
|
-
fromDraftPath: undefined,
|
|
68
|
-
dirtyAllowlist,
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
const draftPath = resolveFromDraftPath(cwd, fromDraft);
|
|
72
|
-
let content;
|
|
73
|
-
try {
|
|
74
|
-
content = await fs.readFile(draftPath, "utf-8");
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
if (isErrnoException(error) && error.code === "ENOENT") {
|
|
78
|
-
throw new FlowError("FROM_DRAFT_NOT_FOUND", `from_draft path does not exist: ${fromDraft}`);
|
|
79
|
-
}
|
|
80
|
-
throw error;
|
|
81
|
-
}
|
|
82
|
-
const draft = parseAndValidateKeiyakuDraft(content);
|
|
83
|
-
const title = provided.title ?? draft.title;
|
|
84
|
-
const goal = provided.goal ?? draft.goal;
|
|
85
|
-
const context = provided.context ?? draft.context;
|
|
86
|
-
const criteria = provided.criteria ?? draft.criteria;
|
|
87
|
-
const rules = provided.rules ?? draft.rules ?? "";
|
|
88
|
-
assertRequiredBindFields({
|
|
89
|
-
title,
|
|
90
|
-
goal,
|
|
91
|
-
context,
|
|
92
|
-
criteria,
|
|
93
|
-
});
|
|
94
|
-
return {
|
|
95
|
-
title: requireText("title", title),
|
|
96
|
-
goal: requireText("goal", goal),
|
|
97
|
-
context: requireText("context", context),
|
|
98
|
-
criteria: requireText("criteria", criteria),
|
|
99
|
-
rules,
|
|
100
|
-
fromDraft,
|
|
101
|
-
fromDraftPath: draftPath,
|
|
102
|
-
dirtyAllowlist,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
export async function readRollbackDraftSnapshot(cwd, candidatePath) {
|
|
106
|
-
const relativePath = resolveGitRelativePath(cwd, candidatePath);
|
|
107
|
-
if (!relativePath)
|
|
108
|
-
return null;
|
|
109
|
-
const absolutePath = path.join(cwd, relativePath);
|
|
110
|
-
try {
|
|
111
|
-
const stat = await fs.stat(absolutePath);
|
|
112
|
-
if (!stat.isFile())
|
|
113
|
-
return null;
|
|
114
|
-
const content = await fs.readFile(absolutePath);
|
|
115
|
-
return { relativePath, content };
|
|
116
|
-
}
|
|
117
|
-
catch (error) {
|
|
118
|
-
if (isErrnoException(error) && error.code === "ENOENT") {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
throw error;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
export async function captureRollbackDraftSnapshots(cwd, fromDraftPath) {
|
|
125
|
-
const canonicalDraftPaths = await listDraftArtifacts(cwd);
|
|
126
|
-
const candidatePaths = canonicalDraftPaths.map((relativePath) => path.join(cwd, relativePath));
|
|
127
|
-
if (fromDraftPath)
|
|
128
|
-
candidatePaths.push(fromDraftPath);
|
|
129
|
-
const snapshots = await Promise.all(candidatePaths.map((candidatePath) => readRollbackDraftSnapshot(cwd, candidatePath)));
|
|
130
|
-
const uniqueSnapshots = new Map();
|
|
131
|
-
for (const snapshot of snapshots) {
|
|
132
|
-
if (!snapshot)
|
|
133
|
-
continue;
|
|
134
|
-
uniqueSnapshots.set(snapshot.relativePath, snapshot);
|
|
135
|
-
}
|
|
136
|
-
return [...uniqueSnapshots.values()];
|
|
137
|
-
}
|
|
138
|
-
export async function restoreRollbackDraftSnapshots(cwd, snapshots) {
|
|
139
|
-
for (const snapshot of snapshots) {
|
|
140
|
-
const absolutePath = path.join(cwd, snapshot.relativePath);
|
|
141
|
-
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
|
142
|
-
await fs.writeFile(absolutePath, snapshot.content);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
export async function handleBindFailure(cwd, error, keiyakuContent, title, fromDraft) {
|
|
146
|
-
let savedDraft = false;
|
|
147
|
-
let retryHints = [];
|
|
148
|
-
let draftSaveFailed;
|
|
149
|
-
if (keiyakuContent && !fromDraft) {
|
|
150
|
-
try {
|
|
151
|
-
const draftPath = await writeDraftArtifact({
|
|
152
|
-
cwd,
|
|
153
|
-
title,
|
|
154
|
-
content: keiyakuContent,
|
|
155
|
-
});
|
|
156
|
-
retryHints = buildBindDraftRecoveryHints(draftPath);
|
|
157
|
-
logInfo(`[keiyaku] Bind failed. Draft saved to ${draftPath}.`, { cwd, section: "script", progressOnly: true });
|
|
158
|
-
savedDraft = true;
|
|
159
|
-
}
|
|
160
|
-
catch (draftSaveError) {
|
|
161
|
-
const message = draftSaveError instanceof Error ? draftSaveError.message : String(draftSaveError);
|
|
162
|
-
draftSaveFailed = message;
|
|
163
|
-
logInfo(`[keiyaku] Failed to save draft file: ${message}`, { cwd, section: "script", progressOnly: true });
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
167
|
-
throw wrapFlowError("bind keiyaku", error);
|
|
168
|
-
}
|
|
169
|
-
if (savedDraft) {
|
|
170
|
-
if (isFlowError(error)) {
|
|
171
|
-
throw wrapFlowError("bind keiyaku", new FlowError(error.code, error.message, {
|
|
172
|
-
cause: error,
|
|
173
|
-
hints: retryHints,
|
|
174
|
-
}));
|
|
175
|
-
}
|
|
176
|
-
if (error instanceof Error) {
|
|
177
|
-
throw wrapFlowError("bind keiyaku", new FlowError("INTERNAL_STATE", error.message, { cause: error, hints: retryHints }));
|
|
178
|
-
}
|
|
179
|
-
throw wrapFlowError("bind keiyaku", new FlowError("INTERNAL_STATE", String(error), { hints: retryHints }));
|
|
180
|
-
}
|
|
181
|
-
if (draftSaveFailed) {
|
|
182
|
-
const draftSaveHint = buildBindDraftSaveFailureHint(draftSaveFailed);
|
|
183
|
-
if (isFlowError(error)) {
|
|
184
|
-
throw wrapFlowError("bind keiyaku", new FlowError(error.code, error.message, {
|
|
185
|
-
cause: error,
|
|
186
|
-
hints: [draftSaveHint],
|
|
187
|
-
}));
|
|
188
|
-
}
|
|
189
|
-
if (error instanceof Error) {
|
|
190
|
-
throw wrapFlowError("bind keiyaku", new FlowError("INTERNAL_STATE", error.message, { cause: error, hints: [draftSaveHint] }));
|
|
191
|
-
}
|
|
192
|
-
throw wrapFlowError("bind keiyaku", new FlowError("INTERNAL_STATE", String(error), { hints: [draftSaveHint] }));
|
|
193
|
-
}
|
|
194
|
-
throw wrapFlowError("bind keiyaku", error);
|
|
195
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs/promises";
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
import { DRAFT_ARTIFACT_NOTICE, KEIYAKU_DRAFT_DIR } from "../keiyaku.js";
|
|
4
|
-
import { isErrnoException } from "../errno.js";
|
|
5
|
-
const DRAFT_SLUG_FALLBACK = "keiyaku-draft";
|
|
6
|
-
function normalizeDraftRelativePath(relativePath) {
|
|
7
|
-
return relativePath.split(path.sep).join("/");
|
|
8
|
-
}
|
|
9
|
-
function formatDraftTimestamp(now) {
|
|
10
|
-
const year = String(now.getFullYear());
|
|
11
|
-
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
12
|
-
const day = String(now.getDate()).padStart(2, "0");
|
|
13
|
-
const hour = String(now.getHours()).padStart(2, "0");
|
|
14
|
-
const minute = String(now.getMinutes()).padStart(2, "0");
|
|
15
|
-
const second = String(now.getSeconds()).padStart(2, "0");
|
|
16
|
-
return `${year}${month}${day}-${hour}${minute}${second}`;
|
|
17
|
-
}
|
|
18
|
-
function toDraftSlug(title) {
|
|
19
|
-
const normalized = title
|
|
20
|
-
.trim()
|
|
21
|
-
.toLowerCase()
|
|
22
|
-
.replace(/[^\p{Script=Han}a-z0-9]+/gu, "-")
|
|
23
|
-
.replace(/-+/g, "-")
|
|
24
|
-
.replace(/^-+/, "")
|
|
25
|
-
.replace(/-+$/, "");
|
|
26
|
-
return normalized || DRAFT_SLUG_FALLBACK;
|
|
27
|
-
}
|
|
28
|
-
export function isDraftArtifactPath(filePath) {
|
|
29
|
-
const normalized = normalizeDraftRelativePath(filePath).replace(/^\.\/+/, "");
|
|
30
|
-
const draftPrefix = `${normalizeDraftRelativePath(KEIYAKU_DRAFT_DIR)}/`;
|
|
31
|
-
return normalized === normalizeDraftRelativePath(KEIYAKU_DRAFT_DIR) || normalized.startsWith(draftPrefix);
|
|
32
|
-
}
|
|
33
|
-
export function buildDraftArtifactRelativePath(title, now = new Date()) {
|
|
34
|
-
const filename = `${formatDraftTimestamp(now)}_${toDraftSlug(title)}.md`;
|
|
35
|
-
return normalizeDraftRelativePath(path.join(KEIYAKU_DRAFT_DIR, filename));
|
|
36
|
-
}
|
|
37
|
-
function buildDraftArtifactMarkdown(content, now) {
|
|
38
|
-
return [
|
|
39
|
-
"---",
|
|
40
|
-
"generated_by: keiyaku",
|
|
41
|
-
"type: draft",
|
|
42
|
-
`time: '${now.toISOString()}'`,
|
|
43
|
-
`notice: '${DRAFT_ARTIFACT_NOTICE}'`,
|
|
44
|
-
"---",
|
|
45
|
-
"",
|
|
46
|
-
content,
|
|
47
|
-
].join("\n");
|
|
48
|
-
}
|
|
49
|
-
export async function writeDraftArtifact(input) {
|
|
50
|
-
const now = input.now ?? new Date();
|
|
51
|
-
const relativePath = buildDraftArtifactRelativePath(input.title, now);
|
|
52
|
-
const absolutePath = path.join(input.cwd, relativePath);
|
|
53
|
-
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
|
54
|
-
await fs.writeFile(absolutePath, buildDraftArtifactMarkdown(input.content, now), "utf-8");
|
|
55
|
-
return relativePath;
|
|
56
|
-
}
|
|
57
|
-
export async function listDraftArtifacts(cwd) {
|
|
58
|
-
const draftDirPath = path.join(cwd, KEIYAKU_DRAFT_DIR);
|
|
59
|
-
try {
|
|
60
|
-
const entries = await fs.readdir(draftDirPath, { withFileTypes: true });
|
|
61
|
-
return entries
|
|
62
|
-
.filter((entry) => entry.isFile())
|
|
63
|
-
.map((entry) => normalizeDraftRelativePath(path.join(KEIYAKU_DRAFT_DIR, entry.name)))
|
|
64
|
-
.sort((a, b) => b.localeCompare(a));
|
|
65
|
-
}
|
|
66
|
-
catch (error) {
|
|
67
|
-
if (isErrnoException(error) && error.code === "ENOENT") {
|
|
68
|
-
return [];
|
|
69
|
-
}
|
|
70
|
-
throw error;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
export async function readLatestDraftArtifact(cwd) {
|
|
74
|
-
const [latestPath] = await listDraftArtifacts(cwd);
|
|
75
|
-
if (!latestPath)
|
|
76
|
-
return null;
|
|
77
|
-
const content = await fs.readFile(path.join(cwd, latestPath), "utf-8");
|
|
78
|
-
return { path: latestPath, content };
|
|
79
|
-
}
|
|
80
|
-
export async function readDraftSnapshot(cwd) {
|
|
81
|
-
const draftPaths = await listDraftArtifacts(cwd);
|
|
82
|
-
if (draftPaths.length === 0)
|
|
83
|
-
return null;
|
|
84
|
-
const files = await Promise.all(draftPaths.map(async (draftPath) => ({
|
|
85
|
-
path: draftPath,
|
|
86
|
-
content: await fs.readFile(path.join(cwd, draftPath)),
|
|
87
|
-
})));
|
|
88
|
-
return {
|
|
89
|
-
path: draftPaths[0],
|
|
90
|
-
files,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
export async function restoreDraftSnapshot(cwd, snapshot) {
|
|
94
|
-
for (const file of snapshot.files) {
|
|
95
|
-
const absolutePath = path.join(cwd, file.path);
|
|
96
|
-
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
|
97
|
-
await fs.writeFile(absolutePath, file.content);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs/promises";
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
import { KEIYAKU_FILE } from "../keiyaku.js";
|
|
4
|
-
import { isErrnoException } from "../errno.js";
|
|
5
|
-
import { FlowError } from "../flow-error.js";
|
|
6
|
-
async function isMissingFile(filePath) {
|
|
7
|
-
try {
|
|
8
|
-
await fs.access(filePath);
|
|
9
|
-
return false;
|
|
10
|
-
}
|
|
11
|
-
catch (error) {
|
|
12
|
-
if (isErrnoException(error) && error.code === "ENOENT") {
|
|
13
|
-
return true;
|
|
14
|
-
}
|
|
15
|
-
throw error;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
export async function assertKeiyakuProtocolFiles(cwd) {
|
|
19
|
-
const keiyakuPath = path.join(cwd, KEIYAKU_FILE);
|
|
20
|
-
const hasKeiyaku = !(await isMissingFile(keiyakuPath));
|
|
21
|
-
if (!hasKeiyaku) {
|
|
22
|
-
throw new FlowError("MISSING_PROTOCOL_FILES", `missing protocol file for current keiyaku (${KEIYAKU_FILE})`);
|
|
23
|
-
}
|
|
24
|
-
}
|