agent-conveyor 0.1.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/README.md +1123 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +19 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/program-name.d.ts +2 -0
- package/dist/cli/program-name.js +12 -0
- package/dist/cli/program-name.js.map +1 -0
- package/dist/cli/typescript-runtime.d.ts +52 -0
- package/dist/cli/typescript-runtime.js +18009 -0
- package/dist/cli/typescript-runtime.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/audit.d.ts +96 -0
- package/dist/runtime/audit.js +298 -0
- package/dist/runtime/audit.js.map +1 -0
- package/dist/runtime/classify.d.ts +8 -0
- package/dist/runtime/classify.js +128 -0
- package/dist/runtime/classify.js.map +1 -0
- package/dist/runtime/codex-session.d.ts +103 -0
- package/dist/runtime/codex-session.js +408 -0
- package/dist/runtime/codex-session.js.map +1 -0
- package/dist/runtime/commands.d.ts +92 -0
- package/dist/runtime/commands.js +408 -0
- package/dist/runtime/commands.js.map +1 -0
- package/dist/runtime/dispatch.d.ts +74 -0
- package/dist/runtime/dispatch.js +669 -0
- package/dist/runtime/dispatch.js.map +1 -0
- package/dist/runtime/export.d.ts +22 -0
- package/dist/runtime/export.js +77 -0
- package/dist/runtime/export.js.map +1 -0
- package/dist/runtime/ingest.d.ts +28 -0
- package/dist/runtime/ingest.js +177 -0
- package/dist/runtime/ingest.js.map +1 -0
- package/dist/runtime/loop-evidence.d.ts +87 -0
- package/dist/runtime/loop-evidence.js +448 -0
- package/dist/runtime/loop-evidence.js.map +1 -0
- package/dist/runtime/manager-config.d.ts +20 -0
- package/dist/runtime/manager-config.js +34 -0
- package/dist/runtime/manager-config.js.map +1 -0
- package/dist/runtime/manager-permissions.d.ts +7 -0
- package/dist/runtime/manager-permissions.js +85 -0
- package/dist/runtime/manager-permissions.js.map +1 -0
- package/dist/runtime/notifications.d.ts +89 -0
- package/dist/runtime/notifications.js +208 -0
- package/dist/runtime/notifications.js.map +1 -0
- package/dist/runtime/replay.d.ts +29 -0
- package/dist/runtime/replay.js +331 -0
- package/dist/runtime/replay.js.map +1 -0
- package/dist/runtime/tasks.d.ts +54 -0
- package/dist/runtime/tasks.js +195 -0
- package/dist/runtime/tasks.js.map +1 -0
- package/dist/runtime/tmux.d.ts +61 -0
- package/dist/runtime/tmux.js +189 -0
- package/dist/runtime/tmux.js.map +1 -0
- package/dist/runtime/visual-diff.d.ts +23 -0
- package/dist/runtime/visual-diff.js +234 -0
- package/dist/runtime/visual-diff.js.map +1 -0
- package/dist/state/database.d.ts +21 -0
- package/dist/state/database.js +142 -0
- package/dist/state/database.js.map +1 -0
- package/dist/state/files.d.ts +38 -0
- package/dist/state/files.js +73 -0
- package/dist/state/files.js.map +1 -0
- package/dist/state/schema-v22.d.ts +1 -0
- package/dist/state/schema-v22.js +566 -0
- package/dist/state/schema-v22.js.map +1 -0
- package/dist/state/sqlite-contract.d.ts +4 -0
- package/dist/state/sqlite-contract.js +78 -0
- package/dist/state/sqlite-contract.js.map +1 -0
- package/dist/state/status.d.ts +12 -0
- package/dist/state/status.js +40 -0
- package/dist/state/status.js.map +1 -0
- package/docs/typescript-migration/cli-contract.md +147 -0
- package/docs/typescript-migration/dashboard-contract.md +76 -0
- package/docs/typescript-migration/package-install-contract.md +98 -0
- package/docs/typescript-migration/qa-gate-matrix.md +103 -0
- package/docs/typescript-migration/sqlite-state-contract.md +92 -0
- package/docs/typescript-migration/t005-runtime-parity.md +47 -0
- package/package.json +88 -0
- package/scripts/capture-static-html-screenshot.mjs +88 -0
- package/skills/codex-review/SKILL.md +116 -0
- package/skills/codex-review/scripts/codex-review +344 -0
- package/skills/manage-codex-workers/SKILL.md +696 -0
- package/skills/manage-codex-workers/agents/openai.yaml +5 -0
package/package.json
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-conveyor",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Local agent manager/worker conveyor control plane for Codex sessions.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"conveyor": "dist/cli/main.js",
|
|
9
|
+
"workerctl": "dist/cli/main.js"
|
|
10
|
+
},
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./cli": {
|
|
19
|
+
"types": "./dist/cli/program-name.d.ts",
|
|
20
|
+
"default": "./dist/cli/program-name.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist/",
|
|
25
|
+
"scripts/capture-static-html-screenshot.mjs",
|
|
26
|
+
"skills/**/*",
|
|
27
|
+
"README.md",
|
|
28
|
+
"docs/typescript-migration/*.md"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"test": "node dashboard/scripts/run-tests.mjs",
|
|
32
|
+
"build": "npm run build:cli && npm run build:dashboard",
|
|
33
|
+
"build:cli": "tsc -p tsconfig.cli.json",
|
|
34
|
+
"build:dashboard": "tsc -p dashboard/tsconfig.json && vite build --config dashboard/vite.config.ts",
|
|
35
|
+
"lint": "eslint .",
|
|
36
|
+
"knip": "npm run build:cli && knip",
|
|
37
|
+
"migration:audit": "node scripts/ts-migration-audit.mjs",
|
|
38
|
+
"migration:audit:final": "node scripts/ts-migration-audit.mjs --require-zero-python",
|
|
39
|
+
"check": "npm run lint && npm run knip && npm test -- --runInBand && npm run build",
|
|
40
|
+
"dashboard": "tsx dashboard/server/index.ts",
|
|
41
|
+
"prepack": "rm -rf dist/cli && npm run build:cli",
|
|
42
|
+
"prepublishOnly": "node scripts/prepublish-guard.mjs"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=20.19"
|
|
46
|
+
},
|
|
47
|
+
"keywords": [
|
|
48
|
+
"agent",
|
|
49
|
+
"codex",
|
|
50
|
+
"conveyor",
|
|
51
|
+
"manager-worker",
|
|
52
|
+
"workflow-automation"
|
|
53
|
+
],
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "git+https://github.com/neonwatty/agent-conveyor.git"
|
|
57
|
+
},
|
|
58
|
+
"bugs": {
|
|
59
|
+
"url": "https://github.com/neonwatty/agent-conveyor/issues"
|
|
60
|
+
},
|
|
61
|
+
"homepage": "https://github.com/neonwatty/agent-conveyor#readme",
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"@homebridge/node-pty-prebuilt-multiarch": "^0.13.1",
|
|
64
|
+
"@playwright/test": "^1.60.0",
|
|
65
|
+
"@vitejs/plugin-react": "^5.1.0",
|
|
66
|
+
"@xterm/addon-fit": "^0.10.0",
|
|
67
|
+
"@xterm/xterm": "^5.5.0",
|
|
68
|
+
"express": "^5.1.0",
|
|
69
|
+
"react": "^19.2.0",
|
|
70
|
+
"react-dom": "^19.2.0",
|
|
71
|
+
"vite": "^7.2.4",
|
|
72
|
+
"ws": "^8.18.3"
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@eslint/js": "^10.0.1",
|
|
76
|
+
"@types/express": "^5.0.5",
|
|
77
|
+
"@types/node": "^24.10.1",
|
|
78
|
+
"@types/react": "^19.2.6",
|
|
79
|
+
"@types/react-dom": "^19.2.3",
|
|
80
|
+
"@types/ws": "^8.18.1",
|
|
81
|
+
"eslint": "^10.4.1",
|
|
82
|
+
"globals": "^17.6.0",
|
|
83
|
+
"knip": "^6.15.0",
|
|
84
|
+
"tsx": "^4.21.0",
|
|
85
|
+
"typescript": "^5.9.3",
|
|
86
|
+
"typescript-eslint": "^8.60.1"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const ERROR_PREFIX = "browser-backed QA requires Playwright/Chromium or a configured browser capture helper";
|
|
4
|
+
|
|
5
|
+
function parseArgs(argv) {
|
|
6
|
+
const args = {};
|
|
7
|
+
for (let index = 2; index < argv.length; index += 2) {
|
|
8
|
+
const key = argv[index];
|
|
9
|
+
const value = argv[index + 1];
|
|
10
|
+
if (!key || !key.startsWith("--") || value === undefined) {
|
|
11
|
+
throw new Error("Usage: capture-static-html-screenshot.mjs --html HTML --output PNG --width WIDTH --height HEIGHT");
|
|
12
|
+
}
|
|
13
|
+
args[key.slice(2)] = value;
|
|
14
|
+
}
|
|
15
|
+
return args;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parsePositiveInteger(value) {
|
|
19
|
+
if (!/^[1-9][0-9]*$/.test(value ?? "")) {
|
|
20
|
+
throw new Error("Usage: capture-static-html-screenshot.mjs --html HTML --output PNG --width WIDTH --height HEIGHT");
|
|
21
|
+
}
|
|
22
|
+
return Number(value);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function main() {
|
|
26
|
+
const args = parseArgs(process.argv);
|
|
27
|
+
const htmlPath = args.html;
|
|
28
|
+
const outputPath = args.output;
|
|
29
|
+
const width = parsePositiveInteger(args.width);
|
|
30
|
+
const height = parsePositiveInteger(args.height);
|
|
31
|
+
|
|
32
|
+
if (!htmlPath || !outputPath) {
|
|
33
|
+
throw new Error("Usage: capture-static-html-screenshot.mjs --html HTML --output PNG --width WIDTH --height HEIGHT");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { pathToFileURL } = await import("node:url");
|
|
37
|
+
const { chromium } = await import("@playwright/test");
|
|
38
|
+
const launchAttempts = [
|
|
39
|
+
{ backend: "playwright-chromium", options: { headless: true } },
|
|
40
|
+
{ backend: "playwright-chrome-channel", options: { channel: "chrome", headless: true } },
|
|
41
|
+
{
|
|
42
|
+
backend: "playwright-chrome-app",
|
|
43
|
+
options: {
|
|
44
|
+
executablePath: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
45
|
+
headless: true,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
let browser;
|
|
50
|
+
let backend;
|
|
51
|
+
let lastError;
|
|
52
|
+
for (const attempt of launchAttempts) {
|
|
53
|
+
try {
|
|
54
|
+
browser = await chromium.launch(attempt.options);
|
|
55
|
+
backend = attempt.backend;
|
|
56
|
+
break;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
lastError = error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (!browser || !backend) {
|
|
62
|
+
throw lastError ?? new Error("No browser launch attempt was made.");
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const page = await browser.newPage({
|
|
66
|
+
deviceScaleFactor: 1,
|
|
67
|
+
viewport: { width, height },
|
|
68
|
+
});
|
|
69
|
+
await page.goto(pathToFileURL(htmlPath).href, { waitUntil: "load" });
|
|
70
|
+
await page.screenshot({ path: outputPath, fullPage: false });
|
|
71
|
+
console.log(JSON.stringify({
|
|
72
|
+
backend,
|
|
73
|
+
html_path: htmlPath,
|
|
74
|
+
screenshot_path: outputPath,
|
|
75
|
+
viewport: `${width}x${height}`,
|
|
76
|
+
}));
|
|
77
|
+
} finally {
|
|
78
|
+
await browser.close();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
await main();
|
|
84
|
+
} catch (error) {
|
|
85
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
86
|
+
console.error(`${ERROR_PREFIX}: ${message}`);
|
|
87
|
+
process.exitCode = 2;
|
|
88
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: codex-review
|
|
3
|
+
description: "Codex code review closeout: local dirty changes, PR branch vs main, parallel tests."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Codex Review
|
|
7
|
+
|
|
8
|
+
Run Codex's built-in code review as a closeout check. This is code review (`codex review`), not Guardian `auto_review` approval routing.
|
|
9
|
+
|
|
10
|
+
Use when:
|
|
11
|
+
- user asks for Codex review / autoreview / second-model review
|
|
12
|
+
- after non-trivial code edits, before final/commit/ship
|
|
13
|
+
- reviewing a local branch or PR branch after fixes
|
|
14
|
+
|
|
15
|
+
## Contract
|
|
16
|
+
|
|
17
|
+
- Treat review output as advisory. Never blindly apply it.
|
|
18
|
+
- Verify every finding by reading the real code path and adjacent files.
|
|
19
|
+
- Read dependency docs/source/types when the finding depends on external behavior.
|
|
20
|
+
- Reject unrealistic edge cases, speculative risks, broad rewrites, and fixes that over-complicate the codebase.
|
|
21
|
+
- Prefer small fixes at the right ownership boundary; no refactor unless it clearly improves the bug class.
|
|
22
|
+
- Keep going until Codex review returns no accepted/actionable findings.
|
|
23
|
+
- If a review-triggered fix changes code, rerun focused tests and rerun Codex review.
|
|
24
|
+
- Never switch or override the review model. If the review hits model capacity, retry the same command a few times with the same model. If it hits sandbox/permission limits, use the helper's `--full-access` option instead of changing models.
|
|
25
|
+
- Stop as soon as the review command/helper exits 0 with no accepted/actionable findings. Do not run an extra direct `codex review` just to get a nicer "clean" line, a second opinion, or clearer closeout wording.
|
|
26
|
+
- Treat the helper's successful exit plus absence of actionable findings as the clean review result, even if the underlying Codex CLI output is terse.
|
|
27
|
+
- If rejecting a finding as intentional/not worth fixing, add a brief inline code comment only when it explains a real invariant or ownership decision that future reviewers should know.
|
|
28
|
+
- Do not push just to review. Push only when the user requested push/ship/PR update.
|
|
29
|
+
|
|
30
|
+
## Pick Target
|
|
31
|
+
|
|
32
|
+
Dirty local work:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
codex review --uncommitted
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Use this only when the patch is actually unstaged/staged/untracked in the
|
|
39
|
+
current checkout. For committed, pushed, or PR work, review the branch against
|
|
40
|
+
its base instead; do not force `--mode local` / `--uncommitted` just because the
|
|
41
|
+
helper docs mention dirty work first. A clean `--uncommitted` review only proves
|
|
42
|
+
there is no local patch.
|
|
43
|
+
|
|
44
|
+
Branch/PR work:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
git fetch origin
|
|
48
|
+
codex review --base origin/main
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Do not pass an inline prompt with `--base`; current CLI rejects `--base` + `[PROMPT]` even though help text is ambiguous. If custom instructions are needed, run the plain base review first, then do a local/manual follow-up pass.
|
|
52
|
+
|
|
53
|
+
If an open PR exists, use its actual base:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
base=$(gh pr view --json baseRefName --jq .baseRefName)
|
|
57
|
+
codex review --base "origin/$base"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Committed single change:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
codex review --commit HEAD
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Parallel Closeout
|
|
67
|
+
|
|
68
|
+
Format first if formatting can change line locations. Then it is OK to run tests and review in parallel:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
~/.codex/skills/codex-review/scripts/codex-review --parallel-tests "<focused test command>"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Tradeoff: tests may force code changes that stale the review. If tests or review lead to code edits, rerun the affected tests and rerun review until no accepted/actionable findings remain. Once that rerun exits cleanly, stop; do not spend another long review cycle on redundant confirmation.
|
|
75
|
+
|
|
76
|
+
## Context Efficiency
|
|
77
|
+
|
|
78
|
+
Codex review is usually noisy. Default to a subagent filter when subagents are available. Ask it to run the review and return only:
|
|
79
|
+
- actionable findings it accepts
|
|
80
|
+
- findings it rejects, with one-line reason
|
|
81
|
+
- exact files/tests to rerun
|
|
82
|
+
|
|
83
|
+
Run inline only for tiny changes or when subagents are unavailable.
|
|
84
|
+
|
|
85
|
+
## Helper
|
|
86
|
+
|
|
87
|
+
Bundled helper:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
~/.codex/skills/codex-review/scripts/codex-review --help
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The helper:
|
|
94
|
+
- chooses dirty `--uncommitted` first
|
|
95
|
+
- otherwise uses current PR base if `gh pr view` works
|
|
96
|
+
- otherwise uses `origin/main` for non-main branches
|
|
97
|
+
- should be left in `--mode auto` or forced to `--mode branch` for committed/PR work; do not force `--mode local` after committing
|
|
98
|
+
- writes only to stdout unless `--output` or `CODEX_REVIEW_OUTPUT` is set
|
|
99
|
+
- supports `--dry-run` and `--parallel-tests`
|
|
100
|
+
- supports `--full-access` for nested review runs that need localhost bind/listen tests
|
|
101
|
+
- prints `codex-review clean: no accepted/actionable findings reported` when the selected review command exits 0
|
|
102
|
+
|
|
103
|
+
Recursion guard:
|
|
104
|
+
- The helper exports `CODEX_REVIEW_HELPER_LEVEL=1` to the nested `codex review` process.
|
|
105
|
+
- If a review session tries to invoke the helper again, the helper exits `78` with `nested codex-review invocation blocked`.
|
|
106
|
+
- Use `CODEX_REVIEW_ALLOW_NESTED=1` only for intentional debugging dry runs; do not use it in normal closeout, GoalBuddy conveyor, or PR review flows.
|
|
107
|
+
|
|
108
|
+
## Final Report
|
|
109
|
+
|
|
110
|
+
Include:
|
|
111
|
+
- review command used
|
|
112
|
+
- tests/proof run
|
|
113
|
+
- findings accepted/rejected, briefly why
|
|
114
|
+
- the clean review result from the final helper/review run, or why a remaining finding was consciously rejected
|
|
115
|
+
|
|
116
|
+
Do not run another Codex review solely to improve the final report wording. If the final helper run exited 0 and produced no accepted/actionable findings, report that exact run as clean.
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
usage() {
|
|
5
|
+
cat <<'EOF'
|
|
6
|
+
Usage: codex-review [options]
|
|
7
|
+
|
|
8
|
+
Options:
|
|
9
|
+
--mode auto|local|branch Target selection. Default: auto.
|
|
10
|
+
--base REF Base ref for branch review. Default: PR base or origin/main.
|
|
11
|
+
--codex-bin PATH Codex binary. Default: codex.
|
|
12
|
+
--full-access Run nested Codex review without sandbox/approval prompts.
|
|
13
|
+
--output FILE Also save output to file.
|
|
14
|
+
--parallel-tests CMD Run review and test command concurrently.
|
|
15
|
+
--dry-run Print selected commands, do not run.
|
|
16
|
+
-h, --help Show help.
|
|
17
|
+
|
|
18
|
+
Modes:
|
|
19
|
+
local codex review --uncommitted
|
|
20
|
+
branch codex review --base <base>
|
|
21
|
+
auto dirty tree -> local, else PR/current branch -> branch
|
|
22
|
+
EOF
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
require_option_value() {
|
|
26
|
+
local option=$1
|
|
27
|
+
local value=${2-}
|
|
28
|
+
|
|
29
|
+
if [[ -z "$value" || "$value" == --* || "$value" == "-h" ]]; then
|
|
30
|
+
echo "missing value for $option" >&2
|
|
31
|
+
usage >&2
|
|
32
|
+
exit 2
|
|
33
|
+
fi
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
mode=auto
|
|
37
|
+
base_ref=
|
|
38
|
+
codex_bin=${CODEX_BIN:-codex}
|
|
39
|
+
codex_args=()
|
|
40
|
+
output=${CODEX_REVIEW_OUTPUT:-}
|
|
41
|
+
parallel_tests=
|
|
42
|
+
dry_run=false
|
|
43
|
+
|
|
44
|
+
while [[ $# -gt 0 ]]; do
|
|
45
|
+
case "$1" in
|
|
46
|
+
--mode)
|
|
47
|
+
require_option_value "$1" "${2-}"
|
|
48
|
+
mode=$2
|
|
49
|
+
shift 2
|
|
50
|
+
;;
|
|
51
|
+
--base)
|
|
52
|
+
require_option_value "$1" "${2-}"
|
|
53
|
+
base_ref=$2
|
|
54
|
+
shift 2
|
|
55
|
+
;;
|
|
56
|
+
--codex-bin)
|
|
57
|
+
require_option_value "$1" "${2-}"
|
|
58
|
+
codex_bin=$2
|
|
59
|
+
shift 2
|
|
60
|
+
;;
|
|
61
|
+
--full-access)
|
|
62
|
+
codex_args+=(--dangerously-bypass-approvals-and-sandbox)
|
|
63
|
+
shift
|
|
64
|
+
;;
|
|
65
|
+
--output)
|
|
66
|
+
require_option_value "$1" "${2-}"
|
|
67
|
+
output=$2
|
|
68
|
+
shift 2
|
|
69
|
+
;;
|
|
70
|
+
--parallel-tests)
|
|
71
|
+
require_option_value "$1" "${2-}"
|
|
72
|
+
parallel_tests=$2
|
|
73
|
+
shift 2
|
|
74
|
+
;;
|
|
75
|
+
--dry-run)
|
|
76
|
+
dry_run=true
|
|
77
|
+
shift
|
|
78
|
+
;;
|
|
79
|
+
-h|--help)
|
|
80
|
+
usage
|
|
81
|
+
exit 0
|
|
82
|
+
;;
|
|
83
|
+
*)
|
|
84
|
+
usage >&2
|
|
85
|
+
exit 2
|
|
86
|
+
;;
|
|
87
|
+
esac
|
|
88
|
+
done
|
|
89
|
+
|
|
90
|
+
case "$mode" in
|
|
91
|
+
auto|local|branch) ;;
|
|
92
|
+
*)
|
|
93
|
+
echo "invalid --mode: $mode" >&2
|
|
94
|
+
exit 2
|
|
95
|
+
;;
|
|
96
|
+
esac
|
|
97
|
+
|
|
98
|
+
nested_level=${CODEX_REVIEW_HELPER_LEVEL:-0}
|
|
99
|
+
allow_nested=${CODEX_REVIEW_ALLOW_NESTED:-}
|
|
100
|
+
if ! [[ "$nested_level" =~ ^[0-9]+$ ]]; then
|
|
101
|
+
echo "codex-review: invalid CODEX_REVIEW_HELPER_LEVEL=$nested_level" >&2
|
|
102
|
+
exit 2
|
|
103
|
+
fi
|
|
104
|
+
nested_level_is_nested=false
|
|
105
|
+
if [[ "$nested_level" =~ [1-9] ]]; then
|
|
106
|
+
nested_level_is_nested=true
|
|
107
|
+
fi
|
|
108
|
+
if [[ "$nested_level_is_nested" == true && "$allow_nested" != "1" ]]; then
|
|
109
|
+
echo "codex-review refusal: nested codex-review invocation blocked; set CODEX_REVIEW_ALLOW_NESTED=1 only for intentional debugging" >&2
|
|
110
|
+
exit 78
|
|
111
|
+
fi
|
|
112
|
+
if [[ "$nested_level_is_nested" != true ]]; then
|
|
113
|
+
export CODEX_REVIEW_HELPER_LEVEL=1
|
|
114
|
+
elif (( ${#nested_level} <= 15 )); then
|
|
115
|
+
export CODEX_REVIEW_HELPER_LEVEL=$((10#$nested_level + 1))
|
|
116
|
+
else
|
|
117
|
+
export CODEX_REVIEW_HELPER_LEVEL=2
|
|
118
|
+
fi
|
|
119
|
+
export CODEX_REVIEW_HELPER_PARENT_PID=$$
|
|
120
|
+
|
|
121
|
+
git rev-parse --show-toplevel >/dev/null
|
|
122
|
+
|
|
123
|
+
current_branch=$(git branch --show-current 2>/dev/null || true)
|
|
124
|
+
dirty=false
|
|
125
|
+
if [[ -n "$(git status --porcelain)" ]]; then
|
|
126
|
+
dirty=true
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
pr_url=
|
|
130
|
+
if [[ -z "$base_ref" && "$mode" != local ]] && command -v gh >/dev/null 2>&1; then
|
|
131
|
+
if pr_lines=$(gh pr view --json baseRefName,url --jq '[.baseRefName, .url] | @tsv' 2>/dev/null); then
|
|
132
|
+
base_name=${pr_lines%%$'\t'*}
|
|
133
|
+
pr_url=${pr_lines#*$'\t'}
|
|
134
|
+
if [[ -n "$base_name" ]]; then
|
|
135
|
+
base_ref="origin/$base_name"
|
|
136
|
+
fi
|
|
137
|
+
fi
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
if [[ -z "$base_ref" ]]; then
|
|
141
|
+
base_ref=origin/main
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
review_kind=
|
|
145
|
+
if [[ "$mode" == local || ( "$mode" == auto && "$dirty" == true ) ]]; then
|
|
146
|
+
review_kind=local
|
|
147
|
+
elif [[ "$mode" == branch || ( "$mode" == auto && -n "$current_branch" && "$current_branch" != "main" ) ]]; then
|
|
148
|
+
review_kind=branch
|
|
149
|
+
else
|
|
150
|
+
echo "no review target: clean main checkout and no forced mode" >&2
|
|
151
|
+
exit 1
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
if [[ "$review_kind" == local ]]; then
|
|
155
|
+
review_cmd=("$codex_bin" ${codex_args[@]+"${codex_args[@]}"} review --uncommitted)
|
|
156
|
+
else
|
|
157
|
+
review_cmd=("$codex_bin" ${codex_args[@]+"${codex_args[@]}"} review --base "$base_ref")
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
printf 'codex-review target: %s\n' "$review_kind"
|
|
161
|
+
printf 'branch: %s\n' "${current_branch:-detached}"
|
|
162
|
+
if [[ -n "$pr_url" ]]; then
|
|
163
|
+
printf 'pr: %s\n' "$pr_url"
|
|
164
|
+
fi
|
|
165
|
+
printf 'review:'
|
|
166
|
+
printf ' %q' "${review_cmd[@]}"
|
|
167
|
+
printf '\n'
|
|
168
|
+
if [[ -n "$parallel_tests" ]]; then
|
|
169
|
+
printf 'tests: %s\n' "$parallel_tests"
|
|
170
|
+
fi
|
|
171
|
+
if [[ "$review_kind" == branch ]]; then
|
|
172
|
+
printf 'fetch: git fetch origin --quiet\n'
|
|
173
|
+
fi
|
|
174
|
+
if [[ -n "$output" ]]; then
|
|
175
|
+
printf 'output: %s\n' "$output"
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
if [[ "$dry_run" == true ]]; then
|
|
179
|
+
exit 0
|
|
180
|
+
fi
|
|
181
|
+
|
|
182
|
+
if [[ "$review_kind" == branch ]]; then
|
|
183
|
+
git fetch origin --quiet || {
|
|
184
|
+
echo "warning: git fetch origin failed; reviewing with existing refs" >&2
|
|
185
|
+
}
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
review_output=$output
|
|
189
|
+
review_output_is_temp=false
|
|
190
|
+
if [[ -z "$review_output" ]]; then
|
|
191
|
+
review_output=$(mktemp)
|
|
192
|
+
review_output_is_temp=true
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
cleanup() {
|
|
196
|
+
if [[ "${review_output_is_temp:-false}" == true && -n "${review_output:-}" ]]; then
|
|
197
|
+
rm -f "$review_output"
|
|
198
|
+
fi
|
|
199
|
+
}
|
|
200
|
+
trap cleanup EXIT
|
|
201
|
+
|
|
202
|
+
run_review() {
|
|
203
|
+
mkdir -p "$(dirname "$review_output")"
|
|
204
|
+
"${review_cmd[@]}" 2>&1 | tee "$review_output"
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
elapsed_since() {
|
|
208
|
+
local started_at=$1
|
|
209
|
+
local finished_at
|
|
210
|
+
finished_at=$(date +%s)
|
|
211
|
+
printf '%s\n' "$((finished_at - started_at))"
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
format_elapsed() {
|
|
215
|
+
local seconds=$1
|
|
216
|
+
if (( seconds < 60 )); then
|
|
217
|
+
printf '%ss\n' "$seconds"
|
|
218
|
+
else
|
|
219
|
+
printf '%sm%ss\n' "$((seconds / 60))" "$((seconds % 60))"
|
|
220
|
+
fi
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
review_output_empty() {
|
|
224
|
+
[[ ! -s "$review_output" ]] || ! grep -q '[^[:space:]]' "$review_output"
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
review_output_has_findings() {
|
|
228
|
+
# Codex CLI transcripts include tool output before the final `codex` section.
|
|
229
|
+
# After the CLI header, prefer the first final-answer marker after the last
|
|
230
|
+
# Codex diagnostic line; if there are no diagnostics, use the last marker.
|
|
231
|
+
# Direct/fake review commands are scanned as a whole.
|
|
232
|
+
awk '
|
|
233
|
+
{
|
|
234
|
+
lines[NR] = $0
|
|
235
|
+
if ($0 ~ /^OpenAI Codex v/) {
|
|
236
|
+
has_codex_header = 1
|
|
237
|
+
}
|
|
238
|
+
if ($0 ~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}T.* (WARN|ERROR) /) {
|
|
239
|
+
last_diagnostic = NR
|
|
240
|
+
}
|
|
241
|
+
if ($0 ~ /\[P[0-3]\]/) {
|
|
242
|
+
any_finding = 1
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
END {
|
|
246
|
+
if (has_codex_header) {
|
|
247
|
+
if (last_diagnostic) {
|
|
248
|
+
for (i = last_diagnostic + 1; i <= NR; i++) {
|
|
249
|
+
if (lines[i] == "codex") {
|
|
250
|
+
final_start = i + 1
|
|
251
|
+
break
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
for (i = 1; i <= NR; i++) {
|
|
256
|
+
if (lines[i] == "codex") {
|
|
257
|
+
final_start = i + 1
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (final_start) {
|
|
262
|
+
for (i = final_start; i <= NR; i++) {
|
|
263
|
+
if (lines[i] ~ /\[P[0-3]\]/) {
|
|
264
|
+
exit 0
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
exit 1
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
exit any_finding ? 0 : 1
|
|
271
|
+
}
|
|
272
|
+
' "$review_output"
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
report_clean_review_or_fail() {
|
|
276
|
+
local elapsed_text
|
|
277
|
+
elapsed_text=$(format_elapsed "${review_elapsed_seconds:-0}")
|
|
278
|
+
|
|
279
|
+
if review_output_has_findings; then
|
|
280
|
+
printf 'codex-review complete after %s\n' "$elapsed_text"
|
|
281
|
+
printf 'codex-review findings: accepted/actionable findings reported\n'
|
|
282
|
+
return 1
|
|
283
|
+
fi
|
|
284
|
+
if review_output_empty; then
|
|
285
|
+
printf 'codex-review complete after %s; no output\n' "$elapsed_text"
|
|
286
|
+
return 1
|
|
287
|
+
fi
|
|
288
|
+
printf 'codex-review complete after %s\n' "$elapsed_text"
|
|
289
|
+
printf 'codex-review clean: no accepted/actionable findings reported\n'
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if [[ -z "$parallel_tests" ]]; then
|
|
293
|
+
review_started_at=$(date +%s)
|
|
294
|
+
set +e
|
|
295
|
+
run_review
|
|
296
|
+
review_status=$?
|
|
297
|
+
review_elapsed_seconds=$(elapsed_since "$review_started_at")
|
|
298
|
+
set -e
|
|
299
|
+
if [[ "$review_status" == 0 ]]; then
|
|
300
|
+
report_clean_review_or_fail
|
|
301
|
+
exit $?
|
|
302
|
+
fi
|
|
303
|
+
exit "$review_status"
|
|
304
|
+
fi
|
|
305
|
+
|
|
306
|
+
review_status_file=$(mktemp)
|
|
307
|
+
review_elapsed_file=$(mktemp)
|
|
308
|
+
tests_status_file=$(mktemp)
|
|
309
|
+
|
|
310
|
+
(
|
|
311
|
+
set +e
|
|
312
|
+
review_started_at=$(date +%s)
|
|
313
|
+
run_review
|
|
314
|
+
status=$?
|
|
315
|
+
elapsed=$(elapsed_since "$review_started_at")
|
|
316
|
+
printf '%s\n' "$status" > "$review_status_file"
|
|
317
|
+
printf '%s\n' "$elapsed" > "$review_elapsed_file"
|
|
318
|
+
) &
|
|
319
|
+
review_pid=$!
|
|
320
|
+
|
|
321
|
+
(
|
|
322
|
+
set +e
|
|
323
|
+
bash -lc "$parallel_tests"
|
|
324
|
+
status=$?
|
|
325
|
+
printf '%s\n' "$status" > "$tests_status_file"
|
|
326
|
+
) &
|
|
327
|
+
tests_pid=$!
|
|
328
|
+
|
|
329
|
+
wait "$review_pid" || true
|
|
330
|
+
wait "$tests_pid" || true
|
|
331
|
+
|
|
332
|
+
review_status=$(cat "$review_status_file")
|
|
333
|
+
review_elapsed_seconds=$(cat "$review_elapsed_file")
|
|
334
|
+
tests_status=$(cat "$tests_status_file")
|
|
335
|
+
rm -f "$review_status_file" "$review_elapsed_file" "$tests_status_file"
|
|
336
|
+
|
|
337
|
+
printf 'codex-review exit: %s\n' "$review_status"
|
|
338
|
+
printf 'tests exit: %s\n' "$tests_status"
|
|
339
|
+
|
|
340
|
+
if [[ "$review_status" != 0 || "$tests_status" != 0 ]]; then
|
|
341
|
+
exit 1
|
|
342
|
+
fi
|
|
343
|
+
|
|
344
|
+
report_clean_review_or_fail
|