@gethmy/harness 1.4.0 → 1.6.0
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/dist/cli.js +736 -154
- package/dist/index.js +886 -424
- package/package.json +2 -2
- package/src/ci-failure.ts +10 -5
- package/src/exec-types.ts +69 -7
- package/src/gate-collectors.ts +30 -4
- package/src/git-diff-stat.ts +2 -1
- package/src/git-pr.ts +59 -25
- package/src/index.ts +1 -0
- package/src/pm.ts +8 -3
- package/src/revert-guard.ts +9 -2
- package/src/run-containment.ts +1312 -0
- package/src/sdk-agent-runner.ts +19 -0
- package/src/verification.ts +295 -66
- package/src/worktree.ts +149 -57
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gethmy/harness",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Execution motor for Harmony playbook stages. Runs exactly one stage per invocation: worktree, role-separated subagents, held oracle, gate evidence. It never routes, never judges, never pushes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@anthropic-ai/claude-agent-sdk": "^0.3.178",
|
|
58
|
-
"@gethmy/mcp": "3.
|
|
58
|
+
"@gethmy/mcp": "3.2.0",
|
|
59
59
|
"@supabase/supabase-js": "2.95.3"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
package/src/ci-failure.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { execFile, execFileSync } from "node:child_process";
|
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
3
|
import type { GitProvider } from "./git-pr.js";
|
|
4
4
|
import { log } from "./log.js";
|
|
5
|
+
import { GIT_NO_HOOKS } from "./run-containment.js";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Reading a red CI in enough detail to act on it (card #981).
|
|
@@ -137,11 +138,15 @@ export function isPrOnOrigin(prUrl: string, cwd: string): boolean {
|
|
|
137
138
|
const prSlug = githubSlug(prUrl);
|
|
138
139
|
if (!prSlug) return false;
|
|
139
140
|
try {
|
|
140
|
-
const remote = execFileSync(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
141
|
+
const remote = execFileSync(
|
|
142
|
+
"git",
|
|
143
|
+
[...GIT_NO_HOOKS, "remote", "get-url", "origin"],
|
|
144
|
+
{
|
|
145
|
+
cwd,
|
|
146
|
+
encoding: "utf-8",
|
|
147
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
148
|
+
},
|
|
149
|
+
).trim();
|
|
145
150
|
return githubSlug(remote) === prSlug;
|
|
146
151
|
} catch {
|
|
147
152
|
return false;
|
package/src/exec-types.ts
CHANGED
|
@@ -58,13 +58,11 @@ export interface VerificationConfig {
|
|
|
58
58
|
* until then this is the fallback for all cards.)
|
|
59
59
|
*/
|
|
60
60
|
model: string;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
*/
|
|
67
|
-
leanSettingSources: string;
|
|
61
|
+
// `leanSettingSources` was removed in #988. The three spawns that read it —
|
|
62
|
+
// review, auto-fix and deep-review — are now pinned to the containment's
|
|
63
|
+
// own `settingSources`, so an operator value here could only ever widen a
|
|
64
|
+
// security boundary. Removed rather than ignored, so a config that sets it
|
|
65
|
+
// fails to typecheck instead of appearing to work.
|
|
68
66
|
};
|
|
69
67
|
worktree: {
|
|
70
68
|
baseBranch: string;
|
|
@@ -97,9 +95,73 @@ export interface VerificationConfig {
|
|
|
97
95
|
* report a timeout as a test failure.
|
|
98
96
|
*/
|
|
99
97
|
testTimeout: number;
|
|
98
|
+
/**
|
|
99
|
+
* Container image for the verification steps (#1036). Empty — the default —
|
|
100
|
+
* runs them in the daemon process as before.
|
|
101
|
+
*
|
|
102
|
+
* `runBuild` / `runTests` / `runFormatFix` / `runLint` execute
|
|
103
|
+
* `bun run <script>` out of the WORKTREE'S OWN `package.json`, which a
|
|
104
|
+
* contained implement run is allowed to rewrite (the worktree is inside
|
|
105
|
+
* `sandbox.filesystem.allowWrite` by design). #988 gave those spawns
|
|
106
|
+
* `containedEnv()`, which removes the secret-shaped variables and was
|
|
107
|
+
* stated there as a reduction and not a fix: the command still ran as the
|
|
108
|
+
* operator and could read `~/.ssh` and `~/.harmony-mcp/config.json` off
|
|
109
|
+
* disk. Setting this moves them into `repair-sandbox.ts`'s container, where
|
|
110
|
+
* the only mount is the worktree and there is no network.
|
|
111
|
+
*
|
|
112
|
+
* **Opt-in on purpose, unlike #988's own containment.** That one could ship
|
|
113
|
+
* on by default because the SDK sandbox is inside the CLI. This one needs a
|
|
114
|
+
* reachable Docker AND an image carrying the repo's toolchain, so a default
|
|
115
|
+
* would stop verification dead on every host that has neither. There is
|
|
116
|
+
* deliberately no guessed image, for the reason `types.ts` already records
|
|
117
|
+
* for the repair path: a container that cannot build anything reports every
|
|
118
|
+
* run as unverified.
|
|
119
|
+
*
|
|
120
|
+
* The image must carry the toolchain — the container has no network, so
|
|
121
|
+
* nothing installs at verification time. Dependencies are already present:
|
|
122
|
+
* `worktree.ts` installs them at worktree creation with `--ignore-scripts`.
|
|
123
|
+
* Point this at whatever your CI uses.
|
|
124
|
+
*
|
|
125
|
+
* Does NOT cover the two `dev` servers (`runDeepReview` and the review
|
|
126
|
+
* worker's). Those are probed over HTTP from the daemon, so a no-network
|
|
127
|
+
* container is unreachable by construction; they remain in
|
|
128
|
+
* `run-containment.ts`'s residual list.
|
|
129
|
+
*/
|
|
130
|
+
sandboxImage: string;
|
|
100
131
|
};
|
|
101
132
|
}
|
|
102
133
|
|
|
134
|
+
/**
|
|
135
|
+
* A configured verification container (#1036), or `undefined` for host
|
|
136
|
+
* execution. Resolved once by the caller from {@link VerificationConfig} so the
|
|
137
|
+
* four steps need no config import and stay unit-testable.
|
|
138
|
+
*/
|
|
139
|
+
export interface VerificationSandbox {
|
|
140
|
+
/** Image carrying the repo's toolchain. Empty means "not configured". */
|
|
141
|
+
image: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Head-room added to a step's own timeout when it runs in a container (#1036).
|
|
146
|
+
*
|
|
147
|
+
* There is deliberately NO separate sandbox timeout knob. Each step already
|
|
148
|
+
* carries the cap its operator chose — `verification.timeout` for build, lint
|
|
149
|
+
* and format, `verification.testTimeout` for the suite, and those two are
|
|
150
|
+
* separate precisely because a real suite outruns a build. A single
|
|
151
|
+
* container-wide cap would silently replace all four: with the values this repo
|
|
152
|
+
* ships, turning an image on would have taken build and lint from 2 minutes to
|
|
153
|
+
* 10, and would have ignored a `testTimeout` an operator had narrowed for a
|
|
154
|
+
* fast suite. Switching on containment must not change what any other setting
|
|
155
|
+
* means.
|
|
156
|
+
*
|
|
157
|
+
* So the step's own timeout wins, and this covers only what the container adds
|
|
158
|
+
* on top: image resolution and process start. Measured at ~5.1 s on macOS /
|
|
159
|
+
* Docker Desktop with a warm image; 60 s is that with an order of magnitude of
|
|
160
|
+
* head-room for a slower host or a cold layer, and it is not a budget for the
|
|
161
|
+
* command, which has already been capped by the time it is added.
|
|
162
|
+
*/
|
|
163
|
+
export const SANDBOX_STARTUP_GRACE_MS = 60_000;
|
|
164
|
+
|
|
103
165
|
/** The slice of the daemon's config that the git helpers actually read. */
|
|
104
166
|
export interface WorktreeConfig {
|
|
105
167
|
worktree: VerificationConfig["worktree"];
|
package/src/gate-collectors.ts
CHANGED
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
CommandMetricCollector,
|
|
47
47
|
type CommandMetricDeps,
|
|
48
48
|
} from "./command-metric.js";
|
|
49
|
+
import type { VerificationSandbox } from "./exec-types.js";
|
|
49
50
|
import { log } from "./log.js";
|
|
50
51
|
import type { OracleDeps } from "./oracle.js";
|
|
51
52
|
import { OracleCollector, OracleRedCollector } from "./oracle-collector.js";
|
|
@@ -152,10 +153,25 @@ export interface BuildGreenDeps {
|
|
|
152
153
|
worktreePath: string;
|
|
153
154
|
buildTimeout: number;
|
|
154
155
|
lintTimeout: number;
|
|
156
|
+
/**
|
|
157
|
+
* Verification container (#1036). Passed straight through to `runBuild` /
|
|
158
|
+
* `runLint`, so the gate's build runs wherever the daemon's own verification
|
|
159
|
+
* runs — a gate that ran the worktree's scripts in this process while
|
|
160
|
+
* `runVerification` contained them would be the hole under a different name.
|
|
161
|
+
*/
|
|
162
|
+
sandbox?: VerificationSandbox;
|
|
155
163
|
/** Inject for tests; defaults to verification.ts `runBuild`. Returns error lines. */
|
|
156
|
-
runBuild?: (
|
|
164
|
+
runBuild?: (
|
|
165
|
+
worktreePath: string,
|
|
166
|
+
timeout: number,
|
|
167
|
+
sandbox?: VerificationSandbox,
|
|
168
|
+
) => string[] | Promise<string[]>;
|
|
157
169
|
/** Inject for tests; defaults to verification.ts `runLint`. Returns warning lines. */
|
|
158
|
-
runLint?: (
|
|
170
|
+
runLint?: (
|
|
171
|
+
worktreePath: string,
|
|
172
|
+
timeout: number,
|
|
173
|
+
sandbox?: VerificationSandbox,
|
|
174
|
+
) => string[] | Promise<string[]>;
|
|
159
175
|
}
|
|
160
176
|
|
|
161
177
|
/**
|
|
@@ -192,8 +208,18 @@ export class BuildGreenCollector implements GateEvidenceCollector {
|
|
|
192
208
|
async collect(_context: GateEvidenceContext): Promise<GateEvidence> {
|
|
193
209
|
const doBuild = this.deps.runBuild ?? runBuild;
|
|
194
210
|
const doLint = this.deps.runLint ?? runLint;
|
|
195
|
-
|
|
196
|
-
|
|
211
|
+
// Awaited: both are async since #1036, and an injected test double may
|
|
212
|
+
// still be sync — `await` accepts either.
|
|
213
|
+
const buildErrors = await doBuild(
|
|
214
|
+
this.deps.worktreePath,
|
|
215
|
+
this.deps.buildTimeout,
|
|
216
|
+
this.deps.sandbox,
|
|
217
|
+
);
|
|
218
|
+
const lintWarnings = await doLint(
|
|
219
|
+
this.deps.worktreePath,
|
|
220
|
+
this.deps.lintTimeout,
|
|
221
|
+
this.deps.sandbox,
|
|
222
|
+
);
|
|
197
223
|
const buildPassed = buildErrors.length === 0;
|
|
198
224
|
const lintPassed = lintWarnings.length === 0;
|
|
199
225
|
// Match verification.ts: only a failing BUILD makes the gate red. Lint
|
package/src/git-diff-stat.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { log } from "./log.js";
|
|
3
|
+
import { GIT_NO_HOOKS } from "./run-containment.js";
|
|
3
4
|
|
|
4
5
|
const TAG = "git-diff-stat";
|
|
5
6
|
|
|
@@ -134,7 +135,7 @@ export function captureDiffStat(
|
|
|
134
135
|
try {
|
|
135
136
|
const raw = execFileSync(
|
|
136
137
|
"git",
|
|
137
|
-
["diff", "--numstat", `${baseBranch}...HEAD`],
|
|
138
|
+
[...GIT_NO_HOOKS, "diff", "--numstat", `${baseBranch}...HEAD`],
|
|
138
139
|
{ cwd: worktreePath, encoding: "utf-8", timeout: 30_000 },
|
|
139
140
|
);
|
|
140
141
|
return parseNumstat(raw, maxFiles);
|
package/src/git-pr.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { Card } from "@harmony/shared";
|
|
|
4
4
|
import { SAFE_GIT_REF_PATTERN } from "@harmony/shared";
|
|
5
5
|
import type { MergeStrategy, WorktreeConfig } from "./exec-types.js";
|
|
6
6
|
import { log } from "./log.js";
|
|
7
|
+
import { GIT_NO_HOOKS } from "./run-containment.js";
|
|
7
8
|
|
|
8
9
|
// Lazy + memoised: promisify(execFile) has no reason to run at module load.
|
|
9
10
|
// git-pr.ts sits in the motor's public barrel (@gethmy/harness `export *`), so
|
|
@@ -33,10 +34,14 @@ export type GitProvider =
|
|
|
33
34
|
|
|
34
35
|
export function detectGitProvider(cwd?: string): GitProvider {
|
|
35
36
|
try {
|
|
36
|
-
const url = execFileSync(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
const url = execFileSync(
|
|
38
|
+
"git",
|
|
39
|
+
[...GIT_NO_HOOKS, "remote", "get-url", "origin"],
|
|
40
|
+
{
|
|
41
|
+
cwd,
|
|
42
|
+
encoding: "utf-8",
|
|
43
|
+
},
|
|
44
|
+
).trim();
|
|
40
45
|
|
|
41
46
|
if (url.includes("github.com")) return "github";
|
|
42
47
|
if (url.includes("dev.azure.com") || url.includes("visualstudio.com"))
|
|
@@ -671,7 +676,7 @@ export async function mergePullRequest(
|
|
|
671
676
|
|
|
672
677
|
export function getHeadSha(cwd: string): string | null {
|
|
673
678
|
try {
|
|
674
|
-
return execFileSync("git", ["rev-parse", "HEAD"], {
|
|
679
|
+
return execFileSync("git", [...GIT_NO_HOOKS, "rev-parse", "HEAD"], {
|
|
675
680
|
cwd,
|
|
676
681
|
encoding: "utf-8",
|
|
677
682
|
}).trim();
|
|
@@ -947,7 +952,13 @@ export function remoteBranchExists(branchName: string, cwd: string): boolean {
|
|
|
947
952
|
try {
|
|
948
953
|
execFileSync(
|
|
949
954
|
"git",
|
|
950
|
-
[
|
|
955
|
+
[
|
|
956
|
+
...GIT_NO_HOOKS,
|
|
957
|
+
"ls-remote",
|
|
958
|
+
"--exit-code",
|
|
959
|
+
"origin",
|
|
960
|
+
`refs/heads/${branchName}`,
|
|
961
|
+
],
|
|
951
962
|
{ cwd, stdio: "pipe" },
|
|
952
963
|
);
|
|
953
964
|
return true;
|
|
@@ -965,13 +976,13 @@ export function pushBranch(branchName: string, cwd: string): void {
|
|
|
965
976
|
// lets a concurrent update slip through. Fetch + pin the expected SHA.
|
|
966
977
|
let expectedSha: string | null = null;
|
|
967
978
|
try {
|
|
968
|
-
execFileSync("git", ["fetch", "origin", branchName], {
|
|
979
|
+
execFileSync("git", [...GIT_NO_HOOKS, "fetch", "origin", branchName], {
|
|
969
980
|
cwd,
|
|
970
981
|
stdio: "pipe",
|
|
971
982
|
});
|
|
972
983
|
expectedSha = execFileSync(
|
|
973
984
|
"git",
|
|
974
|
-
["rev-parse", `refs/remotes/origin/${branchName}`],
|
|
985
|
+
[...GIT_NO_HOOKS, "rev-parse", `refs/remotes/origin/${branchName}`],
|
|
975
986
|
{ cwd, encoding: "utf-8" },
|
|
976
987
|
).trim();
|
|
977
988
|
} catch (err) {
|
|
@@ -983,12 +994,16 @@ export function pushBranch(branchName: string, cwd: string): void {
|
|
|
983
994
|
const lease = expectedSha
|
|
984
995
|
? `--force-with-lease=refs/heads/${branchName}:${expectedSha}`
|
|
985
996
|
: "--force-with-lease";
|
|
986
|
-
execFileSync(
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
997
|
+
execFileSync(
|
|
998
|
+
"git",
|
|
999
|
+
[...GIT_NO_HOOKS, "push", lease, "-u", "origin", branchName],
|
|
1000
|
+
{
|
|
1001
|
+
cwd,
|
|
1002
|
+
stdio: "pipe",
|
|
1003
|
+
},
|
|
1004
|
+
);
|
|
990
1005
|
} else {
|
|
991
|
-
execFileSync("git", ["push", "-u", "origin", branchName], {
|
|
1006
|
+
execFileSync("git", [...GIT_NO_HOOKS, "push", "-u", "origin", branchName], {
|
|
992
1007
|
cwd,
|
|
993
1008
|
stdio: "pipe",
|
|
994
1009
|
});
|
|
@@ -1009,7 +1024,7 @@ export function renameRemoteBranch(
|
|
|
1009
1024
|
if (oldRef === newRef) return;
|
|
1010
1025
|
let sha: string;
|
|
1011
1026
|
try {
|
|
1012
|
-
sha = execFileSync("git", ["rev-parse", "HEAD"], {
|
|
1027
|
+
sha = execFileSync("git", [...GIT_NO_HOOKS, "rev-parse", "HEAD"], {
|
|
1013
1028
|
cwd,
|
|
1014
1029
|
encoding: "utf-8",
|
|
1015
1030
|
}).trim();
|
|
@@ -1021,14 +1036,24 @@ export function renameRemoteBranch(
|
|
|
1021
1036
|
log.info(TAG, `Renaming remote ${oldRef} → ${newRef}`);
|
|
1022
1037
|
execFileSync(
|
|
1023
1038
|
"git",
|
|
1024
|
-
[
|
|
1039
|
+
[
|
|
1040
|
+
...GIT_NO_HOOKS,
|
|
1041
|
+
"push",
|
|
1042
|
+
"origin",
|
|
1043
|
+
`${sha}:refs/heads/${newRef}`,
|
|
1044
|
+
"--force-with-lease",
|
|
1045
|
+
],
|
|
1025
1046
|
{ cwd, stdio: "pipe" },
|
|
1026
1047
|
);
|
|
1027
1048
|
try {
|
|
1028
|
-
execFileSync(
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1049
|
+
execFileSync(
|
|
1050
|
+
"git",
|
|
1051
|
+
[...GIT_NO_HOOKS, "push", "origin", `:refs/heads/${oldRef}`],
|
|
1052
|
+
{
|
|
1053
|
+
cwd,
|
|
1054
|
+
stdio: "pipe",
|
|
1055
|
+
},
|
|
1056
|
+
);
|
|
1032
1057
|
} catch (err) {
|
|
1033
1058
|
log.warn(
|
|
1034
1059
|
TAG,
|
|
@@ -1036,7 +1061,7 @@ export function renameRemoteBranch(
|
|
|
1036
1061
|
);
|
|
1037
1062
|
}
|
|
1038
1063
|
try {
|
|
1039
|
-
execFileSync("git", ["branch", "-m", oldRef, newRef], {
|
|
1064
|
+
execFileSync("git", [...GIT_NO_HOOKS, "branch", "-m", oldRef, newRef], {
|
|
1040
1065
|
cwd,
|
|
1041
1066
|
stdio: "pipe",
|
|
1042
1067
|
});
|
|
@@ -1055,10 +1080,14 @@ export function getBranchWebUrl(
|
|
|
1055
1080
|
cwd: string,
|
|
1056
1081
|
): string | null {
|
|
1057
1082
|
try {
|
|
1058
|
-
const remoteUrl = execFileSync(
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1083
|
+
const remoteUrl = execFileSync(
|
|
1084
|
+
"git",
|
|
1085
|
+
[...GIT_NO_HOOKS, "remote", "get-url", "origin"],
|
|
1086
|
+
{
|
|
1087
|
+
cwd,
|
|
1088
|
+
encoding: "utf-8",
|
|
1089
|
+
},
|
|
1090
|
+
).trim();
|
|
1062
1091
|
const encoded = branchName.split("/").map(encodeURIComponent).join("/");
|
|
1063
1092
|
if (/github\.com[:/]([^/]+)\/([^/.]+)/.test(remoteUrl)) {
|
|
1064
1093
|
const m = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/.]+?)(?:\.git)?$/);
|
|
@@ -1126,7 +1155,12 @@ export function createPullRequest(
|
|
|
1126
1155
|
try {
|
|
1127
1156
|
commitLog = execFileSync(
|
|
1128
1157
|
"git",
|
|
1129
|
-
[
|
|
1158
|
+
[
|
|
1159
|
+
...GIT_NO_HOOKS,
|
|
1160
|
+
"log",
|
|
1161
|
+
"--oneline",
|
|
1162
|
+
`origin/${config.worktree.baseBranch}..HEAD`,
|
|
1163
|
+
],
|
|
1130
1164
|
{ cwd: worktreePath, encoding: "utf-8" },
|
|
1131
1165
|
).trim();
|
|
1132
1166
|
} catch {
|
package/src/index.ts
CHANGED
|
@@ -34,6 +34,7 @@ export * from "./project-type.js";
|
|
|
34
34
|
export * from "./repair-sandbox.js";
|
|
35
35
|
export * from "./revert-guard.js";
|
|
36
36
|
export * from "./review-types.js";
|
|
37
|
+
export * from "./run-containment.js";
|
|
37
38
|
export * from "./run-sizing.js";
|
|
38
39
|
export * from "./runner.js";
|
|
39
40
|
export * from "./sdk-agent-runner.js";
|
package/src/pm.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { log } from "./log.js";
|
|
4
|
+
import { GIT_NO_HOOKS } from "./run-containment.js";
|
|
4
5
|
|
|
5
6
|
const TAG = "pm";
|
|
6
7
|
|
|
@@ -16,9 +17,13 @@ export function detectPackageManager(): PackageManager {
|
|
|
16
17
|
|
|
17
18
|
let repoRoot: string;
|
|
18
19
|
try {
|
|
19
|
-
repoRoot = execFileSync(
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
repoRoot = execFileSync(
|
|
21
|
+
"git",
|
|
22
|
+
[...GIT_NO_HOOKS, "rev-parse", "--show-toplevel"],
|
|
23
|
+
{
|
|
24
|
+
encoding: "utf-8",
|
|
25
|
+
},
|
|
26
|
+
).trim();
|
|
22
27
|
} catch {
|
|
23
28
|
repoRoot = process.cwd();
|
|
24
29
|
}
|
package/src/revert-guard.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { log } from "./log.js";
|
|
3
|
+
import { GIT_NO_HOOKS } from "./run-containment.js";
|
|
3
4
|
|
|
4
5
|
const TAG = "revert-guard";
|
|
5
6
|
|
|
@@ -44,7 +45,7 @@ export function filterTestFiles(paths: string[]): string[] {
|
|
|
44
45
|
*/
|
|
45
46
|
function refetchBase(worktreePath: string, baseBranch: string): void {
|
|
46
47
|
try {
|
|
47
|
-
execFileSync("git", ["fetch", "origin", baseBranch], {
|
|
48
|
+
execFileSync("git", [...GIT_NO_HOOKS, "fetch", "origin", baseBranch], {
|
|
48
49
|
cwd: worktreePath,
|
|
49
50
|
stdio: "pipe",
|
|
50
51
|
});
|
|
@@ -69,7 +70,13 @@ export function listDeletedFilesAgainstBase(
|
|
|
69
70
|
try {
|
|
70
71
|
const out = execFileSync(
|
|
71
72
|
"git",
|
|
72
|
-
[
|
|
73
|
+
[
|
|
74
|
+
...GIT_NO_HOOKS,
|
|
75
|
+
"diff",
|
|
76
|
+
"--diff-filter=D",
|
|
77
|
+
"--name-only",
|
|
78
|
+
`origin/${baseBranch}...HEAD`,
|
|
79
|
+
],
|
|
73
80
|
{ cwd: worktreePath, encoding: "utf-8" },
|
|
74
81
|
);
|
|
75
82
|
return out
|