@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.
Files changed (85) hide show
  1. package/README.md +68 -13
  2. package/package.json +2 -1
  3. package/skills/gitguardex/SKILL.md +13 -0
  4. package/skills/guardex-merge-skills-to-dev/SKILL.md +59 -0
  5. package/src/agents/cleanup-sessions.js +126 -0
  6. package/src/agents/detect.js +160 -0
  7. package/src/agents/finish.js +172 -0
  8. package/src/agents/inspect.js +189 -0
  9. package/src/agents/launch.js +240 -0
  10. package/src/agents/registry.js +133 -0
  11. package/src/agents/selection-panel.js +571 -0
  12. package/src/agents/sessions.js +151 -0
  13. package/src/agents/start.js +591 -0
  14. package/src/agents/status.js +143 -0
  15. package/src/agents/terminal.js +152 -0
  16. package/src/budget/index.js +343 -0
  17. package/src/ci-init/index.js +265 -0
  18. package/src/cli/args.js +305 -1
  19. package/src/cli/main.js +262 -132
  20. package/src/cockpit/action-runner.js +3 -0
  21. package/src/cockpit/actions.js +80 -0
  22. package/src/cockpit/control.js +1121 -0
  23. package/src/cockpit/index.js +426 -0
  24. package/src/cockpit/keybindings.js +224 -0
  25. package/src/cockpit/kitty-layout.js +549 -0
  26. package/src/cockpit/kitty-tree.js +144 -0
  27. package/src/cockpit/layout.js +224 -0
  28. package/src/cockpit/logs-reader.js +182 -0
  29. package/src/cockpit/menu.js +204 -0
  30. package/src/cockpit/pane-actions.js +597 -0
  31. package/src/cockpit/pane-menu.js +387 -0
  32. package/src/cockpit/projects-finder.js +178 -0
  33. package/src/cockpit/render.js +215 -0
  34. package/src/cockpit/settings-render.js +128 -0
  35. package/src/cockpit/settings.js +124 -0
  36. package/src/cockpit/shortcuts.js +24 -0
  37. package/src/cockpit/sidebar.js +311 -0
  38. package/src/cockpit/state.js +72 -0
  39. package/src/cockpit/theme.js +128 -0
  40. package/src/cockpit/welcome.js +266 -0
  41. package/src/context.js +76 -33
  42. package/src/doctor/index.js +3 -2
  43. package/src/finish/index.js +39 -2
  44. package/src/git/index.js +65 -0
  45. package/src/kitty/command.js +101 -0
  46. package/src/kitty/runtime.js +250 -0
  47. package/src/output/index.js +1 -1
  48. package/src/pr-review.js +241 -0
  49. package/src/scaffold/index.js +19 -0
  50. package/src/submodule/index.js +288 -0
  51. package/src/terminal/index.js +120 -0
  52. package/src/terminal/kitty.js +622 -0
  53. package/src/terminal/tmux.js +126 -0
  54. package/src/tmux/command.js +27 -0
  55. package/src/tmux/session.js +89 -0
  56. package/templates/AGENTS.multiagent-safety.md +27 -1
  57. package/templates/codex/skills/gitguardex/SKILL.md +2 -0
  58. package/templates/githooks/pre-commit +22 -1
  59. package/templates/github/workflows/README.md +87 -0
  60. package/templates/github/workflows/ci-full.yml +55 -0
  61. package/templates/github/workflows/ci.yml +56 -0
  62. package/templates/github/workflows/cr.yml +20 -1
  63. package/templates/scripts/agent-branch-finish.sh +544 -26
  64. package/templates/scripts/agent-branch-start.sh +89 -22
  65. package/templates/scripts/agent-preflight.sh +89 -0
  66. package/templates/scripts/agent-worktree-prune.sh +96 -5
  67. package/templates/scripts/codex-agent.sh +41 -6
  68. package/templates/scripts/openspec/init-plan-workspace.sh +43 -0
  69. package/templates/scripts/review-bot-watch.sh +31 -2
  70. package/templates/scripts/agent-session-state.js +0 -171
  71. package/templates/scripts/install-vscode-active-agents-extension.js +0 -135
  72. package/templates/vscode/guardex-active-agents/README.md +0 -34
  73. package/templates/vscode/guardex-active-agents/extension.js +0 -3782
  74. package/templates/vscode/guardex-active-agents/fileicons/gitguardex-fileicons.json +0 -54
  75. package/templates/vscode/guardex-active-agents/fileicons/icons/agent.svg +0 -5
  76. package/templates/vscode/guardex-active-agents/fileicons/icons/branch.svg +0 -7
  77. package/templates/vscode/guardex-active-agents/fileicons/icons/config.svg +0 -4
  78. package/templates/vscode/guardex-active-agents/fileicons/icons/hook.svg +0 -4
  79. package/templates/vscode/guardex-active-agents/fileicons/icons/openspec.svg +0 -5
  80. package/templates/vscode/guardex-active-agents/fileicons/icons/plan.svg +0 -4
  81. package/templates/vscode/guardex-active-agents/fileicons/icons/spec.svg +0 -5
  82. package/templates/vscode/guardex-active-agents/icon.png +0 -0
  83. package/templates/vscode/guardex-active-agents/media/active-agents-hivemind.svg +0 -14
  84. package/templates/vscode/guardex-active-agents/package.json +0 -169
  85. 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 commit the task changes, push the agent branch, and create/update a PR before considering the branch complete.
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=ACMRDTUXB)
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 }}