@astrosheep/keiyaku 2.9.12 → 2.9.13
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 +22 -18
- package/build/.tsbuildinfo +1 -1
- package/build/agents/call-terms.js +1 -0
- package/build/agents/providers/claude-agent-sdk/adapter.js +2 -2
- package/build/cli/commands/akuma/akuma/handler.js +2 -1
- package/build/cli/commands/akuma/akuma/meta.js +4 -3
- package/build/cli/commands/akuma/catalog.js +2 -0
- package/build/cli/commands/akuma/list/handler.js +1 -1
- package/build/cli/commands/akuma/list/meta-list.js +12 -0
- package/build/cli/commands/akuma/list/meta-ls.js +2 -2
- package/build/cli/commands/akuma.js +5 -0
- package/build/cli/commands/contract/amend/meta.js +4 -3
- package/build/cli/commands/contract/arc/meta.js +2 -0
- package/build/cli/commands/contract/audit/handler.js +28 -2
- package/build/cli/commands/contract/audit/meta.js +4 -3
- package/build/cli/commands/contract/petition/handler.js +2 -1
- package/build/cli/commands/contract/petition/meta.js +2 -2
- package/build/cli/commands/contract/renew/handler.js +44 -3
- package/build/cli/commands/contract/renew/meta.js +5 -4
- package/build/cli/commands/metadata.js +12 -9
- package/build/cli/commands/projection/call/handler.js +1 -1
- package/build/cli/commands/projection/call/meta.js +1 -1
- package/build/cli/commands/projection/catalog.js +2 -2
- package/build/cli/commands/projection/{revive → fork}/handler.js +10 -5
- package/build/cli/commands/projection/fork/meta.js +12 -0
- package/build/cli/commands/projection/history/handler.js +46 -2
- package/build/cli/commands/projection/history/meta.js +3 -3
- package/build/cli/commands/projection/kill/meta.js +2 -2
- package/build/cli/commands/projection/status/handler.js +29 -13
- package/build/cli/commands/projection/status/meta.js +14 -3
- package/build/cli/commands/projection/tell/handler.js +8 -4
- package/build/cli/commands/projection/tell/meta.js +1 -1
- package/build/cli/commands/projection/wait/handler.js +45 -20
- package/build/cli/commands/projection/wait/meta.js +1 -1
- package/build/cli/commands/shared.js +1 -1
- package/build/cli/commands/system/catalog.js +2 -0
- package/build/cli/commands/task/add/handler.js +28 -12
- package/build/cli/commands/task/add/meta.js +18 -6
- package/build/cli/commands/task/catalog.js +2 -0
- package/build/cli/commands/task/compose/handler.js +2 -2
- package/build/cli/commands/task/compose/meta.js +2 -2
- package/build/cli/commands/task/doctor/handler.js +2 -3
- package/build/cli/commands/task/doctor/meta.js +2 -2
- package/build/cli/commands/task/done/meta.js +2 -2
- package/build/cli/commands/task/drop/handler.js +7 -2
- package/build/cli/commands/task/drop/meta.js +6 -3
- package/build/cli/commands/task/hold/meta.js +2 -2
- package/build/cli/commands/task/log/meta.js +2 -2
- package/build/cli/commands/task/ls/meta.js +2 -2
- package/build/cli/commands/task/note/handler.js +6 -0
- package/build/cli/commands/task/note/meta.js +11 -0
- package/build/cli/commands/task/resume/meta.js +2 -2
- package/build/cli/commands/task/shared.js +29 -15
- package/build/cli/commands/task/show/meta.js +2 -2
- package/build/cli/commands/task/start/meta.js +2 -2
- package/build/cli/commands/task/stop/meta.js +2 -2
- package/build/cli/commands/task/update/meta.js +3 -2
- package/build/cli/commands/verification/handler.js +43 -0
- package/build/cli/commands/verification/meta.js +13 -0
- package/build/cli/completion.js +16 -13
- package/build/cli/flags.js +43 -11
- package/build/cli/help.js +29 -30
- package/build/cli/index.js +6 -5
- package/build/cli/parse-flags.js +39 -0
- package/build/cli/parse-metadata.js +5 -2
- package/build/cli/parse-selectors.js +18 -0
- package/build/cli/parse.js +21 -8
- package/build/cli/render/arc.js +12 -3
- package/build/cli/render/audit.js +2 -2
- package/build/cli/render/call.js +4 -4
- package/build/cli/render/kanshi.js +122 -85
- package/build/cli/render/misc.js +2 -2
- package/build/cli/render/petition.js +16 -1
- package/build/cli/render/projection-history.js +17 -4
- package/build/cli/render/shared.js +5 -5
- package/build/cli/render/status-indicator.js +47 -0
- package/build/cli/render/status.js +106 -76
- package/build/cli/render/success-response.js +20 -5
- package/build/cli/render/task.js +6 -5
- package/build/cli/render/tell.js +10 -4
- package/build/cli/render/tool-command-normalization.js +137 -0
- package/build/cli/render/tool-presentation.js +2 -1
- package/build/cli/render/verification.js +23 -0
- package/build/cli/render/wait.js +101 -33
- package/build/cli/types.js +2 -1
- package/build/config/akuma-loader.js +21 -1
- package/build/config/settings/knobs.js +7 -1
- package/build/config/settings/schema.js +10 -5
- package/build/core/amend.js +102 -17
- package/build/core/arc.js +4 -4
- package/build/core/audit/candidate.js +10 -18
- package/build/core/audit/coordinates.js +12 -17
- package/build/core/audit/evidence.js +6 -4
- package/build/core/audit/facade.js +3 -3
- package/build/core/audit/report.js +1 -1
- package/build/core/bind-reconciliation.js +42 -23
- package/build/core/bind.js +182 -81
- package/build/core/call/call.js +6 -7
- package/build/core/call/context.js +43 -30
- package/build/core/call/execution.js +21 -6
- package/build/core/call/prompt.js +11 -42
- package/build/core/contract-carrier-runtime.js +209 -105
- package/build/core/contract-carrier.js +156 -73
- package/build/core/contract-view.js +42 -5
- package/build/core/contract.js +2 -1
- package/build/core/derived-replay.js +168 -35
- package/build/core/draft.js +9 -5
- package/build/core/forfeit.js +1 -1
- package/build/core/lifecycle-history.js +13 -0
- package/build/core/lifecycle-recovery.js +32 -27
- package/build/core/lifecycle-runner.js +14 -14
- package/build/core/log.js +1 -1
- package/build/core/outcome-base.js +2 -2
- package/build/core/projection/akuma-name.js +43 -0
- package/build/core/projection/generation/database.js +141 -79
- package/build/core/projection/generation/ledger.js +91 -0
- package/build/core/projection/generation/model.js +18 -13
- package/build/core/projection/generation/projection-generation-continuation.js +1 -1
- package/build/core/projection/generation/projection-generation-doorbell.js +88 -0
- package/build/core/projection/generation/projection-generation-execution.js +24 -0
- package/build/core/projection/generation/projection-generation-identity.js +4 -0
- package/build/core/projection/generation/projection-generation-launcher.js +72 -138
- package/build/core/projection/generation/projection-generation-process.js +16 -15
- package/build/core/projection/generation/projection-generation-runner.js +125 -79
- package/build/core/projection/generation/projection-generation-runtime.js +10 -61
- package/build/core/projection/generation/protocol.js +68 -0
- package/build/core/projection/generation/store.js +17 -7
- package/build/core/projection/generation/transitions.js +14 -14
- package/build/core/projection/index.js +5 -5
- package/build/core/projection/projection-core.js +6 -2
- package/build/core/projection/projection-execution-observer.js +1 -1
- package/build/core/projection/projection-history.js +7 -7
- package/build/core/projection/projection-kill.js +15 -6
- package/build/core/projection/projection-life-observer.js +8 -8
- package/build/core/projection/projection-mint.js +6 -8
- package/build/core/projection/projection-status-observation.js +77 -8
- package/build/core/projection/projection-status.js +151 -4
- package/build/core/projection/projection-wait.js +46 -27
- package/build/core/projection/projection-wake.js +27 -89
- package/build/core/projection/tell/launch-store.js +1 -1
- package/build/core/projection/tell/store.js +1 -1
- package/build/core/renew-build.js +204 -74
- package/build/core/renew-plan.js +34 -9
- package/build/core/renew-rewrite.js +56 -15
- package/build/core/renew.js +39 -35
- package/build/core/repository-ledger/accepted-fold-read.js +6 -3
- package/build/core/repository-ledger/codec.js +11 -5
- package/build/core/repository-ledger/current-state-store.js +13 -16
- package/build/core/repository-ledger/fold-repository.js +5 -6
- package/build/core/repository-ledger/identity.js +60 -0
- package/build/core/repository-ledger/inventory.js +3 -3
- package/build/core/repository-ledger/read-model.js +54 -6
- package/build/core/repository-ledger/write-transaction.js +9 -4
- package/build/core/run-control/connection-bound-close.js +41 -0
- package/build/core/run-control/detached-lease-bootstrap.js +208 -0
- package/build/core/run-control/detached-process.js +14 -0
- package/build/core/run-control/index.js +6 -0
- package/build/core/{process-group.js → run-control/process-tree.js} +1 -1
- package/build/core/run-control/runner-lease.js +172 -0
- package/build/core/{sqlite-process-lifetime-lock.js → run-control/sqlite-process-lifetime-lock.js} +4 -24
- package/build/core/seal.js +44 -32
- package/build/core/settlement/claim-delivery.js +22 -9
- package/build/core/settlement/claim.js +8 -6
- package/build/core/settlement/petition-claim-gates.js +9 -8
- package/build/core/settlement/petition-head-guard.js +5 -6
- package/build/core/settlement/petition-preview.js +21 -5
- package/build/core/settlement/petition.js +51 -17
- package/build/core/settlement/settlement.js +20 -15
- package/build/core/settlement/verdict.js +2 -2
- package/build/core/settlement/verification.js +492 -364
- package/build/core/status/board.js +184 -75
- package/build/core/status/drift.js +8 -10
- package/build/core/task/board.js +1 -0
- package/build/core/task/commands.js +25 -0
- package/build/core/task/compose.js +3 -2
- package/build/core/task/document.js +102 -39
- package/build/core/task/index.js +3 -3
- package/build/core/task/settlement-git.js +1 -4
- package/build/core/task/settlement-policy.js +27 -14
- package/build/core/task/source-board.js +1 -0
- package/build/core/task/task-bind-preparation.js +59 -9
- package/build/core/task/task-contract.js +2 -2
- package/build/core/task/task-store-repository.js +1 -0
- package/build/core/task/task.js +2 -2
- package/build/core/transcripts.js +4 -4
- package/build/core/verification-declaration.js +95 -0
- package/build/core/worktree-bootstrap.js +1 -1
- package/build/core/worktree-path.js +7 -11
- package/build/flow-error.js +9 -1
- package/build/generated/version.js +2 -2
- package/build/git/branches.js +41 -70
- package/build/git/commits.js +92 -21
- package/build/git/core.js +9 -161
- package/build/git/diff/preview.js +4 -4
- package/build/git/diff/read.js +4 -4
- package/build/git/diff/structured.js +5 -5
- package/build/git/process.js +229 -0
- package/build/git/refs.js +30 -5
- package/build/git/staging.js +39 -115
- package/build/git/streaming-batch.js +9 -16
- package/build/git/worktree.js +104 -91
- package/build/index.js +7 -0
- package/package.json +1 -1
- package/skills/keiyaku-akuma/SKILL.md +21 -20
- package/skills/keiyaku-task/SKILL.md +38 -4
- package/skills/keiyaku-workflow/SKILL.md +51 -11
- package/build/cli/commands/projection/revive/meta.js +0 -12
- package/build/core/projection/projection-life-protocol.js +0 -115
- package/build/core/projection/projection-runner-lock.js +0 -211
- package/build/core/projection/tell/database.js +0 -127
- package/build/core/settlement/verification-coordination.js +0 -305
- package/build/core/settlement/verification-supervisor.js +0 -275
package/build/flow-error.js
CHANGED
|
@@ -55,6 +55,12 @@ export function resolveFlowErrorHints(code, context, facts) {
|
|
|
55
55
|
if (facts?.kind === "projection_wake_failure") {
|
|
56
56
|
return [];
|
|
57
57
|
}
|
|
58
|
+
if (facts?.kind === "task_contract_workspace") {
|
|
59
|
+
return ["Select an active contract with a materialized workspace, or retry from the desired checkout with -C."];
|
|
60
|
+
}
|
|
61
|
+
if (facts?.kind === "carrier_dispatch_refusal") {
|
|
62
|
+
return [facts.recoveryCommand];
|
|
63
|
+
}
|
|
58
64
|
switch (code) {
|
|
59
65
|
case "NOT_GIT_REPO":
|
|
60
66
|
return ["The provided `cwd` is not a git repository."];
|
|
@@ -64,6 +70,8 @@ export function resolveFlowErrorHints(code, context, facts) {
|
|
|
64
70
|
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.`];
|
|
65
71
|
case "CONTRACT_AMBIGUOUS":
|
|
66
72
|
return ["Address a specific contract workspace, or use `call --bare` to run without contract attachment."];
|
|
73
|
+
case "CONTRACT_WORKSPACE_UNAVAILABLE":
|
|
74
|
+
return [];
|
|
67
75
|
case "TASK_AMBIGUOUS":
|
|
68
76
|
return ["Pass one complete task coordinate from the listed candidates."];
|
|
69
77
|
case "UNKNOWN_COMMAND":
|
|
@@ -108,7 +116,7 @@ export function resolveFlowErrorHints(code, context, facts) {
|
|
|
108
116
|
case "DETACHED_HEAD":
|
|
109
117
|
return ["Repository is in detached HEAD state. Address an active contract workspace before retrying."];
|
|
110
118
|
case "INVALID_RESPONSE_PATH":
|
|
111
|
-
return ["`keiyaku
|
|
119
|
+
return ["`keiyaku fork ARTIFACT_ID` needs a valid previous Keiyaku response artifact that includes complete provenance plus `session` and `subagent_name`."];
|
|
112
120
|
case "INVALID_CLOSE_INTENT":
|
|
113
121
|
return ["Invalid close intent. Valid values are `CLAIM` and `FORFEIT`."];
|
|
114
122
|
case "INVALID_SETTINGS":
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Generated into build output by scripts/build.mjs
|
|
2
|
-
export const VERSION = "2.9.
|
|
3
|
-
export const GIT_HASH = "
|
|
2
|
+
export const VERSION = "2.9.13";
|
|
3
|
+
export const GIT_HASH = "0971934";
|
package/build/git/branches.js
CHANGED
|
@@ -1,29 +1,37 @@
|
|
|
1
|
-
import { simpleGit } from "simple-git";
|
|
2
1
|
import { FlowError } from "../flow-error.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { errorContainsAnyPattern, isGitExecutableUnavailable, NOT_GIT_REPOSITORY_PATTERNS, wrapGitError, } from "./core.js";
|
|
3
|
+
import { runGitOrThrow, runGitProcess } from "./process.js";
|
|
4
|
+
async function gitOutput(cwd, args, label) {
|
|
5
|
+
try {
|
|
6
|
+
return (await runGitOrThrow(cwd, args)).stdout;
|
|
7
|
+
}
|
|
8
|
+
catch (error) {
|
|
9
|
+
throw wrapGitError(label, error, cwd);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function processFailure(result) {
|
|
13
|
+
return result.error ?? Object.assign(new Error(`git exited with status ${result.status ?? "unknown"}`), {
|
|
14
|
+
stdout: result.stdout,
|
|
15
|
+
stderr: result.stderr,
|
|
16
|
+
status: result.status,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
5
19
|
export async function observeGitRepository(cwd) {
|
|
6
20
|
try {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
git.env("GCM_INTERACTIVE", "Never");
|
|
10
|
-
git.env("GIT_MERGE_AUTOEDIT", "no");
|
|
11
|
-
git.env("LC_ALL", "C");
|
|
12
|
-
git.env("PATH", process.env.PATH ?? "");
|
|
13
|
-
const output = await git.raw(["rev-parse", "--is-inside-work-tree", "--is-bare-repository"]);
|
|
14
|
-
const [insideWorktree, bare] = output.trim().split(/\r?\n/);
|
|
21
|
+
const output = await runGitOrThrow(cwd, ["rev-parse", "--is-inside-work-tree", "--is-bare-repository"]);
|
|
22
|
+
const [insideWorktree, bare] = output.stdout.trim().split(/\r?\n/);
|
|
15
23
|
return {
|
|
16
24
|
capability: "available",
|
|
17
25
|
coordinate: insideWorktree === "true" && bare !== "true" ? "repository" : "non-repository",
|
|
18
26
|
};
|
|
19
27
|
}
|
|
20
|
-
catch (
|
|
21
|
-
if (isGitExecutableUnavailable(
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (isGitExecutableUnavailable(error))
|
|
22
30
|
return { capability: "unavailable" };
|
|
23
|
-
if (errorContainsAnyPattern(
|
|
31
|
+
if (errorContainsAnyPattern(error, NOT_GIT_REPOSITORY_PATTERNS)) {
|
|
24
32
|
return { capability: "available", coordinate: "non-repository" };
|
|
25
33
|
}
|
|
26
|
-
return { capability: "unknown", error: wrapGitError("check-is-repo",
|
|
34
|
+
return { capability: "unknown", error: wrapGitError("check-is-repo", error, cwd) };
|
|
27
35
|
}
|
|
28
36
|
}
|
|
29
37
|
export function assertGitObservation(observation, operation) {
|
|
@@ -40,74 +48,37 @@ export async function requireGitRepository(cwd, operation) {
|
|
|
40
48
|
}
|
|
41
49
|
}
|
|
42
50
|
export async function getCurrentBranch(cwd) {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
status = await git.status();
|
|
47
|
-
}
|
|
48
|
-
catch (err) {
|
|
49
|
-
throw wrapGitError("status", err, cwd);
|
|
51
|
+
const symbolic = await runGitProcess(cwd, ["symbolic-ref", "--quiet", "--short", "HEAD"]);
|
|
52
|
+
if (symbolic.error || (symbolic.status !== 0 && symbolic.status !== 1)) {
|
|
53
|
+
throw wrapGitError("symbolic-ref --quiet --short HEAD", processFailure(symbolic), cwd);
|
|
50
54
|
}
|
|
51
|
-
if (!
|
|
55
|
+
if (symbolic.status === 1 || !symbolic.stdout.trim()) {
|
|
52
56
|
throw new FlowError("DETACHED_HEAD", "Detached HEAD or no branch found");
|
|
53
57
|
}
|
|
54
|
-
|
|
58
|
+
const head = await runGitProcess(cwd, ["rev-parse", "--verify", "--quiet", "HEAD"]);
|
|
59
|
+
if (head.error || (head.status !== 0 && head.status !== 1)) {
|
|
60
|
+
throw wrapGitError("rev-parse --verify HEAD", processFailure(head), cwd);
|
|
61
|
+
}
|
|
62
|
+
if (head.status === 0)
|
|
63
|
+
return symbolic.stdout.trim();
|
|
64
|
+
throw new FlowError("DETACHED_HEAD", "Detached HEAD or no branch found");
|
|
55
65
|
}
|
|
56
66
|
export async function listLocalBranches(cwd) {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
branches = await git.branchLocal();
|
|
61
|
-
}
|
|
62
|
-
catch (err) {
|
|
63
|
-
throw wrapGitError("branch --list", err, cwd);
|
|
64
|
-
}
|
|
65
|
-
return branches.all;
|
|
67
|
+
const output = await gitOutput(cwd, ["for-each-ref", "--format=%(refname:short)", "refs/heads"], "branch --list");
|
|
68
|
+
return output.split(/\r?\n/).filter((branch) => branch.length > 0);
|
|
66
69
|
}
|
|
67
70
|
export async function createAndSwitchBranch(cwd, branchName) {
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
await git.raw(["switch", "-c", branchName]);
|
|
71
|
-
}
|
|
72
|
-
catch (err) {
|
|
73
|
-
throw wrapGitError(`switch -c ${branchName}`, err, cwd);
|
|
74
|
-
}
|
|
71
|
+
await gitOutput(cwd, ["switch", "-c", branchName], `switch -c ${branchName}`);
|
|
75
72
|
}
|
|
76
73
|
export async function switchBranch(cwd, branchName) {
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
await git.raw(["switch", branchName]);
|
|
80
|
-
}
|
|
81
|
-
catch (err) {
|
|
82
|
-
throw wrapGitError(`switch ${branchName}`, err, cwd);
|
|
83
|
-
}
|
|
74
|
+
await gitOutput(cwd, ["switch", branchName], `switch ${branchName}`);
|
|
84
75
|
}
|
|
85
76
|
export async function deleteBranch(cwd, branchName, force = false) {
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
await git.deleteLocalBranch(branchName, force);
|
|
89
|
-
}
|
|
90
|
-
catch (err) {
|
|
91
|
-
throw wrapGitError(`branch -d${force ? " -D" : ""} ${branchName}`, err, cwd);
|
|
92
|
-
}
|
|
77
|
+
await gitOutput(cwd, ["branch", force ? "-D" : "-d", branchName], `branch -d${force ? " -D" : ""} ${branchName}`);
|
|
93
78
|
}
|
|
94
79
|
export async function hasLocalBranch(cwd, branchName) {
|
|
95
|
-
|
|
96
|
-
let branches;
|
|
97
|
-
try {
|
|
98
|
-
branches = await git.branchLocal();
|
|
99
|
-
}
|
|
100
|
-
catch (err) {
|
|
101
|
-
throw wrapGitError("branch --list", err, cwd);
|
|
102
|
-
}
|
|
103
|
-
return branches.all.includes(branchName);
|
|
80
|
+
return (await listLocalBranches(cwd)).includes(branchName);
|
|
104
81
|
}
|
|
105
82
|
export async function assertValidBranchName(cwd, branchName) {
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
await git.raw(["check-ref-format", "--branch", branchName]);
|
|
109
|
-
}
|
|
110
|
-
catch (err) {
|
|
111
|
-
throw wrapGitError(`check-ref-format --branch ${branchName}`, err, cwd);
|
|
112
|
-
}
|
|
83
|
+
await gitOutput(cwd, ["check-ref-format", "--branch", branchName], `check-ref-format --branch ${branchName}`);
|
|
113
84
|
}
|
package/build/git/commits.js
CHANGED
|
@@ -1,35 +1,107 @@
|
|
|
1
1
|
import { FlowError } from "../flow-error.js";
|
|
2
|
-
import {
|
|
2
|
+
import { isFullGitObjectId, runGitProcess, wrapGitError, } from "./core.js";
|
|
3
|
+
import { runGitOrThrow } from "./process.js";
|
|
4
|
+
const COMMIT_COORDINATE_FORMAT = "format:%H%x00%T%x00%P";
|
|
5
|
+
/**
|
|
6
|
+
* Resolve one revision to a peeled commit coordinate through a single argv-safe,
|
|
7
|
+
* non-walking Git query. Ordinary commits and annotated tags share the peeled tip.
|
|
8
|
+
* Missing revisions, non-commit terminal objects, malformed output, and process
|
|
9
|
+
* failures stay distinguishable without matching localized presentation text.
|
|
10
|
+
*/
|
|
11
|
+
export async function readCommitCoordinate(cwd, revision) {
|
|
12
|
+
const result = await runGitProcess(cwd, [
|
|
13
|
+
"log",
|
|
14
|
+
"--no-walk",
|
|
15
|
+
"-1",
|
|
16
|
+
`--format=${COMMIT_COORDINATE_FORMAT}`,
|
|
17
|
+
"--end-of-options",
|
|
18
|
+
revision,
|
|
19
|
+
"--",
|
|
20
|
+
]);
|
|
21
|
+
if (result.error) {
|
|
22
|
+
return { status: "failed", revision, error: result.error, result };
|
|
23
|
+
}
|
|
24
|
+
if (result.status !== 0) {
|
|
25
|
+
return { status: "missing", revision };
|
|
26
|
+
}
|
|
27
|
+
const raw = result.stdout.replace(/(?:\r?\n)+$/, "");
|
|
28
|
+
if (raw.length === 0) {
|
|
29
|
+
return { status: "non-commit", revision };
|
|
30
|
+
}
|
|
31
|
+
const fields = raw.split("\0");
|
|
32
|
+
if (fields.length !== 3) {
|
|
33
|
+
return {
|
|
34
|
+
status: "malformed",
|
|
35
|
+
revision,
|
|
36
|
+
detail: `expected 3 NUL fields, received ${fields.length}`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const [commit, tree, parentsField] = fields;
|
|
40
|
+
if (!isFullGitObjectId(commit) || !isFullGitObjectId(tree)) {
|
|
41
|
+
return {
|
|
42
|
+
status: "malformed",
|
|
43
|
+
revision,
|
|
44
|
+
detail: "commit or tree field is not a full object id",
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const parents = parentsField.trim().length === 0
|
|
48
|
+
? []
|
|
49
|
+
: parentsField.trim().split(/\s+/).filter((part) => part.length > 0);
|
|
50
|
+
if (parents.some((parent) => !isFullGitObjectId(parent))) {
|
|
51
|
+
return {
|
|
52
|
+
status: "malformed",
|
|
53
|
+
revision,
|
|
54
|
+
detail: "parent field contains a non-object id",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return { status: "ok", coordinate: { commit, tree, parents } };
|
|
58
|
+
}
|
|
59
|
+
/** Map a failed coordinate read into a wrapped Git error for throw-style callers. */
|
|
60
|
+
export function commitCoordinateFailureError(read, cwd) {
|
|
61
|
+
if (read.status === "failed") {
|
|
62
|
+
const failure = read.error ?? Object.assign(new Error(read.result.stderr || `git log --no-walk exited ${read.result.status}`), {
|
|
63
|
+
stderr: read.result.stderr,
|
|
64
|
+
stdout: read.result.stdout,
|
|
65
|
+
});
|
|
66
|
+
return wrapGitError(`log --no-walk coordinate ${read.revision}`, failure, cwd);
|
|
67
|
+
}
|
|
68
|
+
if (read.status === "missing") {
|
|
69
|
+
return wrapGitError(`log --no-walk coordinate ${read.revision}`, Object.assign(new Error(`revision '${read.revision}' does not resolve`), { stderr: "" }), cwd);
|
|
70
|
+
}
|
|
71
|
+
if (read.status === "non-commit") {
|
|
72
|
+
return wrapGitError(`log --no-walk coordinate ${read.revision}`, Object.assign(new Error(`revision '${read.revision}' does not resolve to a commit`), { stderr: "" }), cwd);
|
|
73
|
+
}
|
|
74
|
+
return wrapGitError(`log --no-walk coordinate ${read.revision}`, Object.assign(new Error(`malformed commit coordinate for '${read.revision}': ${read.detail}`), { stderr: "" }), cwd);
|
|
75
|
+
}
|
|
76
|
+
/** Require a resolved coordinate or throw a wrapped Git error. */
|
|
77
|
+
export async function requireCommitCoordinate(cwd, revision) {
|
|
78
|
+
const read = await readCommitCoordinate(cwd, revision);
|
|
79
|
+
if (read.status === "ok")
|
|
80
|
+
return read.coordinate;
|
|
81
|
+
throw commitCoordinateFailureError(read, cwd);
|
|
82
|
+
}
|
|
3
83
|
export function shortCommitHash(hash) {
|
|
4
84
|
return hash.slice(0, 12);
|
|
5
85
|
}
|
|
6
86
|
export async function resolveCommitRef(cwd, ref) {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
catch (error) {
|
|
13
|
-
throw new FlowError("INVALID_COMMIT", `commit '${ref}' does not resolve to a commit`, { cause: error });
|
|
87
|
+
const read = await readCommitCoordinate(cwd, ref);
|
|
88
|
+
if (read.status !== "ok") {
|
|
89
|
+
throw new FlowError("INVALID_COMMIT", `commit '${ref}' does not resolve to a commit`, {
|
|
90
|
+
cause: read.status === "failed" ? read.error : undefined,
|
|
91
|
+
});
|
|
14
92
|
}
|
|
93
|
+
return read.coordinate.commit;
|
|
15
94
|
}
|
|
16
95
|
export async function isCommitReachableFromHead(cwd, commit) {
|
|
17
96
|
return isCommitAncestor(cwd, commit, "HEAD");
|
|
18
97
|
}
|
|
19
98
|
export async function isCommitAncestor(cwd, ancestor, descendant) {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
await git.raw(["merge-base", "--is-ancestor", ancestor, descendant]);
|
|
23
|
-
return true;
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
99
|
+
const result = await runGitProcess(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]);
|
|
100
|
+
return result.status === 0 && result.error === undefined;
|
|
28
101
|
}
|
|
29
102
|
export async function getHeadCommitHash(cwd) {
|
|
30
|
-
const git = createGit(cwd);
|
|
31
103
|
try {
|
|
32
|
-
const output = await
|
|
104
|
+
const output = (await runGitOrThrow(cwd, ["rev-parse", "HEAD"])).stdout;
|
|
33
105
|
return output.trim();
|
|
34
106
|
}
|
|
35
107
|
catch (error) {
|
|
@@ -62,16 +134,15 @@ export async function resolveCommitRefs(cwd, refs) {
|
|
|
62
134
|
export async function renderCommitStatPreview(cwd, commits) {
|
|
63
135
|
if (commits.length === 0)
|
|
64
136
|
return "No delivery commits recorded.";
|
|
65
|
-
const git = createGit(cwd);
|
|
66
137
|
try {
|
|
67
|
-
const chunks = await Promise.all(commits.map(async (commit) => (await
|
|
138
|
+
const chunks = await Promise.all(commits.map(async (commit) => (await runGitOrThrow(cwd, [
|
|
68
139
|
"show",
|
|
69
140
|
"--stat",
|
|
70
141
|
"--summary",
|
|
71
142
|
"--format=commit %H%n%s",
|
|
72
143
|
"--no-renames",
|
|
73
144
|
commit,
|
|
74
|
-
])).trim()));
|
|
145
|
+
])).stdout.trim()));
|
|
75
146
|
return chunks.filter((chunk) => chunk.length > 0).join("\n\n");
|
|
76
147
|
}
|
|
77
148
|
catch (error) {
|
package/build/git/core.js
CHANGED
|
@@ -1,31 +1,10 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import { simpleGit } from "simple-git";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
3
|
import { normalizeFilesystemCoordinate } from "../fs/path-coordinate.js";
|
|
4
|
-
import { getConfig } from "../config/env.js";
|
|
5
4
|
import { appendDebugBlock } from "../telemetry/debug-log.js";
|
|
5
|
+
import { gitProcessEnv, resolveGitExecutable, resolveGitTimeoutMs } from "./process.js";
|
|
6
|
+
export { GIT_ABORT_FORCE_KILL_MS, gitProcessEnv, openGitProcess, resolveGitExecutable, resolveGitTimeoutMs, runGitOrThrow, runGitProcess, runGitProcessBytes, terminateGitChild, } from "./process.js";
|
|
6
7
|
const GIT_ERROR_COMPACT_MAX_CHARS = 4000;
|
|
7
|
-
export const GIT_ABORT_FORCE_KILL_MS = 250;
|
|
8
|
-
/** Terminate one non-detached Git child and wait until Node drains its stdio close. */
|
|
9
|
-
export async function terminateGitChild(child, closed, graceMs = GIT_ABORT_FORCE_KILL_MS) {
|
|
10
|
-
if (child.exitCode === null && child.signalCode === null)
|
|
11
|
-
child.kill("SIGTERM");
|
|
12
|
-
let grace;
|
|
13
|
-
const exited = await Promise.race([
|
|
14
|
-
closed.then(() => true),
|
|
15
|
-
new Promise((resolve) => {
|
|
16
|
-
grace = setTimeout(() => resolve(false), graceMs);
|
|
17
|
-
grace.unref();
|
|
18
|
-
}),
|
|
19
|
-
]);
|
|
20
|
-
if (grace)
|
|
21
|
-
clearTimeout(grace);
|
|
22
|
-
// Only the close event proves process exit and stdio drain. `killed` and
|
|
23
|
-
// `signalCode` merely report signal delivery and can be set while a handler
|
|
24
|
-
// keeps the child alive.
|
|
25
|
-
if (!exited)
|
|
26
|
-
child.kill("SIGKILL");
|
|
27
|
-
await closed;
|
|
28
|
-
}
|
|
29
8
|
export const MISSING_GIT_CONFIG_PATTERNS = ["No such section or key"];
|
|
30
9
|
export const NOT_GIT_REPOSITORY_PATTERNS = ["not a git repository"];
|
|
31
10
|
export const MISSING_HEAD_PATTERNS = [
|
|
@@ -41,8 +20,9 @@ export function isFullGitObjectId(value) {
|
|
|
41
20
|
/** Git is an optional adapter for directory-local product surfaces. */
|
|
42
21
|
export function isGitExecutableUnavailable(error) {
|
|
43
22
|
const source = (error ?? {});
|
|
44
|
-
if (source.code === "ENOENT")
|
|
45
|
-
return
|
|
23
|
+
if (source.code === "ENOENT") {
|
|
24
|
+
return typeof source.gitCwd !== "string" || existsSync(source.gitCwd);
|
|
25
|
+
}
|
|
46
26
|
const message = typeof source.message === "string" ? source.message : String(error);
|
|
47
27
|
return /spawn\s+git\s+ENOENT|git(?:\.exe)?["']?\s*:\s*(?:not found|no such file)/i.test(message);
|
|
48
28
|
}
|
|
@@ -93,146 +73,14 @@ export function wrapGitError(commandLabel, err, cwd) {
|
|
|
93
73
|
}
|
|
94
74
|
return wrapped;
|
|
95
75
|
}
|
|
96
|
-
/** Configured Git timeout, with a bootstrap-safe default before env loads. */
|
|
97
|
-
export function resolveGitTimeoutMs() {
|
|
98
|
-
try {
|
|
99
|
-
return getConfig().git.timeoutMs;
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
// Git coordinates and early probes remain usable before config bootstrap.
|
|
103
|
-
return 60_000;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
/** Non-interactive Git process environment shared by one-shot and streaming gates. */
|
|
107
|
-
export function gitProcessEnv(extra) {
|
|
108
|
-
return {
|
|
109
|
-
...process.env,
|
|
110
|
-
...extra,
|
|
111
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
112
|
-
GCM_INTERACTIVE: "Never",
|
|
113
|
-
GIT_MERGE_AUTOEDIT: "no",
|
|
114
|
-
LC_ALL: "C",
|
|
115
|
-
PATH: extra?.PATH ?? process.env.PATH ?? "",
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
76
|
export function createGit(cwd) {
|
|
119
77
|
const timeoutMs = resolveGitTimeoutMs();
|
|
120
78
|
const git = simpleGit(normalizeFilesystemCoordinate(cwd), {
|
|
79
|
+
binary: resolveGitExecutable(),
|
|
121
80
|
timeout: { block: timeoutMs, stdErr: true, stdOut: true },
|
|
81
|
+
unsafe: { allowUnsafeCustomBinary: true },
|
|
122
82
|
});
|
|
123
83
|
// simple-git replaces its child environment when configured through env().
|
|
124
|
-
|
|
125
|
-
// behavior such as a pager into command execution.
|
|
126
|
-
const traceEnvironment = Object.fromEntries(Object.entries(process.env).filter(([key]) => key.startsWith("GIT_TRACE2")));
|
|
127
|
-
git.env({
|
|
128
|
-
...traceEnvironment,
|
|
129
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
130
|
-
GCM_INTERACTIVE: "Never",
|
|
131
|
-
GIT_MERGE_AUTOEDIT: "no",
|
|
132
|
-
LC_ALL: "C",
|
|
133
|
-
PATH: process.env.PATH ?? "",
|
|
134
|
-
});
|
|
84
|
+
git.env(gitProcessEnv());
|
|
135
85
|
return git;
|
|
136
86
|
}
|
|
137
|
-
/**
|
|
138
|
-
* Asynchronous one-shot Git process boundary that preserves stdout/stderr/status facts.
|
|
139
|
-
* Prefer `createGit` when non-zero exits are fatal; use this when callers need
|
|
140
|
-
* soft status codes or stdin (simple-git drops empty-stderr non-zero exits).
|
|
141
|
-
* Long-lived multi-request stdin/stdout sessions use `openGitStreamingBatch`.
|
|
142
|
-
*/
|
|
143
|
-
export async function runGitProcessBytes(cwd, args, options) {
|
|
144
|
-
options?.signal?.throwIfAborted();
|
|
145
|
-
const timeoutMs = resolveGitTimeoutMs();
|
|
146
|
-
const env = gitProcessEnv(options?.env);
|
|
147
|
-
return await new Promise((resolve) => {
|
|
148
|
-
const child = spawn("git", [...args], {
|
|
149
|
-
cwd: normalizeFilesystemCoordinate(cwd),
|
|
150
|
-
env,
|
|
151
|
-
shell: false,
|
|
152
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
153
|
-
});
|
|
154
|
-
const stdoutChunks = [];
|
|
155
|
-
const stderrChunks = [];
|
|
156
|
-
let settled = false;
|
|
157
|
-
let timedOut = false;
|
|
158
|
-
let aborting = false;
|
|
159
|
-
let terminationPromise;
|
|
160
|
-
const closed = new Promise((resolveClosed) => child.once("close", () => resolveClosed()));
|
|
161
|
-
const finish = (result) => {
|
|
162
|
-
if (settled)
|
|
163
|
-
return;
|
|
164
|
-
settled = true;
|
|
165
|
-
clearTimeout(timeout);
|
|
166
|
-
options?.signal?.removeEventListener("abort", abort);
|
|
167
|
-
resolve(result);
|
|
168
|
-
};
|
|
169
|
-
const abort = () => {
|
|
170
|
-
if (aborting)
|
|
171
|
-
return;
|
|
172
|
-
aborting = true;
|
|
173
|
-
terminationPromise = terminateGitChild(child, closed);
|
|
174
|
-
};
|
|
175
|
-
options?.signal?.addEventListener("abort", abort, { once: true });
|
|
176
|
-
if (options?.signal?.aborted)
|
|
177
|
-
abort();
|
|
178
|
-
const timeout = setTimeout(() => {
|
|
179
|
-
timedOut = true;
|
|
180
|
-
child.kill("SIGKILL");
|
|
181
|
-
}, timeoutMs);
|
|
182
|
-
if (options?.input !== undefined) {
|
|
183
|
-
if (typeof options.input === "string")
|
|
184
|
-
child.stdin.end(options.input, "utf8");
|
|
185
|
-
else
|
|
186
|
-
child.stdin.end(options.input);
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
child.stdin.end();
|
|
190
|
-
}
|
|
191
|
-
child.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
|
|
192
|
-
child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
|
|
193
|
-
child.on("error", (error) => {
|
|
194
|
-
finish({
|
|
195
|
-
stdout: Buffer.concat(stdoutChunks),
|
|
196
|
-
stderr: Buffer.concat(stderrChunks),
|
|
197
|
-
status: null,
|
|
198
|
-
error,
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
child.on("close", async (code, signal) => {
|
|
202
|
-
if (terminationPromise)
|
|
203
|
-
await terminationPromise;
|
|
204
|
-
const stdout = Buffer.concat(stdoutChunks);
|
|
205
|
-
const stderr = Buffer.concat(stderrChunks);
|
|
206
|
-
if (timedOut) {
|
|
207
|
-
finish({
|
|
208
|
-
stdout,
|
|
209
|
-
stderr,
|
|
210
|
-
status: null,
|
|
211
|
-
error: new Error("git timed out"),
|
|
212
|
-
});
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
if (signal) {
|
|
216
|
-
finish({
|
|
217
|
-
stdout,
|
|
218
|
-
stderr,
|
|
219
|
-
status: null,
|
|
220
|
-
error: options?.signal?.aborted
|
|
221
|
-
? options.signal.reason instanceof Error ? options.signal.reason : new DOMException("The operation was aborted", "AbortError")
|
|
222
|
-
: new Error(`git exited from signal ${signal}`),
|
|
223
|
-
});
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
finish({ stdout, stderr, status: code });
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
export async function runGitProcess(cwd, args, options) {
|
|
231
|
-
const result = await runGitProcessBytes(cwd, args, options);
|
|
232
|
-
return {
|
|
233
|
-
stdout: result.stdout.toString("utf8"),
|
|
234
|
-
stderr: result.stderr.toString("utf8"),
|
|
235
|
-
status: result.status,
|
|
236
|
-
...(result.error ? { error: result.error } : {}),
|
|
237
|
-
};
|
|
238
|
-
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { wrapGitError } from "../core.js";
|
|
2
|
+
import { runGitOrThrow } from "../process.js";
|
|
2
3
|
import { diffPathspecArgs } from "./pathspec.js";
|
|
3
4
|
export async function getDiffStats(cwd, baseBranch, options = {}) {
|
|
4
|
-
const git = createGit(cwd);
|
|
5
5
|
let rawNumStat;
|
|
6
6
|
try {
|
|
7
|
-
rawNumStat = await
|
|
7
|
+
rawNumStat = (await runGitOrThrow(cwd, [
|
|
8
8
|
"diff",
|
|
9
9
|
"--numstat",
|
|
10
10
|
`${baseBranch}...HEAD`,
|
|
11
11
|
...diffPathspecArgs({ excludePaths: options.excludePaths }),
|
|
12
|
-
]);
|
|
12
|
+
])).stdout;
|
|
13
13
|
}
|
|
14
14
|
catch (err) {
|
|
15
15
|
throw wrapGitError(`diff --numstat ${baseBranch}...HEAD`, err, cwd);
|
package/build/git/diff/read.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { wrapGitError } from "../core.js";
|
|
2
|
+
import { runGitOrThrow } from "../process.js";
|
|
2
3
|
import { diffPathspecArgs } from "./pathspec.js";
|
|
3
4
|
import { isMissingBaseRevisionError, splitDiffByFile, totalJoinedLength, truncateDiffBlockToCharBudget, truncateDiffSectionByLineCount, truncateTextWithFooter, } from "./parsers.js";
|
|
4
5
|
function normalizeDiffTarget(target) {
|
|
@@ -66,7 +67,6 @@ function renderPatchDiff(output, options) {
|
|
|
66
67
|
return renderedSections.join("\n\n").trim() || "No diff.";
|
|
67
68
|
}
|
|
68
69
|
export async function readDiff(cwd, target, format, options = {}) {
|
|
69
|
-
const git = createGit(cwd);
|
|
70
70
|
const formattedTarget = formatDiffTarget(target);
|
|
71
71
|
try {
|
|
72
72
|
if (format === "stat") {
|
|
@@ -75,7 +75,7 @@ export async function readDiff(cwd, target, format, options = {}) {
|
|
|
75
75
|
const args = statVariant === "numstat"
|
|
76
76
|
? ["diff", "--numstat", ...normalizeDiffTarget(target), ...pathspec]
|
|
77
77
|
: ["diff", "--stat=120,80", "--compact-summary", ...normalizeDiffTarget(target), ...pathspec];
|
|
78
|
-
const output = await
|
|
78
|
+
const output = (await runGitOrThrow(cwd, args)).stdout;
|
|
79
79
|
if (statVariant !== "summary") {
|
|
80
80
|
return output.trim() || "No diff.";
|
|
81
81
|
}
|
|
@@ -88,7 +88,7 @@ export async function readDiff(cwd, target, format, options = {}) {
|
|
|
88
88
|
}
|
|
89
89
|
return truncateTextWithFooter(trimmed, maxChars, trimmed.length - maxChars);
|
|
90
90
|
}
|
|
91
|
-
const output = await
|
|
91
|
+
const output = (await runGitOrThrow(cwd, buildPatchCommand(target, options))).stdout;
|
|
92
92
|
return renderPatchDiff(output, options);
|
|
93
93
|
}
|
|
94
94
|
catch (error) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { wrapGitError } from "../core.js";
|
|
2
|
+
import { runGitOrThrow } from "../process.js";
|
|
2
3
|
import { diffPathspecArgs } from "./pathspec.js";
|
|
3
4
|
import { parseHunkRange, parseUnifiedHunks, splitDiffByFile } from "./parsers.js";
|
|
4
5
|
function parseRawDiff(output) {
|
|
@@ -88,14 +89,13 @@ function parsePatchHunks(section) {
|
|
|
88
89
|
});
|
|
89
90
|
}
|
|
90
91
|
export async function readStructuredDiff(cwd, from, to, options = {}) {
|
|
91
|
-
const git = createGit(cwd);
|
|
92
92
|
const range = `${from}..${to}`;
|
|
93
93
|
const pathspec = diffPathspecArgs({ excludePaths: options.excludePaths });
|
|
94
94
|
try {
|
|
95
95
|
const [raw, numstat, patch] = await Promise.all([
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
runGitOrThrow(cwd, ["diff", "--raw", "-z", "--find-renames", range, ...pathspec]).then((result) => result.stdout),
|
|
97
|
+
runGitOrThrow(cwd, ["diff", "--numstat", "-z", "--find-renames", range, ...pathspec]).then((result) => result.stdout),
|
|
98
|
+
runGitOrThrow(cwd, ["diff", "--no-color", "--no-ext-diff", "--unified=3", "--find-renames", range, ...pathspec]).then((result) => result.stdout),
|
|
99
99
|
]);
|
|
100
100
|
const rawFiles = parseRawDiff(raw);
|
|
101
101
|
const stats = parseNumstat(numstat);
|