@imdeadpool/guardex 7.0.41 → 7.0.43
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 +68 -13
- package/package.json +2 -1
- package/skills/gitguardex/SKILL.md +13 -0
- package/skills/guardex-merge-skills-to-dev/SKILL.md +59 -0
- package/src/agents/cleanup-sessions.js +126 -0
- package/src/agents/detect.js +160 -0
- package/src/agents/finish.js +172 -0
- package/src/agents/inspect.js +189 -0
- package/src/agents/launch.js +240 -0
- package/src/agents/registry.js +133 -0
- package/src/agents/selection-panel.js +571 -0
- package/src/agents/sessions.js +151 -0
- package/src/agents/start.js +591 -0
- package/src/agents/status.js +143 -0
- package/src/agents/terminal.js +152 -0
- package/src/budget/index.js +343 -0
- package/src/ci-init/index.js +265 -0
- package/src/cli/args.js +305 -1
- package/src/cli/main.js +262 -132
- package/src/cockpit/action-runner.js +3 -0
- package/src/cockpit/actions.js +80 -0
- package/src/cockpit/control.js +1121 -0
- package/src/cockpit/index.js +426 -0
- package/src/cockpit/keybindings.js +224 -0
- package/src/cockpit/kitty-layout.js +549 -0
- package/src/cockpit/kitty-tree.js +144 -0
- package/src/cockpit/layout.js +224 -0
- package/src/cockpit/logs-reader.js +182 -0
- package/src/cockpit/menu.js +204 -0
- package/src/cockpit/pane-actions.js +597 -0
- package/src/cockpit/pane-menu.js +387 -0
- package/src/cockpit/projects-finder.js +178 -0
- package/src/cockpit/render.js +215 -0
- package/src/cockpit/settings-render.js +128 -0
- package/src/cockpit/settings.js +124 -0
- package/src/cockpit/shortcuts.js +24 -0
- package/src/cockpit/sidebar.js +311 -0
- package/src/cockpit/state.js +72 -0
- package/src/cockpit/theme.js +128 -0
- package/src/cockpit/welcome.js +266 -0
- package/src/context.js +76 -33
- package/src/doctor/index.js +3 -2
- package/src/finish/index.js +39 -2
- package/src/git/index.js +65 -0
- package/src/kitty/command.js +101 -0
- package/src/kitty/runtime.js +250 -0
- package/src/output/index.js +1 -1
- package/src/pr-review.js +241 -0
- package/src/scaffold/index.js +19 -0
- package/src/submodule/index.js +288 -0
- package/src/terminal/index.js +120 -0
- package/src/terminal/kitty.js +622 -0
- package/src/terminal/tmux.js +126 -0
- package/src/tmux/command.js +27 -0
- package/src/tmux/session.js +89 -0
- package/templates/AGENTS.multiagent-safety.md +27 -1
- package/templates/codex/skills/gitguardex/SKILL.md +2 -0
- package/templates/githooks/pre-commit +22 -1
- package/templates/github/workflows/README.md +87 -0
- package/templates/github/workflows/ci-full.yml +55 -0
- package/templates/github/workflows/ci.yml +56 -0
- package/templates/github/workflows/cr.yml +20 -1
- package/templates/scripts/agent-branch-finish.sh +544 -26
- package/templates/scripts/agent-branch-start.sh +89 -22
- package/templates/scripts/agent-preflight.sh +89 -0
- package/templates/scripts/agent-worktree-prune.sh +96 -5
- package/templates/scripts/codex-agent.sh +41 -6
- package/templates/scripts/openspec/init-plan-workspace.sh +43 -0
- package/templates/scripts/review-bot-watch.sh +31 -2
- package/templates/scripts/agent-session-state.js +0 -171
- package/templates/scripts/install-vscode-active-agents-extension.js +0 -135
- package/templates/vscode/guardex-active-agents/README.md +0 -34
- package/templates/vscode/guardex-active-agents/extension.js +0 -3782
- package/templates/vscode/guardex-active-agents/fileicons/gitguardex-fileicons.json +0 -54
- package/templates/vscode/guardex-active-agents/fileicons/icons/agent.svg +0 -5
- package/templates/vscode/guardex-active-agents/fileicons/icons/branch.svg +0 -7
- package/templates/vscode/guardex-active-agents/fileicons/icons/config.svg +0 -4
- package/templates/vscode/guardex-active-agents/fileicons/icons/hook.svg +0 -4
- package/templates/vscode/guardex-active-agents/fileicons/icons/openspec.svg +0 -5
- package/templates/vscode/guardex-active-agents/fileicons/icons/plan.svg +0 -4
- package/templates/vscode/guardex-active-agents/fileicons/icons/spec.svg +0 -5
- package/templates/vscode/guardex-active-agents/icon.png +0 -0
- package/templates/vscode/guardex-active-agents/media/active-agents-hivemind.svg +0 -14
- package/templates/vscode/guardex-active-agents/package.json +0 -169
- package/templates/vscode/guardex-active-agents/session-schema.js +0 -1348
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const tmuxCommand = require('../tmux/command');
|
|
4
|
+
const tmuxSession = require('../tmux/session');
|
|
5
|
+
|
|
6
|
+
function text(value, fallback = '') {
|
|
7
|
+
if (typeof value === 'string') return value.trim() || fallback;
|
|
8
|
+
if (value === null || value === undefined) return fallback;
|
|
9
|
+
return String(value).trim() || fallback;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function requireText(value, name) {
|
|
13
|
+
const normalized = text(value);
|
|
14
|
+
if (!normalized) {
|
|
15
|
+
throw new TypeError(`${name} must be a non-empty string`);
|
|
16
|
+
}
|
|
17
|
+
return normalized;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function targetId(target) {
|
|
21
|
+
if (target && typeof target === 'object') {
|
|
22
|
+
return requireText(target.paneId || target.tmuxPaneId || target.tmuxTarget || target.id || target.target, 'tmux target');
|
|
23
|
+
}
|
|
24
|
+
return requireText(target, 'tmux target');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function assertStatus(result, message) {
|
|
28
|
+
if (result && result.error) throw result.error;
|
|
29
|
+
if (!result || result.status === 0) return result;
|
|
30
|
+
const detail = String(result.stderr || result.stdout || '').trim();
|
|
31
|
+
throw new Error(`${message}${detail ? `: ${detail}` : '.'}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function defaultTmuxApi() {
|
|
35
|
+
return {
|
|
36
|
+
ensureTmuxAvailable: tmuxSession.ensureTmuxAvailable,
|
|
37
|
+
sessionExists: tmuxSession.sessionExists,
|
|
38
|
+
createSession: tmuxSession.createSession,
|
|
39
|
+
attachSession: tmuxSession.attachSession,
|
|
40
|
+
newWindowOrPane: tmuxSession.newWindowOrPane,
|
|
41
|
+
sendKeys: tmuxSession.sendKeys,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function createBackend(options = {}) {
|
|
46
|
+
const tmux = options.tmux || defaultTmuxApi();
|
|
47
|
+
const runTmux = typeof options.runTmux === 'function' ? options.runTmux : tmuxCommand.runTmux;
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
name: 'tmux',
|
|
51
|
+
isAvailable() {
|
|
52
|
+
try {
|
|
53
|
+
if (typeof tmux.isAvailable === 'function') return Boolean(tmux.isAvailable());
|
|
54
|
+
tmux.ensureTmuxAvailable();
|
|
55
|
+
return true;
|
|
56
|
+
} catch (_error) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
openCockpitLayout(config = {}) {
|
|
61
|
+
const sessionName = requireText(config.sessionName, 'tmux sessionName');
|
|
62
|
+
const repoRoot = requireText(config.repoRoot, 'tmux repoRoot');
|
|
63
|
+
const command = requireText(config.command, 'tmux cockpit command');
|
|
64
|
+
|
|
65
|
+
tmux.ensureTmuxAvailable();
|
|
66
|
+
|
|
67
|
+
if (tmux.sessionExists(sessionName)) {
|
|
68
|
+
assertStatus(tmux.attachSession(sessionName), `tmux could not attach session '${sessionName}'`);
|
|
69
|
+
return { action: 'attached', sessionName, repoRoot };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
assertStatus(
|
|
73
|
+
tmux.createSession(sessionName, repoRoot),
|
|
74
|
+
`tmux could not create session '${sessionName}'`,
|
|
75
|
+
);
|
|
76
|
+
assertStatus(
|
|
77
|
+
tmux.sendKeys(sessionName, command),
|
|
78
|
+
'tmux could not start cockpit control pane',
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (config.attach) {
|
|
82
|
+
assertStatus(tmux.attachSession(sessionName), `tmux could not attach session '${sessionName}'`);
|
|
83
|
+
return { action: 'created-attached', sessionName, repoRoot };
|
|
84
|
+
}
|
|
85
|
+
return { action: 'created', sessionName, repoRoot };
|
|
86
|
+
},
|
|
87
|
+
launchAgentPane(config = {}) {
|
|
88
|
+
const session = config.session && typeof config.session === 'object' ? config.session : {};
|
|
89
|
+
const cwd = requireText(config.worktree || session.worktreePath || session.path, 'tmux agent worktree');
|
|
90
|
+
const result = tmux.newWindowOrPane({
|
|
91
|
+
pane: true,
|
|
92
|
+
split: 'horizontal',
|
|
93
|
+
target: config.target || session.tmuxTarget || session.tmuxSession || session.sessionName,
|
|
94
|
+
cwd,
|
|
95
|
+
name: config.title || session.title,
|
|
96
|
+
});
|
|
97
|
+
return assertStatus(result, 'tmux could not launch agent pane');
|
|
98
|
+
},
|
|
99
|
+
launchTerminalPane(config = {}) {
|
|
100
|
+
const result = tmux.newWindowOrPane({
|
|
101
|
+
pane: true,
|
|
102
|
+
split: 'horizontal',
|
|
103
|
+
target: config.target,
|
|
104
|
+
cwd: requireText(config.cwd, 'tmux terminal cwd'),
|
|
105
|
+
name: config.title,
|
|
106
|
+
});
|
|
107
|
+
return assertStatus(result, 'tmux could not launch terminal pane');
|
|
108
|
+
},
|
|
109
|
+
focusPane(target) {
|
|
110
|
+
return assertStatus(runTmux(['select-pane', '-t', targetId(target)]), 'tmux could not focus pane');
|
|
111
|
+
},
|
|
112
|
+
closePane(target) {
|
|
113
|
+
return assertStatus(runTmux(['kill-pane', '-t', targetId(target)]), 'tmux could not close pane');
|
|
114
|
+
},
|
|
115
|
+
sendText(target, value, options = {}) {
|
|
116
|
+
const args = ['send-keys', '-t', targetId(target), String(value === undefined || value === null ? '' : value)];
|
|
117
|
+
if (options.submit) args.push('C-m');
|
|
118
|
+
return assertStatus(runTmux(args), 'tmux could not send text');
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = {
|
|
124
|
+
createBackend,
|
|
125
|
+
targetId,
|
|
126
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const runtime = require('../core/runtime');
|
|
2
|
+
|
|
3
|
+
function assertArgs(args) {
|
|
4
|
+
if (!Array.isArray(args)) {
|
|
5
|
+
throw new TypeError('tmux args must be an array');
|
|
6
|
+
}
|
|
7
|
+
for (const arg of args) {
|
|
8
|
+
if (typeof arg !== 'string') {
|
|
9
|
+
throw new TypeError('tmux args must contain only strings');
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function runTmux(args, options = {}) {
|
|
15
|
+
assertArgs(args);
|
|
16
|
+
return runtime.run(process.env.GUARDEX_TMUX_BIN || 'tmux', args, options);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isTmuxAvailable() {
|
|
20
|
+
const result = runTmux(['-V'], { stdio: 'pipe' });
|
|
21
|
+
return result.status === 0 && !result.error;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
isTmuxAvailable,
|
|
26
|
+
runTmux,
|
|
27
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const tmux = require('./command');
|
|
2
|
+
|
|
3
|
+
function requireName(name) {
|
|
4
|
+
if (typeof name !== 'string' || name.trim() === '') {
|
|
5
|
+
throw new TypeError('tmux session name must be a non-empty string');
|
|
6
|
+
}
|
|
7
|
+
return name;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function addCwd(args, cwd) {
|
|
11
|
+
if (cwd !== undefined) {
|
|
12
|
+
if (typeof cwd !== 'string' || cwd.trim() === '') {
|
|
13
|
+
throw new TypeError('tmux cwd must be a non-empty string');
|
|
14
|
+
}
|
|
15
|
+
args.push('-c', cwd);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function ensureTmuxAvailable() {
|
|
20
|
+
if (tmux.isTmuxAvailable()) return;
|
|
21
|
+
throw new Error('tmux is required for gx cockpit. Install tmux and retry.');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function sessionExists(name) {
|
|
25
|
+
const result = tmux.runTmux(['has-session', '-t', requireName(name)], {
|
|
26
|
+
stdio: 'pipe',
|
|
27
|
+
});
|
|
28
|
+
return result.status === 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function createSession(name, cwd) {
|
|
32
|
+
const args = ['new-session', '-d', '-s', requireName(name)];
|
|
33
|
+
addCwd(args, cwd);
|
|
34
|
+
return tmux.runTmux(args);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function attachSession(name) {
|
|
38
|
+
return tmux.runTmux(['attach-session', '-t', requireName(name)], {
|
|
39
|
+
stdio: 'inherit',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function newWindowOrPane(options = {}) {
|
|
44
|
+
const {
|
|
45
|
+
target,
|
|
46
|
+
cwd,
|
|
47
|
+
name,
|
|
48
|
+
pane = false,
|
|
49
|
+
split = 'vertical',
|
|
50
|
+
} = options;
|
|
51
|
+
const args = pane ? ['split-window'] : ['new-window'];
|
|
52
|
+
|
|
53
|
+
if (pane) {
|
|
54
|
+
if (split === 'horizontal') {
|
|
55
|
+
args.push('-h');
|
|
56
|
+
} else if (split === 'vertical') {
|
|
57
|
+
args.push('-v');
|
|
58
|
+
} else {
|
|
59
|
+
throw new TypeError('tmux split must be horizontal or vertical');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (target !== undefined) {
|
|
63
|
+
args.push('-t', requireName(target));
|
|
64
|
+
}
|
|
65
|
+
if (!pane && name !== undefined) {
|
|
66
|
+
args.push('-n', requireName(name));
|
|
67
|
+
}
|
|
68
|
+
addCwd(args, cwd);
|
|
69
|
+
return tmux.runTmux(args);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function sendKeys(paneId, command) {
|
|
73
|
+
if (typeof paneId !== 'string' || paneId.trim() === '') {
|
|
74
|
+
throw new TypeError('tmux pane id must be a non-empty string');
|
|
75
|
+
}
|
|
76
|
+
if (typeof command !== 'string') {
|
|
77
|
+
throw new TypeError('tmux command must be a string');
|
|
78
|
+
}
|
|
79
|
+
return tmux.runTmux(['send-keys', '-t', paneId, command, 'C-m']);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = {
|
|
83
|
+
ensureTmuxAvailable,
|
|
84
|
+
sessionExists,
|
|
85
|
+
createSession,
|
|
86
|
+
attachSession,
|
|
87
|
+
newWindowOrPane,
|
|
88
|
+
sendKeys,
|
|
89
|
+
};
|
|
@@ -27,6 +27,7 @@ GUARDEX_ON=1
|
|
|
27
27
|
- Work from an `agent/*` branch and worktree, never directly on the protected base branch.
|
|
28
28
|
- Claim files before edits.
|
|
29
29
|
- Use Colony for coordination before falling back to OMX state/notepad.
|
|
30
|
+
- Prefer fff MCP tools for file search whenever available; do not route file search through RTK when fff can answer it.
|
|
30
31
|
- Use OpenSpec for durable behavior contracts and change-driven work.
|
|
31
32
|
- Keep outputs compact: less word, same proof.
|
|
32
33
|
- Commit, push, and open/update a PR for completed work unless the user explicitly says to keep it local.
|
|
@@ -134,6 +135,25 @@ Default: less word, same proof.
|
|
|
134
135
|
- Treat local edit/commit, remote publish/PR, CI diagnosis, and cleanup as bounded phases.
|
|
135
136
|
- Do not spend fresh narration or approval turns on obvious safe follow-ons inside an already authorized phase unless the risk changes.
|
|
136
137
|
|
|
138
|
+
### RTK command compression
|
|
139
|
+
|
|
140
|
+
When `rtk` is available, prefer it for noisy shell discovery and verification. For file search, fff MCP takes precedence whenever available.
|
|
141
|
+
|
|
142
|
+
- Files: `rtk ls .`, `rtk read <file>`, `rtk read <file> -l aggressive`, `rtk smart <file>`, `rtk find "<glob>" .`, `rtk grep "<pattern>" .`, `rtk diff <a> <b>`.
|
|
143
|
+
- Git and GitHub: `rtk git status`, `rtk git diff`, `rtk git log -n 10`, `rtk gh pr list`, `rtk gh pr view <id>`.
|
|
144
|
+
- Tests and builds: `rtk test <cmd>`, `rtk err <cmd>`, `rtk jest`, `rtk vitest`, `rtk playwright test`, `rtk pytest`, `rtk cargo test`, `rtk tsc`, `rtk lint`.
|
|
145
|
+
- Runtime and data probes: `rtk docker ps`, `rtk docker logs <container>`, `rtk kubectl pods`, `rtk json <file>`, `rtk log <file>`, `rtk curl <url>`.
|
|
146
|
+
- Savings checks: `rtk gain`, `rtk discover`, and `rtk session`.
|
|
147
|
+
- Use `rtk proxy <command>` only when raw passthrough is required.
|
|
148
|
+
- Do not wrap machine-readable commands with RTK when code parses stdout (`--porcelain`, `--json`, NUL-delimited output, or exact stdout contracts).
|
|
149
|
+
- If `rtk` is missing, use raw commands and summarize only meaningful lines.
|
|
150
|
+
|
|
151
|
+
### FFF file search
|
|
152
|
+
|
|
153
|
+
Use the fff MCP tools for all file search operations instead of default tools, including RTK shell wrappers.
|
|
154
|
+
|
|
155
|
+
If fff MCP tools are unavailable in the current client, fall back to `rtk grep`, `rtk find`, `rtk ls`, or `rg` and keep output compact.
|
|
156
|
+
|
|
137
157
|
### Caveman style
|
|
138
158
|
|
|
139
159
|
Commentary and progress updates use smart-caveman `ultra` by default:
|
|
@@ -295,7 +315,13 @@ Task is complete only when:
|
|
|
295
315
|
|
|
296
316
|
If anything blocks, append a `BLOCKED:` note and stop. Do not half-finish.
|
|
297
317
|
|
|
298
|
-
OMX completion policy: when a task is done, the agent must
|
|
318
|
+
OMX completion policy: when a task is done, the agent must run `gx branch finish --branch "<agent-branch>" --via-pr --wait-for-merge --cleanup` (or `gx finish --all`) instead of standalone `git push` / `gh pr` commands. The finish flow owns commit, push, PR creation/update, merge wait, and sandbox cleanup.
|
|
319
|
+
|
|
320
|
+
External approval boundary:
|
|
321
|
+
|
|
322
|
+
- Guardex cannot bypass Codex host approval prompts or external-remote policy decisions.
|
|
323
|
+
- When the host blocks a publish or finish command, request approval for the narrow `gx branch finish ...` command, or for the exact session wrapper that invokes it, and continue after approval.
|
|
324
|
+
- Do not replace the finish flow with repeated standalone `git push` / `gh pr` attempts. That increases approval churn and can strand PR, merge, or cleanup state.
|
|
299
325
|
|
|
300
326
|
### Parallel safety
|
|
301
327
|
|
|
@@ -9,3 +9,5 @@ Use when repo safety may be broken.
|
|
|
9
9
|
|
|
10
10
|
Bootstrap: `gx setup`
|
|
11
11
|
Ops: `gx branch start "<task>" "<agent>"`, `gx locks claim --branch "<agent-branch>" <file...>`, `gx branch finish --branch "<agent-branch>" --base <base> --via-pr --wait-for-merge --cleanup`, `gx finish --all`, `gx cleanup`
|
|
12
|
+
|
|
13
|
+
When inspecting or verifying, prefer `rtk` compact wrappers if available (`rtk git status`, `rtk grep`, `rtk test <cmd>`). Do not wrap commands whose stdout is parsed by scripts.
|
|
@@ -207,11 +207,32 @@ fi
|
|
|
207
207
|
|
|
208
208
|
if [[ "$branch" == agent/* ]]; then
|
|
209
209
|
if [[ "${GUARDEX_AUTOCLAIM_STAGED_LOCKS:-1}" == "1" ]]; then
|
|
210
|
+
# Auto-claim non-deletion staged paths. Deletions need an explicit
|
|
211
|
+
# `--allow-delete` flag below so `locks validate --staged` doesn't
|
|
212
|
+
# reject the commit on the same trip the user staged the delete.
|
|
210
213
|
while IFS= read -r staged_file; do
|
|
211
214
|
[[ -z "$staged_file" ]] && continue
|
|
212
215
|
[[ "$staged_file" == ".omx/state/agent-file-locks.json" ]] && continue
|
|
213
216
|
run_guardex_cli locks claim --branch "$branch" "$staged_file" >/dev/null 2>&1 || true
|
|
214
|
-
done < <(git diff --cached --name-only --diff-filter=
|
|
217
|
+
done < <(git diff --cached --name-only --diff-filter=ACMRTUXB)
|
|
218
|
+
|
|
219
|
+
# Auto-approve deletions for the same branch (gated separately so
|
|
220
|
+
# operators can disable this single behavior without disabling the
|
|
221
|
+
# broader auto-claim). Defaults to enabled — matches the auto-claim
|
|
222
|
+
# default and removes the "first commit fails, then `gx locks
|
|
223
|
+
# allow-delete`, then commit again" loop.
|
|
224
|
+
if [[ "${GUARDEX_AUTOCLAIM_STAGED_DELETES:-1}" == "1" ]]; then
|
|
225
|
+
_staged_deletes=()
|
|
226
|
+
while IFS= read -r staged_delete; do
|
|
227
|
+
[[ -z "$staged_delete" ]] && continue
|
|
228
|
+
[[ "$staged_delete" == ".omx/state/agent-file-locks.json" ]] && continue
|
|
229
|
+
_staged_deletes+=("$staged_delete")
|
|
230
|
+
done < <(git diff --cached --name-only --diff-filter=D)
|
|
231
|
+
if (( ${#_staged_deletes[@]} > 0 )); then
|
|
232
|
+
run_guardex_cli locks claim --branch "$branch" --allow-delete \
|
|
233
|
+
"${_staged_deletes[@]}" >/dev/null 2>&1 || true
|
|
234
|
+
fi
|
|
235
|
+
fi
|
|
215
236
|
fi
|
|
216
237
|
|
|
217
238
|
if ! run_guardex_cli locks validate --branch "$branch" --staged; then
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# `templates/github/workflows/` — budget-friendly CI defaults
|
|
2
|
+
|
|
3
|
+
Workflow files in this directory are copied into a gitguardex-managed
|
|
4
|
+
project's `.github/workflows/` directory when bootstrapping. They are
|
|
5
|
+
the **default** budget posture for projects that use `gx branch start`
|
|
6
|
+
to drive agent iterations.
|
|
7
|
+
|
|
8
|
+
Agent flows land a high volume of PRs per month. Without these trims,
|
|
9
|
+
every PR + every post-merge push fans out across CI, CodeQL, Scorecard,
|
|
10
|
+
and Code Review — which dominates the GitHub Actions bill for any
|
|
11
|
+
multi-agent repo. The trims below cut that cost without giving up
|
|
12
|
+
correctness coverage.
|
|
13
|
+
|
|
14
|
+
## What's trimmed and why
|
|
15
|
+
|
|
16
|
+
1. **`concurrency: cancel-in-progress: true`** scoped per workflow + ref
|
|
17
|
+
so rapid pushes to the same agent branch cancel the prior run
|
|
18
|
+
instead of letting both finish on Actions minutes.
|
|
19
|
+
|
|
20
|
+
2. **`if: github.event.pull_request.draft == false`** on every job that
|
|
21
|
+
shouldn't run on a draft PR, paired with
|
|
22
|
+
`pull_request.types: [..., ready_for_review]` in the trigger list so
|
|
23
|
+
CI fires the moment the PR is promoted out of draft.
|
|
24
|
+
|
|
25
|
+
3. **`if: !startsWith(head.ref, 'agent/')`** on the Code Review job
|
|
26
|
+
(`cr.yml`) — skip AI review on automated agent-lane PRs. AI review
|
|
27
|
+
on hundreds of agent PRs per month burns both Actions minutes and
|
|
28
|
+
OpenAI tokens without adding signal; human-authored PRs (any non-
|
|
29
|
+
`agent/*` head branch) still get reviewed.
|
|
30
|
+
|
|
31
|
+
4. **No `push: main` trigger** in `ci.yml` — branch protection on
|
|
32
|
+
`main` forces all changes through a PR, so PR-time CI is sufficient
|
|
33
|
+
and post-merge CI on `main` was pure duplication. Use
|
|
34
|
+
`workflow_dispatch` for ad-hoc full runs.
|
|
35
|
+
|
|
36
|
+
5. **`paths-ignore`** for docs / openspec / template-only changes — skip
|
|
37
|
+
CI on changes that don't affect runtime behavior.
|
|
38
|
+
|
|
39
|
+
## Customizing
|
|
40
|
+
|
|
41
|
+
- Replace `placeholder` steps in `ci.yml` with your build/test/lint
|
|
42
|
+
commands.
|
|
43
|
+
- Keep the `concurrency:`, `if:`, and `paths-ignore:` patterns. They
|
|
44
|
+
are the load-bearing part of the budget posture; removing them undoes
|
|
45
|
+
the win.
|
|
46
|
+
|
|
47
|
+
## When to skip the draft-skip pattern
|
|
48
|
+
|
|
49
|
+
If your CI is fast (≤ 2 min) and you want continuous validation as
|
|
50
|
+
agents iterate, drop the `if: pull_request.draft == false` job guard.
|
|
51
|
+
The concurrency cancel alone still prevents minute pile-up.
|
|
52
|
+
|
|
53
|
+
## When to re-enable AI code review on agent PRs
|
|
54
|
+
|
|
55
|
+
If your team relies on AI review as a true gating signal (not just
|
|
56
|
+
advisory), remove the `!startsWith(head.ref, 'agent/')` guard in
|
|
57
|
+
`cr.yml`. Expect the OpenAI bill to scale linearly with merge volume.
|
|
58
|
+
|
|
59
|
+
## Per-PR label opt-in
|
|
60
|
+
|
|
61
|
+
Both `cr.yml` and `ci-full.yml` honor PR labels so the occasional
|
|
62
|
+
agent PR that actually needs the heavier check can opt in without
|
|
63
|
+
flipping a global toggle:
|
|
64
|
+
|
|
65
|
+
| Label | Effect |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| `needs-review` | Run AI code review on this PR even though it's `agent/*`. Useful for security-sensitive changes or public-API redesigns. |
|
|
68
|
+
| `needs-ci-full` | Run the full cross-runtime matrix from `ci-full.yml` on this PR instead of waiting for the weekly schedule. Useful before a release branch lands. |
|
|
69
|
+
|
|
70
|
+
To enable: open the PR, then `gh pr edit <num> --add-label needs-review`
|
|
71
|
+
(or click the labels picker in the GitHub UI). The label-trigger fires
|
|
72
|
+
the workflow immediately; you don't need to re-push.
|
|
73
|
+
|
|
74
|
+
Add label definitions to your repo with `gh label create needs-review
|
|
75
|
+
--description "Run AI code review on this PR"` and similar for
|
|
76
|
+
`needs-ci-full`, or define them in `.github/labels.yml` if you use a
|
|
77
|
+
label-sync workflow.
|
|
78
|
+
|
|
79
|
+
## What about CodeQL / Scorecard?
|
|
80
|
+
|
|
81
|
+
The gitguardex repo itself runs CodeQL and Scorecard on the **weekly
|
|
82
|
+
schedule + `workflow_dispatch`** only — not on per-PR / per-push
|
|
83
|
+
triggers. Those workflows are long-running (5–10 min for CodeQL) and
|
|
84
|
+
were the largest single line item on the monthly Actions bill before
|
|
85
|
+
this change. If your project needs per-PR CodeQL gating for compliance
|
|
86
|
+
reasons, re-add the `pull_request` trigger and accept the cost; for
|
|
87
|
+
most repos, weekly + on-demand is the right default.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Optional companion to `ci.yml`. Drop in alongside it when your
|
|
2
|
+
# project supports multiple runtimes / OS combinations and you want
|
|
3
|
+
# coverage across all of them without paying per-PR.
|
|
4
|
+
#
|
|
5
|
+
# Strategy: PR-time `ci.yml` runs the primary runtime only (cheap).
|
|
6
|
+
# This workflow runs the full matrix on the weekly schedule, and
|
|
7
|
+
# on-demand via `workflow_dispatch` before a release. Per-PR opt-in
|
|
8
|
+
# is available by applying the `needs-ci-full` label to a PR.
|
|
9
|
+
#
|
|
10
|
+
# Customize the matrix rows below to match your supported runtimes.
|
|
11
|
+
|
|
12
|
+
name: CI (full matrix)
|
|
13
|
+
|
|
14
|
+
on:
|
|
15
|
+
schedule:
|
|
16
|
+
- cron: '15 4 * * 1'
|
|
17
|
+
workflow_dispatch:
|
|
18
|
+
pull_request:
|
|
19
|
+
types: [labeled, synchronize]
|
|
20
|
+
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
|
|
24
|
+
concurrency:
|
|
25
|
+
group: ci-full-${{ github.workflow }}-${{ github.ref }}
|
|
26
|
+
cancel-in-progress: true
|
|
27
|
+
|
|
28
|
+
jobs:
|
|
29
|
+
test:
|
|
30
|
+
name: test (node ${{ matrix.node }})
|
|
31
|
+
# PR runs only fire when the `needs-ci-full` label is present.
|
|
32
|
+
# Schedule and workflow_dispatch always run.
|
|
33
|
+
if: >-
|
|
34
|
+
github.event_name != 'pull_request' ||
|
|
35
|
+
contains(github.event.pull_request.labels.*.name, 'needs-ci-full') ||
|
|
36
|
+
(github.event.action == 'labeled' && github.event.label.name == 'needs-ci-full')
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
strategy:
|
|
39
|
+
fail-fast: false
|
|
40
|
+
matrix:
|
|
41
|
+
node: [18, 22]
|
|
42
|
+
|
|
43
|
+
steps:
|
|
44
|
+
- name: Checkout
|
|
45
|
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
46
|
+
|
|
47
|
+
- name: Setup Node
|
|
48
|
+
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
49
|
+
with:
|
|
50
|
+
node-version: ${{ matrix.node }}
|
|
51
|
+
|
|
52
|
+
# Replace below with your build/test/lint steps. Keep them parallel
|
|
53
|
+
# to `ci.yml` so the weekly matrix matches what runs per-PR.
|
|
54
|
+
- name: Project verification placeholder
|
|
55
|
+
run: echo "Replace this step with your build/test/lint commands."
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Budget-friendly CI default for gitguardex-managed projects.
|
|
2
|
+
#
|
|
3
|
+
# Four trims keep Actions minutes low while agent branches iterate:
|
|
4
|
+
# 1. `concurrency: cancel-in-progress` — rapid pushes to the same ref
|
|
5
|
+
# kill the prior run instead of letting both finish.
|
|
6
|
+
# 2. Job-level `if: pull_request.draft == false` plus the
|
|
7
|
+
# `ready_for_review` PR trigger — draft PRs skip CI, and CI fires
|
|
8
|
+
# automatically the moment the PR is promoted out of draft.
|
|
9
|
+
# 3. `paths-ignore` for docs / openspec / template-only changes —
|
|
10
|
+
# skip CI on changes that don't affect runtime behavior.
|
|
11
|
+
# 4. No `push: main` trigger — branch-protection-required PR runs
|
|
12
|
+
# already cover correctness, and post-merge CI on main is pure
|
|
13
|
+
# duplication. Use `workflow_dispatch` for ad-hoc full runs.
|
|
14
|
+
#
|
|
15
|
+
# Copy this file to `.github/workflows/ci.yml` in your project and
|
|
16
|
+
# replace the placeholder `steps:` block with your build/test/lint
|
|
17
|
+
# commands.
|
|
18
|
+
|
|
19
|
+
name: CI
|
|
20
|
+
|
|
21
|
+
on:
|
|
22
|
+
pull_request:
|
|
23
|
+
branches:
|
|
24
|
+
- main
|
|
25
|
+
types: [opened, reopened, synchronize, ready_for_review]
|
|
26
|
+
paths-ignore:
|
|
27
|
+
- '**/*.md'
|
|
28
|
+
- 'docs/**'
|
|
29
|
+
- 'openspec/**'
|
|
30
|
+
- '.github/ISSUE_TEMPLATE/**'
|
|
31
|
+
- '.github/PULL_REQUEST_TEMPLATE.md'
|
|
32
|
+
- '.changeset/**'
|
|
33
|
+
workflow_dispatch:
|
|
34
|
+
|
|
35
|
+
permissions:
|
|
36
|
+
contents: read
|
|
37
|
+
|
|
38
|
+
concurrency:
|
|
39
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
40
|
+
cancel-in-progress: true
|
|
41
|
+
|
|
42
|
+
jobs:
|
|
43
|
+
build:
|
|
44
|
+
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
steps:
|
|
47
|
+
- name: Checkout
|
|
48
|
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
49
|
+
|
|
50
|
+
# Replace below with your build/test/lint steps. Examples:
|
|
51
|
+
# - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
52
|
+
# with: { node-version: '20' }
|
|
53
|
+
# - run: npm ci
|
|
54
|
+
# - run: npm test
|
|
55
|
+
- name: Project verification placeholder
|
|
56
|
+
run: echo "Replace this step with your build/test/lint commands."
|
|
@@ -2,14 +2,33 @@ name: Code Review
|
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
pull_request:
|
|
5
|
-
types: [opened, reopened, synchronize]
|
|
5
|
+
types: [opened, reopened, synchronize, ready_for_review, labeled]
|
|
6
6
|
|
|
7
7
|
permissions:
|
|
8
8
|
contents: read
|
|
9
9
|
pull-requests: write
|
|
10
10
|
|
|
11
|
+
# Budget-friendly default for gitguardex-managed projects: cancel
|
|
12
|
+
# superseded runs on the same PR so rapid agent pushes don't fan-out
|
|
13
|
+
# the OpenAI bill.
|
|
14
|
+
concurrency:
|
|
15
|
+
group: cr-${{ github.workflow }}-${{ github.event.pull_request.number }}
|
|
16
|
+
cancel-in-progress: true
|
|
17
|
+
|
|
11
18
|
jobs:
|
|
12
19
|
review:
|
|
20
|
+
# Skip on draft PRs and on `agent/*` head branches by default.
|
|
21
|
+
# Agent PRs can opt-in by applying the `needs-review` label —
|
|
22
|
+
# useful for the occasional agent PR that genuinely needs AI
|
|
23
|
+
# eyes (security-sensitive change, public-API redesign, etc.).
|
|
24
|
+
# Human-authored PRs (any non-`agent/*` head branch) always run.
|
|
25
|
+
if: >-
|
|
26
|
+
github.event.pull_request.draft == false &&
|
|
27
|
+
(
|
|
28
|
+
!startsWith(github.event.pull_request.head.ref, 'agent/') ||
|
|
29
|
+
contains(github.event.pull_request.labels.*.name, 'needs-review') ||
|
|
30
|
+
(github.event.action == 'labeled' && github.event.label.name == 'needs-review')
|
|
31
|
+
)
|
|
13
32
|
runs-on: ubuntu-latest
|
|
14
33
|
env:
|
|
15
34
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|