@a2f0/agent-tool 0.1.46 → 0.1.47
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/package.json +1 -1
- package/src/cli.ts +2 -4
- package/src/workflows/core.ts +31 -1
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
1
|
import { readFileSync, writeFileSync, writeSync } from "node:fs";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { DEFAULT_CONFIG, loadConfig } from "./config";
|
|
@@ -28,6 +27,7 @@ import { hooksCommand } from "./workflows/hooks";
|
|
|
28
27
|
import { repoCommand, policyCommand, toolchainCommand } from "./workflows/repository";
|
|
29
28
|
import { prWorkflowCommand } from "./workflows/pr";
|
|
30
29
|
import { releaseCommand } from "./release/commands";
|
|
30
|
+
import { checkoutRoot } from "./workflows/core";
|
|
31
31
|
import { procCommand, teardownCommand } from "./processes/commands";
|
|
32
32
|
|
|
33
33
|
export const HELP = `agent-tool ${metadata.version}
|
|
@@ -96,9 +96,7 @@ returned; inspect VERDICT before shipping. Skill install previews unless --apply
|
|
|
96
96
|
|
|
97
97
|
function output(value: unknown): void { writeSync(1, `${JSON.stringify(value, null, 2)}\n`); }
|
|
98
98
|
function usage(condition: boolean, message: string): void { if (!condition) throw new Error(message); }
|
|
99
|
-
function gitRoot(): string {
|
|
100
|
-
return execFileSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim();
|
|
101
|
-
}
|
|
99
|
+
function gitRoot(): string { return checkoutRoot(); }
|
|
102
100
|
|
|
103
101
|
export function main(argv = process.argv.slice(2)): number | Promise<number> {
|
|
104
102
|
const args = [...argv];
|
package/src/workflows/core.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
2
|
-
import { writeSync } from "node:fs";
|
|
2
|
+
import { lstatSync, realpathSync, writeSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
3
4
|
import { gitEnvironment } from "../git/environment";
|
|
4
5
|
|
|
5
6
|
export function output(value: unknown): void { writeSync(1, `${JSON.stringify(value, null, 2)}\n`); }
|
|
@@ -23,8 +24,37 @@ export function currentBranch(): string {
|
|
|
23
24
|
assert(branch, "Detached HEAD; supply a branch name to checks or attach a branch before mutations.");
|
|
24
25
|
return branch;
|
|
25
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Git skips a `.git` it cannot open (unreadable, or without HEAD while it is
|
|
29
|
+
* rewritten) and keeps searching upward, so a checkout nested inside another
|
|
30
|
+
* repository, such as a workspace's `checkouts/<name>`, would silently resolve
|
|
31
|
+
* to the outer one. Refuse that instead of acting on another repository.
|
|
32
|
+
*/
|
|
33
|
+
export function assertOwnCheckout(root: string, cwd = process.cwd()): void {
|
|
34
|
+
const top = realpathSync(root);
|
|
35
|
+
const relative = path.relative(top, realpathSync(cwd));
|
|
36
|
+
if (!relative || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) return;
|
|
37
|
+
let current = top;
|
|
38
|
+
for (const part of relative.split(path.sep)) {
|
|
39
|
+
current = path.join(current, part);
|
|
40
|
+
assert(!hasEntry(path.join(current, ".git")), `${current} has its own .git, but Git resolved the enclosing repository ${top}; the nested checkout's Git directory is unreadable or incomplete (mid-checkout?). Retry once it is readable.`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** Whether anything, even a dangling link, exists at `file`; an entry that cannot be inspected counts. */
|
|
44
|
+
function hasEntry(file: string): boolean {
|
|
45
|
+
try { lstatSync(file); return true; } catch (error) { return (error as NodeJS.ErrnoException).code !== "ENOENT"; }
|
|
46
|
+
}
|
|
47
|
+
/** The top level of the checkout Git resolves from `cwd`, never an enclosing repository. */
|
|
48
|
+
export function checkoutRoot(cwd = process.cwd()): string {
|
|
49
|
+
const root = git(["rev-parse", "--show-toplevel"], cwd);
|
|
50
|
+
assertOwnCheckout(root, cwd);
|
|
51
|
+
return root;
|
|
52
|
+
}
|
|
26
53
|
export interface Repository { nameWithOwner: string; defaultBranchRef: { name: string }; url: string; sshUrl: string }
|
|
27
54
|
export function repository(): Repository {
|
|
55
|
+
// gh resolves the repository from the same Git discovery; outside any checkout it may still use GH_REPO.
|
|
56
|
+
const top = spawnSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], env: gitEnvironment() });
|
|
57
|
+
if (top.status === 0) assertOwnCheckout(top.stdout.trim());
|
|
28
58
|
const repo = json<Repository>(["repo", "view", "--json", "nameWithOwner,defaultBranchRef,url,sshUrl"]);
|
|
29
59
|
assert(/^[^/\s]+\/[^/\s]+$/.test(repo.nameWithOwner) && repo.defaultBranchRef?.name, "Could not resolve repository identity and default branch.");
|
|
30
60
|
return repo;
|