Skip to content

Commit 88efea4

Browse files
omegent-app[bot]patroza
authored andcommitted
feat(tooling): agent ship gate with draft-free push and pr:ready
Adopt scanner-style husky hooks, lint-staged, and pnpm pr:ready so agents can push freely on drafts, validate once via the ship gate on publish or ready-PR pushes, and cannot undraft with raw gh pr ready. Rebased onto fork/changes and applied the ship-gate model across the fork's AGENTS.md: publish validation runs through the automated gate (pre-push on ready PRs / pnpm pr:ready), with per-layer stack CI kept as the explicit protected-branch exception. Pre-commit now runs format + lint (typecheck and tests stay in the ship gate). Node builtins in the new scripts follow the fork's namespace-import convention. Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
1 parent 331d917 commit 88efea4

19 files changed

Lines changed: 1207 additions & 106 deletions

.envrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Prefer the repo gh policy shim so coding agents cannot undraft with raw
2+
# `gh pr ready` (use `pnpm pr:ready`). Installed by scripts/install-git-hooks.mjs.
3+
PATH_add .tools/bin

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,9 @@ node_modules/
4040
*.log
4141
.env*
4242
!.env.example
43+
!.envrc
44+
# direnv
45+
.direnv
46+
# agent gh shim + local run state (ship-gate cache, etc.)
47+
.tools/
48+
.run/

.husky/pre-commit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pnpm lint-staged

.husky/pre-push

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Agent-only ship gate (humans: no-op). See scripts/agent-pre-push.mjs.
2+
# Draft / no PR: free agent push. Ready PR: vp check + typecheck + tests.
3+
# Publish: pnpm pr:ready (not raw gh pr ready). Agent gh shim: .tools/bin/gh.
4+
#
5+
# Humans: SKIP_AGENT_PREPUSH=1 git push
6+
# Agents: never SKIP_AGENT_PREPUSH / never --no-verify
7+
pnpm exec node scripts/agent-pre-push.mjs

AGENTS.md

Lines changed: 108 additions & 104 deletions
Large diffs are not rendered by default.

docs/internals/scripts.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@ authenticated.
5858
- `vp run typecheck`: Strict TypeScript checks for all packages.
5959
- `vp run test`: Runs workspace tests.
6060
- `vp run lint:mobile`: Mobile native static analysis (`scripts/mobile-native-static-check.ts`).
61+
- `pnpm pr:ready`: Agent publish path — runs the ship gate (`vp check`, `vpr typecheck`,
62+
`vp run test`), then marks the open draft PR ready. Do not use raw `gh pr ready` from coding
63+
agents; the `.tools/bin/gh` shim blocks undraft side channels (installed by
64+
`scripts/install-git-hooks.mjs` on `prepare`).
65+
- `pnpm test:agent-gate`: Unit tests for the agent pre-push / PR-state / gh-policy helpers.
66+
- Husky: `pre-commit` runs `pnpm lint-staged` (`vp fmt` on staged files + `vp lint --fix` on staged
67+
code files);
68+
`pre-push` runs `scripts/agent-pre-push.mjs` (agents only: free push on draft / no PR; full ship
69+
gate on ready PRs; SHA cache in `.run/agent-ship-gate.json`).
6170
- `node apps/server/scripts/t3-sqlite-state.ts <query|exec> --base-dir <path> ...`: Inspects or seeds
6271
an isolated T3 SQLite database; writes create a private backup first.
6372

lint-staged.config.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/** @type {import("lint-staged").Configuration} */
2+
export default {
3+
// Keep in sync with vite.config.ts `staged`.
4+
// Commit runs format + lint; the heavier typecheck + tests stay in the agent
5+
// ship gate (pre-push on ready PRs / `pnpm pr:ready`).
6+
// `--no-error-on-unmatched-pattern`: a commit whose staged files are all
7+
// unformattable (e.g. only *.nix) must not fail pre-commit.
8+
"*": "vp fmt --no-error-on-unmatched-pattern",
9+
// Lint (with autofix) only the code files oxlint understands.
10+
"*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}": "vp lint --fix",
11+
};

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
"private": true,
44
"type": "module",
55
"scripts": {
6-
"prepare": "node scripts/clean-tsgo-backups.mjs && effect-tsgo patch && vp config --no-agent",
6+
"prepare": "node scripts/clean-tsgo-backups.mjs && effect-tsgo patch && vp config --no-agent && node scripts/install-git-hooks.mjs",
7+
"pr:ready": "node scripts/agent-pr-ready.mjs",
8+
"test:agent-gate": "vp test run scripts/agent-pre-push.test.mjs",
79
"dev": "node scripts/dev-runner.ts dev",
810
"dev:share": "node scripts/dev-runner.ts dev --share",
911
"dev:server": "node scripts/dev-runner.ts dev:server",
@@ -55,6 +57,8 @@
5557
"@oxlint/plugins": "^1.63.0",
5658
"@types/node": "catalog:",
5759
"@typescript/native-preview": "catalog:",
60+
"husky": "^9.1.7",
61+
"lint-staged": "^16.4.0",
5862
"vite-plus": "catalog:"
5963
},
6064
"engines": {

pnpm-lock.yaml

Lines changed: 77 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/agent-gh.mjs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Agent-facing `gh` policy shim.
4+
*
5+
* Installed at `.tools/bin/gh` by `scripts/install-git-hooks.mjs`.
6+
* When coding-agent env markers are set, blocks undraft side channels
7+
* (`gh pr ready`, ready_for_review API). Use `pnpm pr:ready` instead
8+
* (sets AGENT_PR_SHIP=1 for the real call).
9+
*
10+
* Humans / non-agents: transparent pass-through to the next `gh` on PATH.
11+
*/
12+
import * as NodeChildProcess from "node:child_process";
13+
import * as NodePath from "node:path";
14+
import * as NodeProcess from "node:process";
15+
import * as NodeURL from "node:url";
16+
import { isCodingAgent } from "./lib/agent-env.mjs";
17+
import { findRealGh, inspectAgentGhCommand } from "./lib/agent-gh-policy.mjs";
18+
19+
const selfPath = NodeURL.fileURLToPath(import.meta.url);
20+
const argv = NodeProcess.argv.slice(2);
21+
22+
if (isCodingAgent()) {
23+
const decision = inspectAgentGhCommand(argv);
24+
if (decision.blocked) {
25+
console.error(`agent gh: blocked: ${decision.reason}`);
26+
NodeProcess.exit(1);
27+
}
28+
}
29+
30+
const realGh = findRealGh({ selfPath });
31+
if (!realGh) {
32+
console.error("agent gh: could not resolve real `gh` binary (set AGENT_GH_REAL)");
33+
NodeProcess.exit(127);
34+
}
35+
36+
// Avoid re-entering this shim if PATH still prefers us.
37+
const env = { ...NodeProcess.env };
38+
const toolsBin = NodePath.resolve(NodePath.dirname(selfPath), "..", ".tools", "bin");
39+
const pathParts = (env["PATH"] ?? "").split(NodePath.delimiter).filter(Boolean);
40+
env["PATH"] = pathParts.filter((p) => NodePath.resolve(p) !== toolsBin).join(NodePath.delimiter);
41+
env["AGENT_GH_REAL"] = realGh;
42+
43+
const result = NodeChildProcess.spawnSync(realGh, argv, {
44+
stdio: "inherit",
45+
env,
46+
shell: false,
47+
});
48+
NodeProcess.exit(result.status === null ? 1 : result.status);

0 commit comments

Comments
 (0)