@astrosheep/keiyaku 2.9.2 → 2.9.4
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/call-terms.js +13 -94
- package/build/agents/harness/activity-values.js +16 -0
- package/build/agents/harness/event-persistence.js +218 -0
- package/build/agents/harness/execution-handle.js +25 -222
- package/build/agents/harness/index.js +3 -1
- package/build/agents/harness/outcome.js +47 -4
- package/build/agents/harness/projection.js +42 -59
- package/build/agents/harness/provider-adapter.js +58 -0
- package/build/agents/harness/router.js +11 -187
- package/build/agents/harness/runtime.js +17 -4
- package/build/agents/harness/session.js +15 -0
- package/build/agents/launch-snapshot/model.js +15 -236
- package/build/agents/launch-snapshot/parse-helpers.js +114 -0
- package/build/agents/launch-snapshot/resolve.js +17 -43
- package/build/agents/opencode-sdk.js +45 -12
- package/build/agents/providers/claude-agent-sdk/adapter.js +6 -17
- package/build/agents/providers/claude-agent-sdk/registration.js +214 -0
- package/build/agents/providers/claude-agent-sdk/session-schema.js +2 -0
- package/build/agents/providers/codex-app-server/adapter.js +50 -53
- package/build/agents/providers/codex-app-server/events.js +67 -11
- package/build/agents/providers/codex-app-server/identity.js +1 -0
- package/build/agents/providers/codex-app-server/policy.js +29 -0
- package/build/agents/providers/codex-app-server/registration.js +248 -0
- package/build/agents/providers/codex-app-server/session-schema.js +6 -0
- package/build/agents/providers/codex-app-server/session.js +23 -17
- package/build/agents/providers/codex-app-server/terms.js +1 -0
- package/build/agents/providers/opencode-sdk/adapter.js +94 -50
- package/build/agents/providers/opencode-sdk/events.js +8 -4
- package/build/agents/providers/opencode-sdk/registration.js +135 -0
- package/build/agents/providers/opencode-sdk/session-schema.js +2 -0
- package/build/agents/providers/opencode-sdk/session.js +21 -14
- package/build/agents/providers/opencode-sdk/terms.js +1 -0
- package/build/agents/providers/pi/adapter.js +36 -30
- package/build/agents/providers/pi/registration.js +145 -0
- package/build/agents/providers/pi/session-schema.js +6 -0
- package/build/agents/providers/pi/terms.js +1 -0
- package/build/agents/providers/registration.js +7 -0
- package/build/agents/providers/registry.js +76 -0
- package/build/agents/selector.js +2 -29
- package/build/agents/types.js +8 -0
- package/build/artifact-directories.js +22 -0
- package/build/cli/commands/akuma/akuma/handler.js +1 -1
- package/build/cli/commands/akuma/view/handler.js +5 -4
- package/build/cli/commands/akuma.js +6 -0
- package/build/cli/commands/contract/amend/handler.js +8 -2
- package/build/cli/commands/contract/arc/handler.js +7 -1
- package/build/cli/commands/contract/bind/handler.js +4 -3
- package/build/cli/commands/contract/bind/meta.js +34 -7
- package/build/cli/commands/contract/forfeit/handler.js +4 -4
- package/build/cli/commands/contract/log/handler.js +3 -3
- package/build/cli/commands/contract/petition/handler.js +4 -6
- package/build/cli/commands/contract/renew/handler.js +20 -6
- package/build/cli/commands/contract/renew/meta.js +5 -4
- package/build/cli/commands/metadata.js +58 -0
- package/build/cli/commands/projection/call/handler.js +6 -5
- package/build/cli/commands/projection/kill/handler.js +16 -32
- package/build/cli/commands/projection/shared.js +28 -0
- package/build/cli/commands/projection/status/handler.js +1 -1
- package/build/cli/commands/projection/tell/handler.js +14 -30
- package/build/cli/commands/projection/wait/handler.js +45 -22
- package/build/cli/commands/projection/wait/meta.js +7 -2
- package/build/cli/commands/registry.js +2 -54
- package/build/cli/commands/shared.js +10 -7
- package/build/cli/commands/skills/install/handler.js +2 -5
- package/build/cli/commands/skills/skills/handler.js +1 -1
- package/build/cli/commands/task/add/handler.js +5 -13
- package/build/cli/commands/task/catalog.js +2 -2
- package/build/cli/commands/task/done/handler.js +4 -8
- package/build/cli/commands/task/drop/handler.js +3 -6
- package/build/cli/commands/task/ls/handler.js +3 -5
- package/build/cli/commands/task/shared.js +14 -4
- package/build/cli/commands/task/{view → show}/handler.js +7 -8
- package/build/cli/commands/task/show/meta.js +8 -0
- package/build/cli/commands/task/start/handler.js +3 -6
- package/build/cli/commands/task/stop/handler.js +4 -8
- package/build/cli/commands/task/task/handler.js +1 -1
- package/build/cli/commands/task/update/handler.js +4 -8
- package/build/cli/completion.js +1 -1
- package/build/cli/flags.js +19 -3
- package/build/cli/help.js +4 -11
- package/build/cli/index.js +57 -83
- package/build/cli/missing-subcommand.js +7 -0
- package/build/cli/parse-flags.js +14 -5
- package/build/cli/parse-metadata.js +3 -3
- package/build/cli/parse-selectors.js +1 -1
- package/build/cli/parse.js +21 -0
- package/build/cli/projection-address.js +7 -12
- package/build/cli/render/address.js +41 -37
- package/build/cli/render/arc.js +24 -12
- package/build/cli/render/call.js +11 -5
- package/build/cli/render/errors.js +36 -1
- package/build/cli/render/format.js +4 -0
- package/build/cli/render/kanshi.js +6 -12
- package/build/cli/render/kill.js +9 -0
- package/build/cli/render/misc.js +55 -12
- package/build/cli/render/petition.js +20 -15
- package/build/cli/render/projection-activity.js +3 -0
- package/build/cli/render/shared.js +60 -21
- package/build/cli/render/skills.js +10 -0
- package/build/cli/render/status.js +37 -14
- package/build/cli/render/success-response.js +77 -0
- package/build/cli/render/task.js +54 -14
- package/build/cli/render/tell.js +5 -0
- package/build/cli/render/terminal-failure.js +58 -0
- package/build/cli/render/types.js +1 -0
- package/build/cli/render/wait.js +38 -36
- package/build/cli/types.js +1 -1
- package/build/config/akuma-loader.js +0 -5
- package/build/config/env-keys.js +1 -1
- package/build/config/provider-policy-ownership.js +3 -32
- package/build/config/settings/disease.js +8 -1
- package/build/config/settings/loader.js +11 -3
- package/build/config/settings/provider-instance-fields.js +51 -0
- package/build/config/settings/schema.js +6 -176
- package/build/core/addressing.js +66 -16
- package/build/core/amend.js +135 -18
- package/build/core/arc.js +19 -67
- package/build/core/bind.js +82 -35
- package/build/core/call/call.js +15 -9
- package/build/core/call/context.js +6 -51
- package/build/core/call/execution.js +21 -22
- package/build/core/call/prompt.js +14 -1
- package/build/core/command-markdown.js +45 -0
- package/build/core/context.js +13 -7
- package/build/core/contract-view.js +20 -3
- package/build/core/draft.js +123 -16
- package/build/core/forfeit.js +5 -2
- package/build/core/hints.js +1 -75
- package/build/core/identifier-slug.js +66 -0
- package/build/core/ids.js +25 -11
- package/build/core/ledger.js +20 -68
- package/build/core/markdown/parser.js +0 -68
- package/build/core/markdown/render.js +68 -0
- package/build/core/markdown/sections.js +2 -1
- package/build/core/outcome-base.js +4 -0
- package/build/core/projection/generation/database.js +2 -38
- package/build/core/projection/generation/model.js +42 -1
- package/build/core/{projection-generation-continuation.js → projection/generation/projection-generation-continuation.js} +3 -3
- package/build/core/{projection-generation-execution.js → projection/generation/projection-generation-execution.js} +19 -49
- package/build/core/{projection-generation-identity.js → projection/generation/projection-generation-identity.js} +2 -2
- package/build/core/{projection-generation-launcher.js → projection/generation/projection-generation-launcher.js} +5 -3
- package/build/core/{projection-generation-process.js → projection/generation/projection-generation-process.js} +2 -1
- package/build/core/{projection-generation-runner.js → projection/generation/projection-generation-runner.js} +18 -3
- package/build/core/projection/generation/store.js +2 -2
- package/build/core/projection/generation/transitions.js +1 -1
- package/build/core/projection/index.js +27 -0
- package/build/core/{projection-activity.js → projection/projection-activity.js} +12 -0
- package/build/core/{projection-alias.js → projection/projection-alias.js} +14 -19
- package/build/core/projection/projection-coordinate.js +61 -0
- package/build/core/{projection-core.js → projection/projection-core.js} +9 -9
- package/build/core/{projection-execution-observer.js → projection/projection-execution-observer.js} +2 -57
- package/build/core/{projection-identity.js → projection/projection-identity.js} +1 -1
- package/build/core/{projection-kill.js → projection/projection-kill.js} +1 -1
- package/build/core/{projection-life-observer.js → projection/projection-life-observer.js} +1 -1
- package/build/core/{projection-mint.js → projection/projection-mint.js} +10 -10
- package/build/core/{projection-status.js → projection/projection-status.js} +98 -25
- package/build/core/projection/projection-terminal-failure.js +104 -0
- package/build/core/projection/projection-wait.js +278 -0
- package/build/core/projection/projection-wake.js +203 -0
- package/build/core/provider-loader-ports.js +4 -0
- package/build/core/ref-log.js +65 -40
- package/build/core/registry.js +63 -10
- package/build/core/renew-session.js +128 -0
- package/build/core/renew.js +426 -99
- package/build/core/scope.js +143 -66
- package/build/core/seal.js +139 -35
- package/build/core/{claim-delivery.js → settlement/claim-delivery.js} +9 -14
- package/build/core/{claim.js → settlement/claim.js} +32 -8
- package/build/core/settlement/index.js +19 -0
- package/build/core/{petition-claim-gates.js → settlement/petition-claim-gates.js} +22 -18
- package/build/core/{petition-forfeit.js → settlement/petition-forfeit.js} +2 -2
- package/build/core/{petition-head-guard.js → settlement/petition-head-guard.js} +2 -2
- package/build/core/settlement/petition-io.js +1 -0
- package/build/core/{petition-preview.js → settlement/petition-preview.js} +1 -1
- package/build/core/{petition.js → settlement/petition.js} +12 -10
- package/build/core/settlement/queue-read-model.js +143 -0
- package/build/core/settlement/queue-seat-allocation.js +156 -0
- package/build/core/settlement/queue.js +6 -0
- package/build/core/settlement/settlement.js +295 -0
- package/build/core/{verdict.js → settlement/verdict.js} +9 -37
- package/build/core/status/board.js +24 -23
- package/build/core/status/ledger-batch.js +158 -0
- package/build/core/status/reconciliation.js +10 -4
- package/build/core/stored-agent-event.js +3 -0
- package/build/core/task/board.js +40 -4
- package/build/core/task/commands.js +30 -21
- package/build/core/task/index.js +12 -0
- package/build/core/task/settlement-git.js +157 -65
- package/build/core/task/settlement-policy.js +54 -24
- package/build/core/task/task-contract.js +370 -0
- package/build/core/{task-doctor.js → task/task-doctor.js} +1 -1
- package/build/core/{task-git-actor.js → task/task-git-actor.js} +5 -10
- package/build/core/{task-git-runtime.js → task/task-git-runtime.js} +3 -3
- package/build/core/{task-git-store.js → task/task-git-store.js} +92 -29
- package/build/core/task/task-repository.js +1 -0
- package/build/core/{task-store-repository.js → task/task-store-repository.js} +5 -4
- package/build/core/task/task-store.js +1 -0
- package/build/core/{task-worktree.js → task/task-worktree.js} +2 -1
- package/build/core/task/task.js +5 -0
- package/build/core/task/tree.js +37 -0
- package/build/core/task/validation.js +5 -2
- package/build/core/transcripts.js +15 -3
- package/build/core/worktree-path.js +43 -28
- package/build/flow-error.js +85 -39
- package/build/fs/durability.js +27 -0
- package/build/fs/path-coordinate.js +16 -0
- package/build/generated/version.js +3 -2
- package/build/git/branches.js +2 -1
- package/build/git/core.js +106 -2
- package/build/git/path-coordinate.js +17 -0
- package/build/git/refs.js +57 -0
- package/build/git/streaming-batch.js +301 -0
- package/build/git/worktree.js +65 -0
- package/build/index.js +3 -3
- package/build/keiyaku.js +2 -36
- package/package.json +4 -10
- package/skills/keiyaku/SKILL.md +3 -2
- package/skills/keiyaku-akuma/SKILL.md +10 -6
- package/skills/keiyaku-task/SKILL.md +1 -1
- package/skills/keiyaku-workflow/SKILL.md +25 -4
- package/build/agents/providers/codex-sdk.js +0 -329
- package/build/cli/commands/task/view/meta.js +0 -8
- package/build/core/command-io.js +0 -174
- package/build/core/path-coordinate.js +0 -27
- package/build/core/petition-claim.js +0 -117
- package/build/core/petition-run.js +0 -83
- package/build/core/projection-coordinate.js +0 -63
- package/build/core/projection-wait.js +0 -165
- package/build/core/projection-wake.js +0 -136
- package/build/core/queue.js +0 -343
- package/build/core/task-contract.js +0 -237
- package/build/core/task.js +0 -4
- /package/build/{core/task-repository.js → agents/launch-snapshot/types.js} +0 -0
- /package/build/{core/task-store.js → agents/providers/claude-agent-sdk/terms.js} +0 -0
- /package/build/core/{projection-generation-runtime.js → projection/generation/projection-generation-runtime.js} +0 -0
- /package/build/core/{projection-life-protocol.js → projection/projection-life-protocol.js} +0 -0
- /package/build/core/{projection-runner-lock.js → projection/projection-runner-lock.js} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FlowError } from "
|
|
1
|
+
import { FlowError } from "../../flow-error.js";
|
|
2
2
|
function documentFacts(task) {
|
|
3
3
|
return JSON.stringify({
|
|
4
4
|
id: task.id,
|
|
@@ -41,6 +41,7 @@ export function withTaskWorktreePolicy(repository, contractId) {
|
|
|
41
41
|
const mutation = await apply(before);
|
|
42
42
|
return {
|
|
43
43
|
replacements: mutation.replacements,
|
|
44
|
+
...(mutation.subject === undefined ? {} : { subject: mutation.subject }),
|
|
44
45
|
select(after) {
|
|
45
46
|
validateTaskWorktreeMutation(before, mutation.replacements, after, contractId);
|
|
46
47
|
return mutation.select(after);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { parseTaskDocument, serializeTaskDocument, } from "./document.js";
|
|
2
|
+
export { resolveTaskReadId, } from "./board.js";
|
|
3
|
+
export { buildTaskBoard } from "./source-board.js";
|
|
4
|
+
export { buildTaskTree } from "./tree.js";
|
|
5
|
+
export { readTaskBoard, createTask, updateTask, startTask, stopTask, doneTask, dropTask, selectSoleInProgress, rollbackOrphanTaskPointers, } from "./commands.js";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
function compareText(left, right) {
|
|
2
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
3
|
+
}
|
|
4
|
+
/** Board task order is the deterministic order for every parent child list. */
|
|
5
|
+
function compareBoardOrder(left, right) {
|
|
6
|
+
const leftTerminal = left.state === "done" || left.state === "drop" ? 1 : 0;
|
|
7
|
+
const rightTerminal = right.state === "done" || right.state === "drop" ? 1 : 0;
|
|
8
|
+
return leftTerminal - rightTerminal
|
|
9
|
+
|| left.pri - right.pri
|
|
10
|
+
|| compareText(left.createdAt, right.createdAt)
|
|
11
|
+
|| compareText(left.id, right.id);
|
|
12
|
+
}
|
|
13
|
+
function diagnosticOrder(left, right) {
|
|
14
|
+
return compareText(left.code, right.code)
|
|
15
|
+
|| compareText(left.id ?? "", right.id ?? "")
|
|
16
|
+
|| compareText(left.message, right.message);
|
|
17
|
+
}
|
|
18
|
+
export function buildTaskTree(board, rootId) {
|
|
19
|
+
const childrenByParent = new Map();
|
|
20
|
+
for (const task of board.tasks) {
|
|
21
|
+
if (task.parent === null)
|
|
22
|
+
continue;
|
|
23
|
+
childrenByParent.set(task.parent, [...(childrenByParent.get(task.parent) ?? []), task]);
|
|
24
|
+
}
|
|
25
|
+
for (const children of childrenByParent.values())
|
|
26
|
+
children.sort(compareBoardOrder);
|
|
27
|
+
const visit = (parentId, ancestors) => (childrenByParent.get(parentId) ?? [])
|
|
28
|
+
.filter((child) => !ancestors.has(child.id))
|
|
29
|
+
.map((child) => ({
|
|
30
|
+
task: child,
|
|
31
|
+
children: visit(child.id, new Set([...ancestors, child.id])),
|
|
32
|
+
}));
|
|
33
|
+
return {
|
|
34
|
+
descendants: visit(rootId, new Set([rootId])),
|
|
35
|
+
diagnostics: [...board.diseases].sort(diagnosticOrder),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FlowError } from "../../flow-error.js";
|
|
2
|
-
|
|
2
|
+
import { isNormalizedIdentifierSlug } from "../identifier-slug.js";
|
|
3
3
|
export const TASK_STATES = new Set(["open", "in_progress", "done", "drop"]);
|
|
4
4
|
const CANONICAL_ISO_UTC_MS = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
5
5
|
export function invalid(message) {
|
|
@@ -13,10 +13,13 @@ export function nonblank(field, value) {
|
|
|
13
13
|
}
|
|
14
14
|
export function fullId(field, value) {
|
|
15
15
|
const id = nonblank(field, value);
|
|
16
|
-
if (!
|
|
16
|
+
if (!isTaskId(id))
|
|
17
17
|
invalid(`task field '${field}' must be a canonical full task id`);
|
|
18
18
|
return id;
|
|
19
19
|
}
|
|
20
|
+
export function isTaskId(value) {
|
|
21
|
+
return isNormalizedIdentifierSlug(value);
|
|
22
|
+
}
|
|
20
23
|
export function canonicalTimestamp(field, value) {
|
|
21
24
|
const timestamp = nonblank(field, value);
|
|
22
25
|
if (!CANONICAL_ISO_UTC_MS.test(timestamp)) {
|
|
@@ -21,6 +21,15 @@ const RESPONSE_HISTORY_SESSION_KEY = "session";
|
|
|
21
21
|
const RESPONSE_HISTORY_LAUNCH_SNAPSHOT_KEY = "launch_snapshot";
|
|
22
22
|
const RESPONSE_HISTORY_PROVENANCE_KEY = "provenance";
|
|
23
23
|
const REVIEW_HISTORY_FILE_RE = /^\d{8}-\d{9}_REVIEW_(\d+)\.md$/;
|
|
24
|
+
/** Artifact-owner rendering of seal receipt/diffstat facts into the durable Diff section. */
|
|
25
|
+
export function renderSealDiffSection(sealed, emptyLabel) {
|
|
26
|
+
if (!sealed)
|
|
27
|
+
return emptyLabel;
|
|
28
|
+
if (sealed.receipts.length === 0)
|
|
29
|
+
return "nothing sealed";
|
|
30
|
+
const count = sealed.receipts.length;
|
|
31
|
+
return `${count} receipt${count === 1 ? "" : "s"} · +${sealed.diffstat.insertions} −${sealed.diffstat.deletions}`;
|
|
32
|
+
}
|
|
24
33
|
export const RESPONSE_ARTIFACT_HEADINGS = {
|
|
25
34
|
call: "# Call",
|
|
26
35
|
bind: "# Bind",
|
|
@@ -100,9 +109,12 @@ function buildMarkdown(input, iso, provenance) {
|
|
|
100
109
|
return [...frontmatter, "# Call", "", "## Task", section(input.task), "", "## Context", section(input.context), "", "## Response", section(input.response), ""].join("\n");
|
|
101
110
|
if (input.tool === "review")
|
|
102
111
|
return [...frontmatter, "# Review", "", "## Prompt", section(input.prompt), "", "## Response", section(input.response), ""].join("\n");
|
|
103
|
-
if (input.tool === "arc")
|
|
104
|
-
|
|
105
|
-
|
|
112
|
+
if (input.tool === "arc") {
|
|
113
|
+
const diff = renderSealDiffSection(input.sealed, input.emptyDiffLabel);
|
|
114
|
+
return [...frontmatter, "# Arc", "", "## Objective", section(input.objective), "", "## Brief", section(input.brief), "", "## Ledger Commit", section(input.ledgerCommit), "", "## Summary", section(input.summary), "", "## Diff", section(diff), ""].join("\n");
|
|
115
|
+
}
|
|
116
|
+
const diff = renderSealDiffSection(input.sealed, input.emptyDiffLabel);
|
|
117
|
+
return [...frontmatter, "# Renew", "", "## Base", `${input.oldBase} -> ${input.newBase}`, "", "## Head", `${input.oldHead} -> ${input.newHead}`, "", "## Ledger Commit", section(input.ledgerCommit), "", "## Summary", section(input.summary), "", "## Diff", section(diff), ""].join("\n");
|
|
106
118
|
}
|
|
107
119
|
/** The sole response artifact writer. */
|
|
108
120
|
export async function persistResponseHistory(input) {
|
|
@@ -3,9 +3,9 @@ import * as fs from "node:fs/promises";
|
|
|
3
3
|
import { FlowError } from "../flow-error.js";
|
|
4
4
|
import { createGit, wrapGitError } from "../git/core.js";
|
|
5
5
|
import { readLedger } from "./ledger.js";
|
|
6
|
-
import { listContractIds } from "./ledger.js";
|
|
7
6
|
import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
|
|
8
|
-
import {
|
|
7
|
+
import { normalizeFilesystemCoordinate } from "../fs/path-coordinate.js";
|
|
8
|
+
import { gitCommonRootPath, resolveGitCommonRoot } from "../git/path-coordinate.js";
|
|
9
9
|
export function findBindEntry(entries) {
|
|
10
10
|
return entries.find((entry) => entry.kind === "bind");
|
|
11
11
|
}
|
|
@@ -36,8 +36,10 @@ async function listWorktrees(cwd) {
|
|
|
36
36
|
throw wrapGitError("worktree list --porcelain -z", error, cwd);
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
function
|
|
40
|
-
const
|
|
39
|
+
export function worktreeContainsCoordinate(root, candidate) {
|
|
40
|
+
const normalizedRoot = normalizeFilesystemCoordinate(root);
|
|
41
|
+
const normalizedCandidate = normalizeFilesystemCoordinate(candidate);
|
|
42
|
+
const relative = path.relative(normalizedRoot, normalizedCandidate);
|
|
41
43
|
return relative === "" || (!path.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path.sep}`));
|
|
42
44
|
}
|
|
43
45
|
export async function stableRepoRoot(cwd) {
|
|
@@ -100,6 +102,37 @@ async function findContractWorktree(cwd, contractId) {
|
|
|
100
102
|
export async function findContractWorktreePath(cwd, contractId) {
|
|
101
103
|
return (await findContractWorktree(cwd, contractId)).path;
|
|
102
104
|
}
|
|
105
|
+
export async function findRegisteredContractWorktreePath(cwd, contractId) {
|
|
106
|
+
const stableRoot = await stableRepoRoot(cwd);
|
|
107
|
+
const ledger = await readLedger(stableRoot, contractId);
|
|
108
|
+
const bind = ledger ? findBindEntry(ledger.entries) : undefined;
|
|
109
|
+
if (!bind || bind.data.workspace !== "worktree")
|
|
110
|
+
return undefined;
|
|
111
|
+
let expected;
|
|
112
|
+
try {
|
|
113
|
+
expected = await fs.realpath(await contractWorktreePathFromBind(stableRoot, contractId, bind));
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
for (const worktree of await listWorktrees(stableRoot)) {
|
|
119
|
+
try {
|
|
120
|
+
if (await fs.realpath(worktree.path) === expected)
|
|
121
|
+
return worktree.path;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// A stale Git registration is not an engine-owned worktree fact.
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Delivery coordinate for dirty admission and KEIYAKU.md render: the linked
|
|
131
|
+
* contract worktree when Git registers one, otherwise cwd (workspace here).
|
|
132
|
+
*/
|
|
133
|
+
export async function resolveContractDeliveryCwd(cwd, contractId) {
|
|
134
|
+
return (await findContractWorktreePath(cwd, contractId)) ?? cwd;
|
|
135
|
+
}
|
|
103
136
|
/**
|
|
104
137
|
* Resolve the contract id for an engine-owned bound worktree containing cwd.
|
|
105
138
|
* Matches the canonical bind place path and worktree workspace only; ledger
|
|
@@ -132,27 +165,23 @@ async function contractIdForEngineOwnedWorktree(cwd, options) {
|
|
|
132
165
|
}
|
|
133
166
|
if (canonicalWorktree !== expectedWorktree)
|
|
134
167
|
continue;
|
|
135
|
-
if (
|
|
168
|
+
if (worktreeContainsCoordinate(canonicalWorktree, canonicalCwd))
|
|
136
169
|
return contractId;
|
|
137
170
|
}
|
|
138
171
|
return undefined;
|
|
139
172
|
}
|
|
140
|
-
/**
|
|
141
|
-
* Resolve the active commission presented by an engine-owned contract
|
|
142
|
-
* worktree containing cwd. Ordinary repository directories and arbitrary
|
|
143
|
-
* worktrees never present a commission address.
|
|
144
|
-
*/
|
|
145
|
-
export async function contractPresentedByWorktree(cwd) {
|
|
146
|
-
return contractIdForEngineOwnedWorktree(cwd, { requireActive: true });
|
|
147
|
-
}
|
|
148
173
|
/**
|
|
149
174
|
* Resolve the contract bound by an engine-owned worktree containing cwd,
|
|
150
175
|
* including when the ledger is already terminal. Task CLI contract context
|
|
151
|
-
* uses this residual binding; call
|
|
176
|
+
* uses this residual binding; call attachment never infers from cwd.
|
|
152
177
|
*/
|
|
153
178
|
export async function contractBoundByEngineWorktree(cwd) {
|
|
154
179
|
return contractIdForEngineOwnedWorktree(cwd, { requireActive: false });
|
|
155
180
|
}
|
|
181
|
+
/** Existing-contract verbs may infer only a verified, active engine binding. */
|
|
182
|
+
export async function activeContractBoundByEngineWorktree(cwd) {
|
|
183
|
+
return contractIdForEngineOwnedWorktree(cwd, { requireActive: true });
|
|
184
|
+
}
|
|
156
185
|
export async function cleanupContractWorkspace(cwd, contractId) {
|
|
157
186
|
const stableRoot = await stableRepoRoot(cwd);
|
|
158
187
|
const git = createGit(stableRoot);
|
|
@@ -197,17 +226,3 @@ export async function cleanupContractWorkspace(cwd, contractId) {
|
|
|
197
226
|
}
|
|
198
227
|
return facts;
|
|
199
228
|
}
|
|
200
|
-
export async function activeContractIdForWorktreeDir(cwd, dirname) {
|
|
201
|
-
const ids = await listContractIds(cwd);
|
|
202
|
-
for (const id of ids) {
|
|
203
|
-
const ledger = await readLedger(cwd, id);
|
|
204
|
-
if (!ledger || isTerminalState(deriveContractState(ledger.entries)))
|
|
205
|
-
continue;
|
|
206
|
-
if (id === dirname)
|
|
207
|
-
return id;
|
|
208
|
-
const place = ledger ? findBindEntry(ledger.entries)?.data.place : undefined;
|
|
209
|
-
if (place === dirname)
|
|
210
|
-
return id;
|
|
211
|
-
}
|
|
212
|
-
return undefined;
|
|
213
|
-
}
|
package/build/flow-error.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ACTOR_IDENTITY, SETTINGS_PATH_SEGMENTS } from "./keiyaku-constants.js";
|
|
2
|
+
import { formatArtifactDirectories, KEIYAKU_ARTIFACT_DIRS } from "./artifact-directories.js";
|
|
2
3
|
const SETTINGS_DISPLAY_PATH = SETTINGS_PATH_SEGMENTS.join("/");
|
|
3
4
|
const DEFAULT_HINT_CONTEXT = {
|
|
4
5
|
bindToolName: "bind",
|
|
@@ -17,69 +18,117 @@ export function renderTemplate(template, values) {
|
|
|
17
18
|
return Object.prototype.hasOwnProperty.call(values, key) ? values[key] : match;
|
|
18
19
|
});
|
|
19
20
|
}
|
|
20
|
-
function
|
|
21
|
+
function formatInvalidSettingsDiseaseHints(diseases) {
|
|
22
|
+
return diseases.map((disease) => {
|
|
23
|
+
const where = disease.knob ? `${disease.source}:${disease.knob}` : `${disease.source}:(root)`;
|
|
24
|
+
return `${where}: ${disease.reason}`;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/** CLI-owned typed hint table. Domain code supplies code + facts only. */
|
|
28
|
+
export function resolveFlowErrorHints(code, context, facts) {
|
|
29
|
+
if (facts?.kind === "invalid_settings") {
|
|
30
|
+
return formatInvalidSettingsDiseaseHints(facts.diseases);
|
|
31
|
+
}
|
|
32
|
+
if (facts?.kind === "verification_failure") {
|
|
33
|
+
return [
|
|
34
|
+
`Fix failures from '${facts.label}', then retry CLAIM.`,
|
|
35
|
+
`stdout:\n${facts.stdout}`,
|
|
36
|
+
`stderr:\n${facts.stderr}`,
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
if (facts?.kind === "dirty_worktree") {
|
|
40
|
+
return [
|
|
41
|
+
`Files under \`.keiyaku/\` are blocking the operation. Add ${formatArtifactDirectories(KEIYAKU_ARTIFACT_DIRS)} to \`.gitignore\` if you do not want those artifacts tracked.`,
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
if (facts?.kind === "loose_commits") {
|
|
45
|
+
return [
|
|
46
|
+
`keiyaku @${facts.contractId} arc — opens a new arc to receipt them, then ${facts.nextVerb}`,
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
if (facts?.kind === "needs_renew") {
|
|
50
|
+
return [`keiyaku @${facts.contractId} renew — renews delivery before petition`];
|
|
51
|
+
}
|
|
52
|
+
if (facts?.kind === "projection_in_flight") {
|
|
53
|
+
return ["keiyaku status — watch for the akuma to land, then renew"];
|
|
54
|
+
}
|
|
55
|
+
if (facts?.kind === "projection_wake_failure") {
|
|
56
|
+
return [
|
|
57
|
+
`keiyaku tell ${facts.projectionId} [--wait DURATION] "retry the saved tell" — retries the same-projection wake; the saved tell remains queued`,
|
|
58
|
+
];
|
|
59
|
+
}
|
|
21
60
|
switch (code) {
|
|
22
61
|
case "NOT_GIT_REPO":
|
|
23
|
-
return "The provided `cwd` is not a git repository.";
|
|
62
|
+
return ["The provided `cwd` is not a git repository."];
|
|
24
63
|
case "GIT_UNAVAILABLE":
|
|
25
|
-
return "Install Git, or use an explicitly bare command that does not require repository authority.";
|
|
64
|
+
return ["Install Git, or use an explicitly bare command that does not require repository authority."];
|
|
26
65
|
case "ACTIVE_KEIYAKU_EXISTS":
|
|
27
|
-
return `Next: open an arc with \`${context.arcToolName}\`, then use \`${context.renewToolName}\` when delivery must follow the current base, or \`${context.closeToolName}\` to settle it
|
|
66
|
+
return [`Next: open an arc with \`${context.arcToolName}\`, then use \`${context.renewToolName}\` when delivery must follow the current base, or \`${context.closeToolName}\` to settle it.`];
|
|
28
67
|
case "CONTRACT_AMBIGUOUS":
|
|
29
|
-
return "Address a specific contract workspace, or use `call --bare` to run without contract attachment.";
|
|
68
|
+
return ["Address a specific contract workspace, or use `call --bare` to run without contract attachment."];
|
|
30
69
|
case "UNKNOWN_COMMAND":
|
|
31
|
-
return "Run `keiyaku --help` to inspect available commands.";
|
|
70
|
+
return ["Run `keiyaku --help` to inspect available commands."];
|
|
32
71
|
case "EMPTY_PARAM":
|
|
33
|
-
return "One or more required parameters are empty.";
|
|
72
|
+
return ["One or more required parameters are empty."];
|
|
34
73
|
case "DIRTY_WORKTREE":
|
|
35
|
-
return "The working tree has uncommitted changes.";
|
|
74
|
+
return ["The working tree has uncommitted changes."];
|
|
36
75
|
case "NOT_ACTIVE_KEIYAKU_BRANCH":
|
|
37
|
-
return "
|
|
76
|
+
return ["keiyaku bind — opens a new contract"];
|
|
38
77
|
case "MISSING_KEIYAKU_BASE":
|
|
39
|
-
return "Current branch is not associated with an active contract ledger.";
|
|
78
|
+
return ["Current branch is not associated with an active contract ledger."];
|
|
40
79
|
case "MISSING_PROTOCOL_FILES":
|
|
41
|
-
return "Required protocol file is missing (`KEIYAKU.md`).";
|
|
80
|
+
return ["Required protocol file is missing (`KEIYAKU.md`)."];
|
|
42
81
|
case "CLOSE_MERGE_CONFLICT":
|
|
43
|
-
return [
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
82
|
+
return [`Open a Keiyaku recovery session with \`keiyaku ${context.renewToolName} --resolve\`.`];
|
|
83
|
+
case "RENEW_COORDINATE_DRIFT":
|
|
84
|
+
return [`Restore the delivery to its ledger-recorded history with Git reflog, then retry \`keiyaku ${context.renewToolName}\`.`];
|
|
85
|
+
case "RENEW_SESSION_OPEN":
|
|
86
|
+
return [`Continue the owned rewrite with \`keiyaku ${context.renewToolName} --continue\`, or restore it with \`keiyaku ${context.renewToolName} --abort\`.`];
|
|
87
|
+
case "RENEW_SESSION_NOT_FOUND":
|
|
88
|
+
return [`No recovery session is open; run \`keiyaku ${context.renewToolName}\` normally.`];
|
|
48
89
|
case "CLOSE_QUALITY_GATE_FAILED":
|
|
49
|
-
return `CLAIM denied: a configured verification command failed. Use \`${context.arcToolName}\` after addressing the failure before petitioning again
|
|
90
|
+
return [`CLAIM denied: a configured verification command failed. Use \`${context.arcToolName}\` after addressing the failure before petitioning again.`];
|
|
50
91
|
case "INVALID_BRANCH_TITLE":
|
|
51
|
-
return "Title cannot be converted to a valid branch token for `keiyaku/<title>`.";
|
|
92
|
+
return ["Title cannot be converted to a valid branch token for `keiyaku/<title>`."];
|
|
52
93
|
case "UNKNOWN_SUBAGENT": {
|
|
53
94
|
const names = context.availableActorNames.join(", ");
|
|
54
|
-
return `Unknown ${context.actorIdentity.toLowerCase()} name. Valid options: ${names}
|
|
95
|
+
return [`Unknown ${context.actorIdentity.toLowerCase()} name. Valid options: ${names}.`];
|
|
55
96
|
}
|
|
56
97
|
case "FROM_DRAFT_NOT_FOUND":
|
|
57
|
-
return "The path in `from_draft` does not exist. `from_draft` must reference an existing file path (absolute or relative to `cwd`).";
|
|
98
|
+
return ["The path in `from_draft` does not exist. `from_draft` must reference an existing file path (absolute or relative to `cwd`)."];
|
|
58
99
|
case "INVALID_KEIYAKU_DRAFT":
|
|
59
|
-
return "Invalid draft/input. Use a `# <title>` heading with non-empty `## Objective` and `## Scope` sections (or the equivalent shape in `from_draft`).";
|
|
100
|
+
return ["Invalid draft/input. Use a `# <title>` heading with non-empty `## Objective` and `## Scope` sections (or the equivalent shape in `from_draft`)."];
|
|
101
|
+
case "BIND_TASK_INCOMPLETE":
|
|
102
|
+
return ["Task supplies name/objective defaults; pipe only the missing sections as partial contract Markdown on stdin."];
|
|
60
103
|
case "KEIYAKU_FILE_EXISTS":
|
|
61
|
-
return `\`${context.bindToolName}\` requires a clean slate. \`KEIYAKU.md\` must be removed before starting
|
|
104
|
+
return [`\`${context.bindToolName}\` requires a clean slate. \`KEIYAKU.md\` must be removed before starting.`];
|
|
62
105
|
case "DETACHED_HEAD":
|
|
63
|
-
return "Repository is in detached HEAD state. Address an active contract workspace before retrying.";
|
|
106
|
+
return ["Repository is in detached HEAD state. Address an active contract workspace before retrying."];
|
|
64
107
|
case "INVALID_RESPONSE_PATH":
|
|
65
|
-
return "`keiyaku revive ARTIFACT_ID` needs a valid previous Keiyaku response artifact that includes complete provenance plus `session` and `subagent_name`.";
|
|
108
|
+
return ["`keiyaku revive ARTIFACT_ID` needs a valid previous Keiyaku response artifact that includes complete provenance plus `session` and `subagent_name`."];
|
|
66
109
|
case "INVALID_CLOSE_INTENT":
|
|
67
|
-
return "Invalid close intent. Valid values are `CLAIM` and `FORFEIT`.";
|
|
110
|
+
return ["Invalid close intent. Valid values are `CLAIM` and `FORFEIT`."];
|
|
68
111
|
case "INVALID_SETTINGS":
|
|
69
|
-
return `Fix invalid configuration in \`${SETTINGS_DISPLAY_PATH}\` and retry
|
|
112
|
+
return [`Fix invalid configuration in \`${SETTINGS_DISPLAY_PATH}\` and retry.`];
|
|
70
113
|
case "INVALID_EFFORT_OVERRIDE":
|
|
71
|
-
return "Choose an effort value declared by the selected Akuma's effortOptions.";
|
|
114
|
+
return ["Choose an effort value declared by the selected Akuma's effortOptions."];
|
|
72
115
|
case "CONCURRENT_MODIFICATION":
|
|
73
|
-
return `Concurrent repository changes were detected during \`${context.arcToolName}\` or \`${context.renewToolName}\`. Retry on the latest branch state
|
|
116
|
+
return [`Concurrent repository changes were detected during \`${context.arcToolName}\` or \`${context.renewToolName}\`. Retry on the latest branch state.`];
|
|
117
|
+
case "TASK_LOCK_ADMISSION_TIMEOUT":
|
|
118
|
+
return ["Task coordination did not become available before the bounded deadline. Retry the command; repeated timeouts indicate a stuck or damaged repository lock."];
|
|
74
119
|
case "INTERNAL_STATE":
|
|
75
|
-
return "An internal state error occurred. Please verify your repository status.";
|
|
120
|
+
return ["An internal state error occurred. Please verify your repository status."];
|
|
121
|
+
case "SCOPE_VIOLATION":
|
|
122
|
+
return ["Files outside the contract's declared scope were modified. Widen the scope (amend), waive the violations, or move the changes back."];
|
|
123
|
+
case "NO_OPEN_ARC":
|
|
124
|
+
return ["Open an arc to receipt delivery commits after the fence, then retry."];
|
|
76
125
|
case "REVIEW_REJECTED":
|
|
77
|
-
return `CLAIM denied by the reviewer. Address the findings and retry \`${context.arcToolName}\` before petitioning again
|
|
126
|
+
return [`CLAIM denied by the reviewer. Address the findings and retry \`${context.arcToolName}\` before petitioning again.`];
|
|
78
127
|
case "VERDICT_DENIED":
|
|
79
128
|
case "OATH_MISSING":
|
|
80
|
-
return DEFAULT_ERROR_HINT;
|
|
129
|
+
return [DEFAULT_ERROR_HINT];
|
|
81
130
|
default:
|
|
82
|
-
return DEFAULT_ERROR_HINT;
|
|
131
|
+
return [DEFAULT_ERROR_HINT];
|
|
83
132
|
}
|
|
84
133
|
}
|
|
85
134
|
function getDefaultErrorHint() {
|
|
@@ -87,14 +136,14 @@ function getDefaultErrorHint() {
|
|
|
87
136
|
}
|
|
88
137
|
export class FlowError extends Error {
|
|
89
138
|
code;
|
|
90
|
-
hints;
|
|
91
139
|
review;
|
|
140
|
+
facts;
|
|
92
141
|
constructor(code, message, options) {
|
|
93
142
|
super(message, options?.cause === undefined ? undefined : { cause: options.cause });
|
|
94
143
|
this.name = "FlowError";
|
|
95
144
|
this.code = code;
|
|
96
|
-
this.hints = options?.hints;
|
|
97
145
|
this.review = options?.review;
|
|
146
|
+
this.facts = options?.facts;
|
|
98
147
|
}
|
|
99
148
|
}
|
|
100
149
|
export function isFlowError(err) {
|
|
@@ -117,18 +166,15 @@ export function wrapFlowError(action, err) {
|
|
|
117
166
|
if (isFlowError(err)) {
|
|
118
167
|
return new FlowError(err.code, `${action} failed: ${err.message}`, {
|
|
119
168
|
cause: err,
|
|
120
|
-
hints: err.hints,
|
|
121
169
|
review: err.review,
|
|
170
|
+
facts: err.facts,
|
|
122
171
|
});
|
|
123
172
|
}
|
|
124
173
|
return new Error(`${action} failed: ${asMessage(err)}`);
|
|
125
174
|
}
|
|
126
175
|
export function pickHintsFromError(err, hintContext = DEFAULT_HINT_CONTEXT) {
|
|
127
176
|
if (isFlowError(err)) {
|
|
128
|
-
|
|
129
|
-
return err.hints;
|
|
130
|
-
}
|
|
131
|
-
return [resolveFlowErrorHint(err.code, hintContext)];
|
|
177
|
+
return resolveFlowErrorHints(err.code, hintContext, err.facts);
|
|
132
178
|
}
|
|
133
179
|
return [getDefaultErrorHint()];
|
|
134
180
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as fsPromises from "node:fs/promises";
|
|
3
|
+
export function supportsDirectorySync(platform = process.platform) {
|
|
4
|
+
return platform !== "win32";
|
|
5
|
+
}
|
|
6
|
+
export function syncDirectorySync(directory, platform = process.platform) {
|
|
7
|
+
if (!supportsDirectorySync(platform))
|
|
8
|
+
return;
|
|
9
|
+
const descriptor = fs.openSync(directory, fs.constants.O_RDONLY);
|
|
10
|
+
try {
|
|
11
|
+
fs.fsyncSync(descriptor);
|
|
12
|
+
}
|
|
13
|
+
finally {
|
|
14
|
+
fs.closeSync(descriptor);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function syncDirectory(directory, platform = process.platform) {
|
|
18
|
+
if (!supportsDirectorySync(platform))
|
|
19
|
+
return;
|
|
20
|
+
const handle = await fsPromises.open(directory, "r");
|
|
21
|
+
try {
|
|
22
|
+
await handle.sync();
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
await handle.close();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
/**
|
|
3
|
+
* Keep one host-native spelling before using a coordinate as a filesystem cwd.
|
|
4
|
+
* Git for Windows may emit or receive native, slash-normalized, or MSYS drive
|
|
5
|
+
* paths for the same location.
|
|
6
|
+
*/
|
|
7
|
+
export function normalizeFilesystemCoordinate(raw, platform = process.platform) {
|
|
8
|
+
const value = raw.trim();
|
|
9
|
+
if (platform !== "win32")
|
|
10
|
+
return path.normalize(value);
|
|
11
|
+
const msysDrive = value.match(/^\/([A-Za-z])(?:\/|$)(.*)$/);
|
|
12
|
+
if (msysDrive)
|
|
13
|
+
return path.win32.normalize(`${msysDrive[1].toUpperCase()}:/${msysDrive[2]}`);
|
|
14
|
+
const normalized = path.win32.normalize(value);
|
|
15
|
+
return normalized.replace(/^([a-z]):/, (_, drive) => `${drive.toUpperCase()}:`);
|
|
16
|
+
}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
//
|
|
2
|
-
export const VERSION = "2.9.
|
|
1
|
+
// Generated into build output by scripts/build.mjs
|
|
2
|
+
export const VERSION = "2.9.4";
|
|
3
|
+
export const GIT_HASH = "0aa48ab";
|
package/build/git/branches.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { simpleGit } from "simple-git";
|
|
2
2
|
import { FlowError } from "../flow-error.js";
|
|
3
|
+
import { normalizeFilesystemCoordinate } from "../fs/path-coordinate.js";
|
|
3
4
|
import { createGit, errorContainsAnyPattern, isGitExecutableUnavailable, NOT_GIT_REPOSITORY_PATTERNS, wrapGitError, } from "./core.js";
|
|
4
5
|
export async function observeGitRepository(cwd) {
|
|
5
6
|
try {
|
|
6
|
-
const git = simpleGit(cwd);
|
|
7
|
+
const git = simpleGit(normalizeFilesystemCoordinate(cwd));
|
|
7
8
|
git.env("GIT_TERMINAL_PROMPT", "0");
|
|
8
9
|
git.env("GCM_INTERACTIVE", "Never");
|
|
9
10
|
git.env("GIT_MERGE_AUTOEDIT", "no");
|
package/build/git/core.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
1
2
|
import { simpleGit } from "simple-git";
|
|
3
|
+
import { normalizeFilesystemCoordinate } from "../fs/path-coordinate.js";
|
|
2
4
|
import { getConfig } from "../config/env.js";
|
|
3
5
|
import { appendDebugBlock } from "../telemetry/debug-log.js";
|
|
4
6
|
const GIT_ERROR_COMPACT_MAX_CHARS = 4000;
|
|
@@ -38,6 +40,14 @@ export function errorContainsAnyPattern(err, patterns) {
|
|
|
38
40
|
const text = collectGitErrorText(err);
|
|
39
41
|
return patterns.some((pattern) => text.includes(pattern));
|
|
40
42
|
}
|
|
43
|
+
/** Parse `git rev-list` stdout into ordered object ids. Sole shared line parser. */
|
|
44
|
+
export function parseRevListOutput(output) {
|
|
45
|
+
return output
|
|
46
|
+
.trim()
|
|
47
|
+
.split(/\r?\n/)
|
|
48
|
+
.map((line) => line.trim())
|
|
49
|
+
.filter((line) => line.length > 0);
|
|
50
|
+
}
|
|
41
51
|
export function wrapGitError(commandLabel, err, cwd) {
|
|
42
52
|
const source = (err ?? {});
|
|
43
53
|
const message = source.message ?? String(err);
|
|
@@ -57,9 +67,31 @@ export function wrapGitError(commandLabel, err, cwd) {
|
|
|
57
67
|
}
|
|
58
68
|
return wrapped;
|
|
59
69
|
}
|
|
70
|
+
/** Configured Git timeout, with a bootstrap-safe default before env loads. */
|
|
71
|
+
export function resolveGitTimeoutMs() {
|
|
72
|
+
try {
|
|
73
|
+
return getConfig().git.timeoutMs;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Git coordinates and early probes remain usable before config bootstrap.
|
|
77
|
+
return 60_000;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Non-interactive Git process environment shared by one-shot and streaming gates. */
|
|
81
|
+
export function gitProcessEnv(extra) {
|
|
82
|
+
return {
|
|
83
|
+
...process.env,
|
|
84
|
+
...extra,
|
|
85
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
86
|
+
GCM_INTERACTIVE: "Never",
|
|
87
|
+
GIT_MERGE_AUTOEDIT: "no",
|
|
88
|
+
LC_ALL: "C",
|
|
89
|
+
PATH: extra?.PATH ?? process.env.PATH ?? "",
|
|
90
|
+
};
|
|
91
|
+
}
|
|
60
92
|
export function createGit(cwd) {
|
|
61
|
-
const timeoutMs =
|
|
62
|
-
const git = simpleGit(cwd, {
|
|
93
|
+
const timeoutMs = resolveGitTimeoutMs();
|
|
94
|
+
const git = simpleGit(normalizeFilesystemCoordinate(cwd), {
|
|
63
95
|
timeout: { block: timeoutMs, stdErr: true, stdOut: true },
|
|
64
96
|
});
|
|
65
97
|
git.env("GIT_TERMINAL_PROMPT", "0");
|
|
@@ -69,3 +101,75 @@ export function createGit(cwd) {
|
|
|
69
101
|
git.env("PATH", process.env.PATH ?? "");
|
|
70
102
|
return git;
|
|
71
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Asynchronous one-shot Git process boundary that preserves stdout/stderr/status facts.
|
|
106
|
+
* Prefer `createGit` when non-zero exits are fatal; use this when callers need
|
|
107
|
+
* soft status codes or stdin (simple-git drops empty-stderr non-zero exits).
|
|
108
|
+
* Long-lived multi-request stdin/stdout sessions use `openGitStreamingBatch`.
|
|
109
|
+
*/
|
|
110
|
+
export async function runGitProcess(cwd, args, options) {
|
|
111
|
+
const timeoutMs = resolveGitTimeoutMs();
|
|
112
|
+
const env = gitProcessEnv(options?.env);
|
|
113
|
+
return await new Promise((resolve) => {
|
|
114
|
+
const child = spawn("git", [...args], {
|
|
115
|
+
cwd: normalizeFilesystemCoordinate(cwd),
|
|
116
|
+
env,
|
|
117
|
+
shell: false,
|
|
118
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
119
|
+
});
|
|
120
|
+
const stdoutChunks = [];
|
|
121
|
+
const stderrChunks = [];
|
|
122
|
+
let settled = false;
|
|
123
|
+
let timedOut = false;
|
|
124
|
+
const finish = (result) => {
|
|
125
|
+
if (settled)
|
|
126
|
+
return;
|
|
127
|
+
settled = true;
|
|
128
|
+
clearTimeout(timeout);
|
|
129
|
+
resolve(result);
|
|
130
|
+
};
|
|
131
|
+
const timeout = setTimeout(() => {
|
|
132
|
+
timedOut = true;
|
|
133
|
+
child.kill("SIGKILL");
|
|
134
|
+
}, timeoutMs);
|
|
135
|
+
if (options?.input !== undefined) {
|
|
136
|
+
child.stdin.end(options.input, "utf8");
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
child.stdin.end();
|
|
140
|
+
}
|
|
141
|
+
child.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
|
|
142
|
+
child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
|
|
143
|
+
child.on("error", (error) => {
|
|
144
|
+
finish({
|
|
145
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
146
|
+
stderr: Buffer.concat(stderrChunks).toString("utf8"),
|
|
147
|
+
status: null,
|
|
148
|
+
error,
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
child.on("close", (code, signal) => {
|
|
152
|
+
const stdout = Buffer.concat(stdoutChunks).toString("utf8");
|
|
153
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf8");
|
|
154
|
+
if (timedOut) {
|
|
155
|
+
finish({
|
|
156
|
+
stdout,
|
|
157
|
+
stderr,
|
|
158
|
+
status: null,
|
|
159
|
+
error: new Error("git timed out"),
|
|
160
|
+
});
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (signal) {
|
|
164
|
+
finish({
|
|
165
|
+
stdout,
|
|
166
|
+
stderr,
|
|
167
|
+
status: null,
|
|
168
|
+
error: new Error(`git exited from signal ${signal}`),
|
|
169
|
+
});
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
finish({ stdout, stderr, status: code });
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as fsPromises from "node:fs/promises";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { normalizeFilesystemCoordinate } from "../fs/path-coordinate.js";
|
|
5
|
+
export function normalizeGitPath(raw, platform = process.platform) {
|
|
6
|
+
return normalizeFilesystemCoordinate(raw, platform);
|
|
7
|
+
}
|
|
8
|
+
export function gitCommonRootPath(raw, platform = process.platform) {
|
|
9
|
+
const paths = platform === "win32" ? path.win32 : path.posix;
|
|
10
|
+
return paths.dirname(normalizeFilesystemCoordinate(raw, platform));
|
|
11
|
+
}
|
|
12
|
+
export async function resolveGitCommonRoot(raw) {
|
|
13
|
+
return await fsPromises.realpath(gitCommonRootPath(raw));
|
|
14
|
+
}
|
|
15
|
+
export function resolveGitCommonRootSync(raw) {
|
|
16
|
+
return fs.realpathSync(gitCommonRootPath(raw));
|
|
17
|
+
}
|