agent-orchestrator-kit 0.1.6 → 0.1.8

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/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.1.8] - 2026-07-07
6
+
7
+ ### Changed
8
+ - **README** — Quickstart block (new vs existing project) and upgrade guide for `status` / `gate-check` / GitHub Spec Verifier adoption
9
+
10
+ ## [0.1.7] - 2026-07-07
11
+
12
+ ### Added
13
+ - **`agent-orchestrator status`** — dashboard for active OpenSpec changes: task progress (`N/M`), review verdict, `ready to archive` flag
14
+ - **`agent-orchestrator gate-check`** — deterministic review-gate check; exits non-zero when `src/` changed without an approved `review.md` (`require_spec_review: true`); warns (non-blocking) when active changes exceed `max_active_changes`; graceful no-op without `.agents/orchestrator.yaml`, when review isn't required, or when the diff can't be computed
15
+ - **GitHub AI Spec Verifier** — `init --ci github --spec-verify` installs `.github/workflows/spec-verify.yml` + `scripts/post-pr-verdict-github.sh` (via `gh pr comment`), reusing the existing stack-agnostic `scripts/verify-specs.sh`
16
+ - **OpenSpec specs** — `orchestrator-cli-controls`, `github-spec-verify`
17
+
18
+ ### Changed
19
+ - **`sync`** (CLI) now removes stale skills/rules no longer present in `.agents/skills|rules` — matches `sync-local-agent-skills.sh` (`rsync --delete`); does not touch `memory.json`, `.mcp.json`, `CLAUDE.md`, `settings.json`
20
+ - **`agent-verify.yml`** (both GitHub and GitLab fragments) now run `gate-check` as part of verify
21
+ - **`--spec-verify`** — no longer GitLab-only; valid with `--ci gitlab` or `--ci github`
22
+ - **README / AGENTS.md / agent-orchestration skill** — document `status`, `gate-check`, and GitHub Spec Verifier parity
23
+
5
24
  ## [0.1.6] - 2026-07-02
6
25
 
7
26
  ### Added
@@ -75,6 +94,8 @@ All notable changes to this project will be documented in this file.
75
94
  ### Added
76
95
  - Initial release: 5-role orchestration pipeline, `/opsx:*` commands, IDE sync
77
96
 
97
+ [0.1.8]: https://github.com/makshc2/agent-orchestrator-kit/compare/v0.1.7...v0.1.8
98
+ [0.1.7]: https://github.com/makshc2/agent-orchestrator-kit/compare/v0.1.6...v0.1.7
78
99
  [0.1.5]: https://github.com/makshc2/agent-orchestrator-kit/compare/v0.1.4...v0.1.5
79
100
  [0.1.4]: https://github.com/makshc2/agent-orchestrator-kit/compare/v0.1.3...v0.1.4
80
101
  [0.1.3]: https://github.com/makshc2/agent-orchestrator-kit/compare/v0.1.2...v0.1.3
package/README.md CHANGED
@@ -26,6 +26,28 @@ Without role separation, AI agents tend to mix thinking with implementation, ski
26
26
 
27
27
  The `AGENTS.md` / `CLAUDE.md` files tell each IDE exactly what the roles are, so you don't repeat yourself every session.
28
28
 
29
+ ## Quickstart
30
+
31
+ **🆕 New project:**
32
+
33
+ ```bash
34
+ npm i -D @fission-ai/openspec && npx openspec init
35
+ npx agent-orchestrator-kit@latest init --profile generic --ci gitlab --spec-verify
36
+ ./scripts/sync-local-agent-skills.sh
37
+ ```
38
+
39
+ See [Installation](#installation) for profile/CI options.
40
+
41
+ **🔄 Already have the kit installed? Get `status` / `gate-check` / GitHub Spec Verifier (v0.1.7+):**
42
+
43
+ ```bash
44
+ npx agent-orchestrator-kit@latest update
45
+ ./scripts/sync-local-agent-skills.sh
46
+ npx agent-orchestrator-kit@latest status # try it right away
47
+ ```
48
+
49
+ See [Upgrading an existing project](#upgrading-an-existing-project-to-v017-status--gate-check--github-spec-verifier) for what changes and what stays opt-in.
50
+
29
51
  ## Installation
30
52
 
31
53
  ### Prerequisites
@@ -99,8 +121,9 @@ your-project/
99
121
  ├── AGENTS.md
100
122
  ├── CLAUDE.md
101
123
  ├── .github/workflows/agent-verify.yml # CI (default --ci github)
124
+ ├── .github/workflows/spec-verify.yml # AI Spec Verifier (--ci github --spec-verify, opt-in)
102
125
  ├── .gitlab/agent-verify.yml # CI fragment (--ci gitlab)
103
- ├── .gitlab/spec-verify.yml # AI Spec Verifier (--spec-verify, opt-in)
126
+ ├── .gitlab/spec-verify.yml # AI Spec Verifier (--ci gitlab --spec-verify, opt-in)
104
127
  ├── .agents/
105
128
  │ ├── orchestrator.yaml
106
129
  │ ├── mcp.json.example # Cursor MCP template
@@ -117,7 +140,7 @@ your-project/
117
140
  │ ├── openspec-sync-specs/
118
141
  │ └── spec-workflow-openspec/
119
142
  ├── scripts/sync-local-agent-skills.sh
120
- └── scripts/verify-specs.sh + post-mr-verdict.sh # (--spec-verify, opt-in)
143
+ └── scripts/verify-specs.sh + post-mr-verdict.sh / post-pr-verdict-github.sh # (--spec-verify, opt-in)
121
144
  ```
122
145
 
123
146
  ### Included in kit
@@ -126,9 +149,10 @@ your-project/
126
149
  |----------|----------|
127
150
  | Orchestration | 5-role pipeline, `AGENTS.md`, `orchestrator.yaml`, review command |
128
151
  | OpenSpec skills | All 7 skills for `/opsx:*` workflow |
129
- | IDE sync | Cursor + Claude Code sync script |
130
- | CI | `agent-verify.yml` GitHub (default) or GitLab fragment + `prebuild` hook |
131
- | AI Spec Verifier | `spec-verify.yml` + verifier scripts GitLab opt-in (`--spec-verify`) |
152
+ | IDE sync | Cursor + Claude Code sync script (`--delete` semantics — removes stale skills) |
153
+ | CLI gates | `agent-orchestrator status` / `gate-check` deterministic review-gate checks |
154
+ | CI | `agent-verify.yml` GitHub (default) or GitLab fragment + `prebuild` hook, both run `gate-check` |
155
+ | AI Spec Verifier | `spec-verify.yml` + verifier scripts — GitLab or GitHub, opt-in (`--spec-verify`) |
132
156
  | MCP templates | Memory MCP for Cursor and Amp |
133
157
 
134
158
  ### Not included (install separately)
@@ -329,15 +353,18 @@ Optional: include `.gitlab/agent-verify.yml` in `.gitlab-ci.yml` for full lint/b
329
353
 
330
354
  Blocks merge if any gate fails.
331
355
 
332
- #### AI Spec Verifier (GitLab, opt-in)
356
+ Every CI fragment (`agent-verify.yml`, GitHub and GitLab) also runs `npx agent-orchestrator-kit gate-check` — see [Deterministic gates](#deterministic-gates-status--gate-check) below. It never fails the pipeline for projects without `.agents/orchestrator.yaml`.
357
+
358
+ #### AI Spec Verifier (GitLab or GitHub, opt-in)
333
359
 
334
360
  ```bash
335
361
  npx agent-orchestrator-kit init --ci gitlab --spec-verify
362
+ npx agent-orchestrator-kit init --ci github --spec-verify
336
363
  ```
337
364
 
338
- Installs an AI verification layer on top of the deterministic gates: on every merge request that changes `src/`, an Amp agent reads `openspec/specs/`, checks the changed code against every relevant requirement, posts a **PASS / BLOCKED** comment to the MR, and **fails the pipeline on BLOCKED** — specs become an enforceable merge contract, not just documentation.
365
+ Installs an AI verification layer on top of the deterministic gates: on every merge/pull request that changes `src/`, an Amp agent reads `openspec/specs/`, checks the changed code against every relevant requirement, posts a **PASS / BLOCKED** comment to the MR/PR, and **fails the pipeline on BLOCKED** — specs become an enforceable merge contract, not just documentation.
339
366
 
340
- Installed files:
367
+ Installed files (GitLab):
341
368
 
342
369
  | File | Purpose |
343
370
  |------|---------|
@@ -345,9 +372,17 @@ Installed files:
345
372
  | `scripts/verify-specs.sh` | Collects changed files + specs, builds prompt (project context from `openspec/config.yaml`), calls `amp -x`, writes `artifacts/verdict.json` |
346
373
  | `scripts/post-mr-verdict.sh` | Posts the verdict as an MR comment via GitLab API |
347
374
 
375
+ Installed files (GitHub):
376
+
377
+ | File | Purpose |
378
+ |------|---------|
379
+ | `.github/workflows/spec-verify.yml` | Workflow triggered on `pull_request` for `src/**` — same verdict evaluation, `permissions: pull-requests: write` |
380
+ | `scripts/verify-specs.sh` | Same script as GitLab — stack-agnostic, reused as-is |
381
+ | `scripts/post-pr-verdict-github.sh` | Posts the verdict as a PR comment via `gh pr comment` |
382
+
348
383
  The flag also adds `spec-verify-blocking` to `roles.verifier.gates` in `.agents/orchestrator.yaml`.
349
384
 
350
- Setup after install:
385
+ Setup after install (GitLab):
351
386
 
352
387
  1. Include the fragment from `.gitlab-ci.yml`:
353
388
 
@@ -358,11 +393,31 @@ include:
358
393
 
359
394
  2. Add CI/CD variables (Settings → CI/CD → Variables, masked): `AMP_API_KEY`, `GITLAB_VERIFIER_TOKEN` (project access token with `api` scope).
360
395
 
396
+ Setup after install (GitHub): the workflow runs automatically on `pull_request` — just add the repo secret `AMP_API_KEY` (Settings → Secrets and variables → Actions). `GITHUB_TOKEN` is provided by Actions automatically.
397
+
361
398
  Verdict schema (`artifacts/verdict.json`): `pass`, `score` (0–100), `summary`, `findings[]` with `severity` (`error` fails the job), `spec`, `requirement`, `message`, `file`. The script degrades gracefully — no `src/` changes, no specs, missing `amp` CLI, or missing `AMP_API_KEY` produce a skipped passing verdict and never block the pipeline. Secrets are never logged; `.env`/key/token files are excluded from prompts.
362
399
 
363
- **Warning-only rollout (Phase 1):** uncomment `allow_failure: true` in `.gitlab/spec-verify.yml` to keep the pipeline green while the team builds trust in verdicts, then remove it to enforce blocking (Phase 2).
400
+ **Warning-only rollout (Phase 1):** uncomment `allow_failure: true` (GitLab) or `continue-on-error: true` (GitHub) to keep the pipeline green while the team builds trust in verdicts, then remove it to enforce blocking (Phase 2).
364
401
 
365
- `update` refreshes the three spec-verify files only in projects that already installed them — the feature stays opt-in.
402
+ `update` refreshes the spec-verify files only in projects that already installed them — the feature stays opt-in.
403
+
404
+ ---
405
+
406
+ ### Deterministic gates: `status` / `gate-check`
407
+
408
+ Orchestration hard rules (review approval, one active change) used to rely entirely on the agent remembering to check them in chat. Two CLI commands make them checkable and CI-enforceable:
409
+
410
+ ```bash
411
+ npx agent-orchestrator-kit status
412
+ ```
413
+
414
+ Prints every active OpenSpec change with task progress (`N/M tasks`), review verdict (`APPROVE` / `REQUEST CHANGES` / `none`), and a `ready to archive` flag once all tasks are `[x]` — no more running `openspec status` per change by hand.
415
+
416
+ ```bash
417
+ npx agent-orchestrator-kit gate-check [change-name] [--src-glob src/] [--base HEAD~1]
418
+ ```
419
+
420
+ Fails (non-zero exit) when `pipeline.require_spec_review: true`, the diff against `--base` touches `--src-glob`, and the active change has no `review.md` with `Verdict: APPROVE`. It degrades gracefully to exit 0 (with a message, not silently) when: `.agents/orchestrator.yaml` is missing, review isn't required, the diff can't be computed (e.g. shallow clone), or nothing under `--src-glob` changed. It also warns (never fails) when active changes exceed `pipeline.max_active_changes`. Both `agent-verify.yml` fragments (GitHub and GitLab) call `gate-check` automatically.
366
421
 
367
422
  ---
368
423
 
@@ -411,6 +466,31 @@ npx agent-orchestrator-kit update
411
466
  - `openspec/changes/`
412
467
  - Any project-conventions skills
413
468
 
469
+ ### Upgrading an existing project to v0.1.7 (status / gate-check / GitHub Spec Verifier)
470
+
471
+ If the kit is already installed and you just want the new deterministic gates, no re-`init` needed:
472
+
473
+ ```bash
474
+ npx agent-orchestrator-kit@latest update
475
+ ./scripts/sync-local-agent-skills.sh
476
+ ```
477
+
478
+ What this gets you automatically:
479
+ - `.github/workflows/agent-verify.yml` / `.gitlab/agent-verify.yml` refreshed with a `gate-check` step (fails CI if `src/` changed without an approved `review.md`)
480
+ - `sync` (both the CLI command and the shell script) starts removing skills that no longer exist in `.agents/skills/`
481
+ - `agent-orchestrator status` and `agent-orchestrator gate-check` are available immediately (they ship inside `bin/`, not as opt-in templates) — try `npx agent-orchestrator-kit@latest status` right away
482
+
483
+ Two things `update` will **not** do for you (by design — opt-in, and it never touches your CI root file):
484
+
485
+ 1. **GitLab-only projects that already had `--spec-verify`** — `update` refreshes `.gitlab/spec-verify.yml` and the scripts automatically (only because they already exist in your project).
486
+ 2. **Adding GitHub Spec Verifier where you didn't have it before** — that's a new opt-in, run it once:
487
+ ```bash
488
+ npx agent-orchestrator-kit@latest init --ci github --spec-verify
489
+ ```
490
+ then add the `AMP_API_KEY` repo secret (Settings → Secrets and variables → Actions).
491
+
492
+ Nothing about `update` retroactively edits your `.gitlab-ci.yml` / already-included workflows — if `gate-check` doesn't seem to run, check that your `.gitlab-ci.yml` still `include`s `.gitlab/agent-verify.yml` (GitHub Actions picks up `.github/workflows/*.yml` automatically, no include step needed).
493
+
414
494
  ## Profiles
415
495
 
416
496
  | Profile | Stack | Extra (separate install) |
@@ -498,15 +578,26 @@ npx agent-orchestrator-kit init [options]
498
578
  --lang <code> Agent language: en | uk | ...
499
579
  --name <name> Project name (default: directory name)
500
580
  --ci <provider> CI provider: gitlab | github | none (default: github)
501
- --spec-verify Install AI Spec Verifier blocking gate (GitLab only)
581
+ --spec-verify Install AI Spec Verifier blocking gate (GitLab or GitHub)
502
582
  --force Overwrite existing files
503
583
 
504
584
  npx agent-orchestrator-kit update
505
585
  Updates kit-managed files, preserves project overlay
506
586
 
507
587
  npx agent-orchestrator-kit sync [options]
508
- --target <ide> cursor | claude | all (default: all)
509
- Copies .agents/ to local IDE directories
588
+ --target <ide> cursor | claude | amp | all (default: all)
589
+ Copies .agents/ to local IDE directories, removing skills/rules no longer
590
+ present in .agents/ (does not touch memory.json, .mcp.json, CLAUDE.md, etc.)
591
+
592
+ npx agent-orchestrator-kit status
593
+ Show progress, review verdict, and archive-readiness for active changes
594
+
595
+ npx agent-orchestrator-kit gate-check [change-name] [options]
596
+ --src-glob <glob> Source path filter used to detect code changes (default: src/)
597
+ --base <ref> Git ref to diff against (default: HEAD~1)
598
+ Exit non-zero when require_spec_review is true, src/ changed, and the
599
+ active change has no review.md with Verdict: APPROVE. Graceful no-op
600
+ otherwise (missing config, review not required, no relevant diff).
510
601
  ```
511
602
 
512
603
  ## Directory Reference
@@ -540,6 +631,15 @@ openspec/ # Committed — spec-driven workflow
540
631
 
541
632
  ## Changelog
542
633
 
634
+ ### 0.1.8
635
+ - README — Quickstart (new vs existing project) and upgrade guide for adopting `status`, `gate-check`, and GitHub Spec Verifier
636
+
637
+ ### 0.1.7
638
+ - `agent-orchestrator status` — dashboard for active OpenSpec changes: task progress, review verdict, archive readiness
639
+ - `agent-orchestrator gate-check` — deterministic review-gate check (exit non-zero without an approved `review.md`); wired into both `agent-verify.yml` CI fragments
640
+ - `init --ci github --spec-verify` — GitHub parity for the AI Spec Verifier (`.github/workflows/spec-verify.yml`, `scripts/post-pr-verdict-github.sh`), reusing the existing stack-agnostic `verify-specs.sh`
641
+ - `sync` (CLI) now removes skills/rules that no longer exist in `.agents/` — matches `sync-local-agent-skills.sh` (`rsync --delete`) behavior; leaves `memory.json`, `.mcp.json`, `CLAUDE.md`, `settings.json` untouched
642
+
543
643
  ### 0.1.6
544
644
  - `init --ci gitlab --spec-verify` — opt-in AI Spec Verifier: blocking MR gate via Amp CLI
545
645
  - Templates: `.gitlab/spec-verify.yml`, `scripts/verify-specs.sh`, `scripts/post-mr-verdict.sh`
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { program } from 'commander';
3
3
  import pc from 'picocolors';
4
- import { readFileSync, existsSync, mkdirSync, copyFileSync, readdirSync, statSync, writeFileSync } from 'fs';
4
+ import { readFileSync, existsSync, mkdirSync, copyFileSync, readdirSync, statSync, writeFileSync, rmSync } from 'fs';
5
5
  import { join, dirname, basename } from 'path';
6
6
  import { fileURLToPath } from 'url';
7
7
  import { execSync } from 'child_process';
@@ -32,12 +32,25 @@ const KIT_MANAGED_PATHS = [
32
32
  'scripts/sync-local-agent-skills.sh',
33
33
  ];
34
34
 
35
- // Opt-in files: refreshed by `update` only when already present in the project
36
- const KIT_OPTIN_PATHS = [
35
+ // Opt-in AI Spec Verifier files, per CI provider. `scripts/verify-specs.sh` is
36
+ // shared it is stack- and CI-agnostic already.
37
+ const GITLAB_SPEC_VERIFY_PATHS = [
37
38
  '.gitlab/spec-verify.yml',
38
39
  'scripts/verify-specs.sh',
39
40
  'scripts/post-mr-verdict.sh',
40
41
  ];
42
+ const GITHUB_SPEC_VERIFY_PATHS = [
43
+ '.github/workflows/spec-verify.yml',
44
+ 'scripts/verify-specs.sh',
45
+ 'scripts/post-pr-verdict-github.sh',
46
+ ];
47
+
48
+ // Opt-in files: refreshed by `update` only when already present in the project
49
+ const KIT_OPTIN_PATHS = [...new Set([...GITLAB_SPEC_VERIFY_PATHS, ...GITHUB_SPEC_VERIFY_PATHS])];
50
+
51
+ function specVerifyPathsFor(ci) {
52
+ return ci === 'github' ? GITHUB_SPEC_VERIFY_PATHS : GITLAB_SPEC_VERIFY_PATHS;
53
+ }
41
54
 
42
55
  const VALID_CI_PROVIDERS = ['gitlab', 'github', 'none'];
43
56
  const VERIFY_OPENSPEC_SCRIPT = 'npx openspec validate --all --strict';
@@ -53,10 +66,11 @@ const log = {
53
66
  };
54
67
 
55
68
  function copyDir(src, dest, opts = {}) {
56
- const { overwrite = true, skip = [] } = opts;
69
+ const { overwrite = true, skip = [], delete: deleteStale = false } = opts;
57
70
  if (!existsSync(src)) return;
58
71
  mkdirSync(dest, { recursive: true });
59
- for (const entry of readdirSync(src)) {
72
+ const srcEntries = readdirSync(src);
73
+ for (const entry of srcEntries) {
60
74
  if (skip.includes(entry)) continue;
61
75
  const srcPath = join(src, entry);
62
76
  const destPath = join(dest, entry);
@@ -71,6 +85,17 @@ function copyDir(src, dest, opts = {}) {
71
85
  log.ok(destPath.replace(process.cwd() + '/', ''));
72
86
  }
73
87
  }
88
+
89
+ // Remove entries that exist in dest but no longer exist in src (e.g. skills
90
+ // removed by a kit `update`), keeping .cursor/.claude in sync with .agents/.
91
+ if (deleteStale && existsSync(dest)) {
92
+ for (const entry of readdirSync(dest)) {
93
+ if (skip.includes(entry) || srcEntries.includes(entry)) continue;
94
+ const destPath = join(dest, entry);
95
+ rmSync(destPath, { recursive: true, force: true });
96
+ log.warn(`removed stale: ${destPath.replace(process.cwd() + '/', '')}`);
97
+ }
98
+ }
74
99
  }
75
100
 
76
101
  function gitignoreLines(content) {
@@ -128,6 +153,59 @@ function hasOpenSpec(projectDir) {
128
153
  return existsSync(join(projectDir, 'openspec', 'config.yaml'));
129
154
  }
130
155
 
156
+ function listActiveChanges(projectDir) {
157
+ const changesDir = join(projectDir, 'openspec', 'changes');
158
+ if (!existsSync(changesDir)) return [];
159
+ return readdirSync(changesDir)
160
+ .filter((name) => name !== 'archive' && statSync(join(changesDir, name)).isDirectory())
161
+ .sort();
162
+ }
163
+
164
+ function parseTasksProgress(changeDir) {
165
+ const tasksPath = join(changeDir, 'tasks.md');
166
+ if (!existsSync(tasksPath)) return null;
167
+ const content = readFileSync(tasksPath, 'utf-8');
168
+ const total = (content.match(/^\s*- \[[ xX]\]/gm) || []).length;
169
+ const done = (content.match(/^\s*- \[[xX]\]/gm) || []).length;
170
+ return { total, done };
171
+ }
172
+
173
+ function parseReviewVerdict(changeDir) {
174
+ const reviewPath = join(changeDir, 'review.md');
175
+ if (!existsSync(reviewPath)) return null;
176
+ const content = readFileSync(reviewPath, 'utf-8');
177
+ const match = content.match(/\*\*Verdict:\*\*\s*(.+)/);
178
+ return match ? match[1].trim() : 'unknown';
179
+ }
180
+
181
+ function readPipelineConfig(projectDir) {
182
+ const orchPath = join(projectDir, '.agents', 'orchestrator.yaml');
183
+ if (!existsSync(orchPath)) return null;
184
+ const content = readFileSync(orchPath, 'utf-8');
185
+ const requireReviewMatch = content.match(/require_spec_review:\s*(true|false)/);
186
+ const maxActiveMatch = content.match(/max_active_changes:\s*(\d+)/);
187
+ return {
188
+ requireSpecReview: requireReviewMatch ? requireReviewMatch[1] === 'true' : true,
189
+ maxActiveChanges: maxActiveMatch ? parseInt(maxActiveMatch[1], 10) : null,
190
+ };
191
+ }
192
+
193
+ // Returns true/false when the diff is known, or null when it could not be
194
+ // determined (no git repo, invalid base ref, shallow clone, etc.) — callers
195
+ // must treat null as "skip gracefully", never as "block".
196
+ function gitDiffTouchesGlob(projectDir, base, srcGlob) {
197
+ try {
198
+ const out = execSync(`git diff --name-only ${base}...HEAD -- "${srcGlob}"`, {
199
+ cwd: projectDir,
200
+ stdio: ['ignore', 'pipe', 'ignore'],
201
+ encoding: 'utf-8',
202
+ });
203
+ return out.trim().length > 0;
204
+ } catch {
205
+ return null;
206
+ }
207
+ }
208
+
131
209
  function installOpenspecConfigExample(projectDir, profile, vars, force) {
132
210
  const src = resolveTemplate('openspec-config.yaml.example', profile);
133
211
  if (!existsSync(src)) return;
@@ -230,8 +308,9 @@ function installCi(projectDir, templateDir, ci, force) {
230
308
  }
231
309
  }
232
310
 
233
- function installSpecVerify(projectDir, templateDir, force) {
234
- for (const rel of KIT_OPTIN_PATHS) {
311
+ function installSpecVerify(projectDir, templateDir, force, ci) {
312
+ const paths = specVerifyPathsFor(ci);
313
+ for (const rel of paths) {
235
314
  const src = join(templateDir, rel);
236
315
  const dest = join(projectDir, rel);
237
316
  if (!existsSync(src)) continue;
@@ -243,8 +322,9 @@ function installSpecVerify(projectDir, templateDir, force) {
243
322
  copyFileSync(src, dest);
244
323
  log.ok(rel);
245
324
  }
325
+ const scripts = paths.filter((rel) => rel.startsWith('scripts/')).map((rel) => join(projectDir, rel));
246
326
  try {
247
- execSync(`chmod +x ${join(projectDir, 'scripts', 'verify-specs.sh')} ${join(projectDir, 'scripts', 'post-mr-verdict.sh')}`);
327
+ execSync(`chmod +x ${scripts.join(' ')}`);
248
328
  } catch {}
249
329
  }
250
330
 
@@ -332,9 +412,15 @@ function printNextSteps(profile, projectDir, ci = 'github', specVerify = false)
332
412
 
333
413
  if (specVerify) {
334
414
  lines.push(` ${pc.bold('AI Spec Verifier:')}`);
335
- lines.push(` - include ${pc.cyan(".gitlab/spec-verify.yml")} from your .gitlab-ci.yml`);
336
- lines.push(` - add CI/CD variables: ${pc.cyan('AMP_API_KEY')}, ${pc.cyan('GITLAB_VERIFIER_TOKEN')} (masked)`);
337
- lines.push(` - BLOCKED verdict fails the MR pipeline (uncomment allow_failure for warning-only rollout)`);
415
+ if (ci === 'gitlab') {
416
+ lines.push(` - include ${pc.cyan(".gitlab/spec-verify.yml")} from your .gitlab-ci.yml`);
417
+ lines.push(` - add CI/CD variables: ${pc.cyan('AMP_API_KEY')}, ${pc.cyan('GITLAB_VERIFIER_TOKEN')} (masked)`);
418
+ lines.push(` - BLOCKED verdict fails the MR pipeline (uncomment allow_failure for warning-only rollout)`);
419
+ } else if (ci === 'github') {
420
+ lines.push(` - workflow ${pc.cyan('.github/workflows/spec-verify.yml')} runs automatically on pull_request`);
421
+ lines.push(` - add repo secret: ${pc.cyan('AMP_API_KEY')} (Settings → Secrets and variables → Actions)`);
422
+ lines.push(` - BLOCKED verdict fails the PR pipeline (remove continue-on-error for warning-only rollout)`);
423
+ }
338
424
  }
339
425
 
340
426
  console.log('\n' + lines.join('\n') + '\n');
@@ -368,7 +454,7 @@ program
368
454
  .option('--name <name>', 'Project name (defaults to directory name)')
369
455
  .option('--force', 'Overwrite existing files', false)
370
456
  .option('--ci <provider>', 'CI provider: gitlab | github | none', 'github')
371
- .option('--spec-verify', 'Install AI Spec Verifier blocking gate (GitLab only)', false)
457
+ .option('--spec-verify', 'Install AI Spec Verifier blocking gate (GitLab or GitHub)', false)
372
458
  .action((opts) => {
373
459
  const projectDir = process.cwd();
374
460
  const projectName = opts.name || basename(projectDir);
@@ -408,13 +494,13 @@ program
408
494
  injectVerifyScripts(projectDir, { pm });
409
495
  }
410
496
 
411
- const specVerify = Boolean(opts.specVerify) && ci === 'gitlab';
412
- if (opts.specVerify && ci !== 'gitlab') {
413
- log.warn('--spec-verify requires --ci gitlab — skipping AI Spec Verifier install');
497
+ const specVerify = Boolean(opts.specVerify) && (ci === 'gitlab' || ci === 'github');
498
+ if (opts.specVerify && !specVerify) {
499
+ log.warn('--spec-verify requires --ci gitlab or --ci github — skipping AI Spec Verifier install');
414
500
  }
415
501
  if (specVerify) {
416
502
  log.title('Installing AI Spec Verifier (opt-in)');
417
- installSpecVerify(projectDir, templateDir, opts.force);
503
+ installSpecVerify(projectDir, templateDir, opts.force, ci);
418
504
  }
419
505
 
420
506
  log.title('Installing root files');
@@ -506,8 +592,8 @@ program
506
592
 
507
593
  if (syncCursor) {
508
594
  log.info('Syncing .agents/ → .cursor/');
509
- copyDir(join(projectDir, '.agents', 'skills'), join(projectDir, '.cursor', 'skills'), { overwrite: true });
510
- copyDir(join(projectDir, '.agents', 'rules'), join(projectDir, '.cursor', 'rules'), { overwrite: true });
595
+ copyDir(join(projectDir, '.agents', 'skills'), join(projectDir, '.cursor', 'skills'), { overwrite: true, delete: true });
596
+ copyDir(join(projectDir, '.agents', 'rules'), join(projectDir, '.cursor', 'rules'), { overwrite: true, delete: true });
511
597
 
512
598
  const mcpExample = join(projectDir, '.agents', 'mcp.json.example');
513
599
  const mcpDest = join(projectDir, '.mcp.json');
@@ -519,7 +605,7 @@ program
519
605
 
520
606
  if (syncClaude) {
521
607
  log.info('Syncing .agents/ → .claude/');
522
- copyDir(join(projectDir, '.agents', 'skills'), join(projectDir, '.claude', 'skills'), { overwrite: true });
608
+ copyDir(join(projectDir, '.agents', 'skills'), join(projectDir, '.claude', 'skills'), { overwrite: true, delete: true });
523
609
 
524
610
  const claudeMd = join(projectDir, 'CLAUDE.md');
525
611
  const claudeDir = join(projectDir, '.claude');
@@ -540,4 +626,98 @@ program
540
626
  log.warn('.cursor/, .claude/, .amp/ are local only — not committed to git');
541
627
  });
542
628
 
629
+ program
630
+ .command('status')
631
+ .description('Show status of active OpenSpec changes (tasks progress, review verdict, archive readiness)')
632
+ .action(() => {
633
+ const projectDir = process.cwd();
634
+ log.title('agent-orchestrator status');
635
+
636
+ const changes = listActiveChanges(projectDir);
637
+ if (changes.length === 0) {
638
+ log.info('No active changes');
639
+ return;
640
+ }
641
+
642
+ for (const name of changes) {
643
+ const changeDir = join(projectDir, 'openspec', 'changes', name);
644
+ const progress = parseTasksProgress(changeDir);
645
+ const verdict = parseReviewVerdict(changeDir);
646
+ const progressStr = progress ? `${progress.done}/${progress.total} tasks` : 'no tasks.md';
647
+ const verdictStr = verdict || 'none';
648
+ const readyToArchive = Boolean(progress && progress.total > 0 && progress.done === progress.total);
649
+
650
+ console.log(`\n${pc.bold(name)}`);
651
+ console.log(` tasks: ${progressStr}`);
652
+ console.log(` review: ${verdictStr}`);
653
+ if (readyToArchive) log.ok('ready to archive');
654
+ }
655
+ console.log('');
656
+ });
657
+
658
+ program
659
+ .command('gate-check [change-name]')
660
+ .description('Deterministically check the review gate before apply/merge (exit non-zero if unmet)')
661
+ .option('--src-glob <glob>', 'source path filter used to detect code changes', 'src/')
662
+ .option('--base <ref>', 'git ref to diff against', 'HEAD~1')
663
+ .action((changeName, opts) => {
664
+ const projectDir = process.cwd();
665
+ log.title('agent-orchestrator gate-check');
666
+
667
+ const config = readPipelineConfig(projectDir);
668
+ if (!config) {
669
+ log.info('.agents/orchestrator.yaml not found — nothing to gate');
670
+ return;
671
+ }
672
+
673
+ if (!config.requireSpecReview) {
674
+ log.ok('review not required (pipeline.require_spec_review: false)');
675
+ return;
676
+ }
677
+
678
+ const touchesSrc = gitDiffTouchesGlob(projectDir, opts.base, opts.srcGlob);
679
+ if (touchesSrc === false) {
680
+ log.ok(`no changes under ${opts.srcGlob} — nothing to gate`);
681
+ return;
682
+ }
683
+ if (touchesSrc === null) {
684
+ log.warn('could not compute git diff — skipping gate-check');
685
+ return;
686
+ }
687
+
688
+ const changes = listActiveChanges(projectDir);
689
+ if (config.maxActiveChanges && changes.length > config.maxActiveChanges) {
690
+ log.warn(`${changes.length} active changes exceed pipeline.max_active_changes (${config.maxActiveChanges})`);
691
+ }
692
+
693
+ let target = changeName;
694
+ if (!target) {
695
+ if (changes.length === 0) {
696
+ log.warn(`${opts.srcGlob} changed but no active OpenSpec change found — cannot verify review gate`);
697
+ return;
698
+ }
699
+ target = changes
700
+ .map((name) => ({ name, mtime: statSync(join(projectDir, 'openspec', 'changes', name)).mtimeMs }))
701
+ .sort((a, b) => b.mtime - a.mtime)[0].name;
702
+ log.info(`auto-selected change: ${target} (override: gate-check <name>)`);
703
+ }
704
+
705
+ const changeDir = join(projectDir, 'openspec', 'changes', target);
706
+ if (!existsSync(changeDir)) {
707
+ log.err(`change not found: ${target}`);
708
+ process.exitCode = 1;
709
+ return;
710
+ }
711
+
712
+ const verdict = parseReviewVerdict(changeDir);
713
+ if (verdict && /^APPROVE/i.test(verdict)) {
714
+ log.ok(`review gate passed — ${target}: APPROVE`);
715
+ return;
716
+ }
717
+
718
+ log.err(`review gate failed — change "${target}" has ${verdict ? `verdict "${verdict}"` : 'no review.md'}`);
719
+ log.err(`Run /opsx:review ${target} and get an explicit APPROVE before apply/merge.`);
720
+ process.exitCode = 1;
721
+ });
722
+
543
723
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-orchestrator-kit",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Universal AI agent orchestration kit for Cursor, Claude Code, and Amp Code — spec-driven pipeline with OpenSpec integration",
5
5
  "keywords": [
6
6
  "ai-agent",
@@ -74,6 +74,8 @@ Before apply, check `.agents/orchestrator.yaml`:
74
74
 
75
75
  If Request Changes — fix artifacts, re-run `/opsx:review`.
76
76
 
77
+ This is no longer only a chat convention: `agent-orchestrator-kit gate-check` runs in CI (both `agent-verify.yml` fragments) and fails the pipeline if `src/` changed without an approved `review.md` — a forgotten or skipped review is caught at merge time, not just at apply time.
78
+
77
79
  ### apply → verify
78
80
  Exit Implementer when:
79
81
  - All `tasks.md` boxes `[x]`
@@ -91,7 +93,7 @@ After PR merged + CI green:
91
93
 
92
94
  **Start of each session:**
93
95
  1. Announce role: "Starting Spec Reviewer session for change: <name>"
94
- 2. Check `openspec list` — confirm active change limit (`max_active_changes` in orchestrator.yaml)
96
+ 2. Run `agent-orchestrator-kit status` (or `openspec list`) — confirm active change limit (`max_active_changes` in orchestrator.yaml) and see task/review progress for every active change at a glance
95
97
  3. Read `orchestrator.yaml` for project config and review gate
96
98
 
97
99
  **During session:**
@@ -135,7 +137,8 @@ At start of new session: read relevant entities to restore context without re-ex
135
137
  - [ ] explicit **Approve** received before apply (when `require_spec_review: true`)
136
138
  - [ ] `review.md` with `Verdict: APPROVE` exists (when review required)
137
139
  - [ ] all tasks `[x]` + build OK before PR
138
- - [ ] `/opsx:archive` run after merge
140
+ - [ ] `agent-orchestrator-kit gate-check` passes locally before pushing (mirrors the CI gate)
141
+ - [ ] `/opsx:archive` run after merge — `agent-orchestrator-kit status` shows "ready to archive"
139
142
 
140
143
  ## Anti-patterns
141
144
 
@@ -10,6 +10,8 @@ jobs:
10
10
  runs-on: ubuntu-latest
11
11
  steps:
12
12
  - uses: actions/checkout@v4
13
+ with:
14
+ fetch-depth: 0
13
15
 
14
16
  - uses: actions/setup-node@v4
15
17
  with:
@@ -45,6 +47,9 @@ jobs:
45
47
  - name: OpenSpec validate
46
48
  run: npx openspec validate --all --strict
47
49
 
50
+ - name: Gate check (review gate)
51
+ run: npx agent-orchestrator-kit gate-check --base "${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || 'HEAD~1' }}"
52
+
48
53
  - name: Lint (npm)
49
54
  if: steps.pm.outputs.manager == 'npm'
50
55
  run: npm run lint --if-present
@@ -0,0 +1,82 @@
1
+ name: spec-verify
2
+
3
+ # ──────────────────────────────────────────────────────────────
4
+ # AI Spec Verifier — blocking gate on pull requests.
5
+ # Verifies that changed src/ code complies with openspec/specs/
6
+ # via Amp CLI, posts a PASS / BLOCKED comment to the PR, and
7
+ # fails the pipeline on a BLOCKED verdict.
8
+ #
9
+ # Installed by agent-orchestrator-kit (init --ci github --spec-verify).
10
+ #
11
+ # Required repo secret (Settings → Secrets and variables → Actions):
12
+ # AMP_API_KEY — Amp API key
13
+ # GITHUB_TOKEN is provided automatically by Actions.
14
+ #
15
+ # Phase 1 (warning-only rollout): uncomment `continue-on-error: true` on the
16
+ # "Evaluate verdict" step below, then remove it once the team trusts the
17
+ # verdicts (Phase 2, blocking by default).
18
+ # ──────────────────────────────────────────────────────────────
19
+
20
+ on:
21
+ pull_request:
22
+ paths:
23
+ - 'src/**'
24
+
25
+ permissions:
26
+ pull-requests: write
27
+ contents: read
28
+
29
+ jobs:
30
+ spec-verify:
31
+ runs-on: ubuntu-latest
32
+ steps:
33
+ - uses: actions/checkout@v4
34
+ with:
35
+ fetch-depth: 0
36
+
37
+ - uses: actions/setup-node@v4
38
+ with:
39
+ node-version: 20
40
+
41
+ - name: Install Amp CLI
42
+ run: npm install -g @sourcegraph/amp@latest
43
+
44
+ - name: Make scripts executable
45
+ run: chmod +x scripts/verify-specs.sh scripts/post-pr-verdict-github.sh
46
+
47
+ - name: Run spec verifier
48
+ env:
49
+ AMP_API_KEY: ${{ secrets.AMP_API_KEY }}
50
+ # verify-specs.sh is shared with the GitLab variant and reads this
51
+ # variable name as the diff base — reused as-is, not GitHub-specific.
52
+ CI_MERGE_REQUEST_DIFF_BASE_SHA: ${{ github.event.pull_request.base.sha }}
53
+ run: bash scripts/verify-specs.sh
54
+
55
+ - name: Post PR comment
56
+ if: always()
57
+ env:
58
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
59
+ run: bash scripts/post-pr-verdict-github.sh
60
+
61
+ - name: Upload verifier artifacts
62
+ if: always()
63
+ uses: actions/upload-artifact@v4
64
+ with:
65
+ name: spec-verify-artifacts
66
+ path: |
67
+ artifacts/verdict.json
68
+ artifacts/verifier-prompt.md
69
+ retention-days: 7
70
+ if-no-files-found: ignore
71
+
72
+ - name: Evaluate verdict
73
+ # Phase 1 (warning-only rollout): uncomment the next line to let the
74
+ # pipeline stay green while the team builds trust in verdicts.
75
+ # continue-on-error: true
76
+ run: |
77
+ PASS=$(python3 -c "import json; v=json.load(open('artifacts/verdict.json')); print(str(v.get('pass', True)).lower())")
78
+ if [ "$PASS" != "true" ]; then
79
+ echo "Spec verifier found errors. See PR comment for details."
80
+ exit 1
81
+ fi
82
+ echo "Spec verification passed."
@@ -1,5 +1,7 @@
1
1
  .agent-verify-base:
2
2
  image: node:20
3
+ variables:
4
+ GIT_DEPTH: 0
3
5
  before_script:
4
6
  - |
5
7
  if [ -f pnpm-lock.yaml ]; then
@@ -15,6 +17,7 @@
15
17
  fi
16
18
  script:
17
19
  - npx openspec validate --all --strict
20
+ - npx agent-orchestrator-kit gate-check --base "${CI_MERGE_REQUEST_DIFF_BASE_SHA:-HEAD~1}"
18
21
  - |
19
22
  run_pm_script() {
20
23
  local name=$1
@@ -26,7 +26,9 @@ Never mix phases in one chat — this is the single most important rule.
26
26
 
27
27
  Verifier runs on **GitHub Actions** (default) or **GitLab** via `prebuild` → `verify:openspec` when using `init --ci gitlab`. GitLab projects do not use `.github/workflows/`.
28
28
 
29
- With `init --ci gitlab --spec-verify`, an **AI Spec Verifier** also runs on MRs changing `src/`: an Amp agent checks the changed code against `openspec/specs/` and a **BLOCKED verdict fails the pipeline** (gate `spec-verify-blocking` in `.agents/orchestrator.yaml`).
29
+ With `init --ci gitlab --spec-verify` or `init --ci github --spec-verify`, an **AI Spec Verifier** also runs on MRs/PRs changing `src/`: an Amp agent checks the changed code against `openspec/specs/` and a **BLOCKED verdict fails the pipeline** (gate `spec-verify-blocking` in `.agents/orchestrator.yaml`).
30
+
31
+ Both CI fragments also run `agent-orchestrator-kit gate-check` — a deterministic check that fails the pipeline when `src/` changed but the active change has no `review.md` with `Verdict: APPROVE` (when `require_spec_review: true`). Run `agent-orchestrator-kit status` at the start of any session to see task progress, review verdict, and archive readiness for every active change without querying `openspec` per change.
30
32
 
31
33
  ## Hard Rules
32
34
 
@@ -42,9 +44,9 @@ With `init --ci gitlab --spec-verify`, an **AI Spec Verifier** also runs on MRs
42
44
  |------------|------|
43
45
  | explore → propose | Decision brief written; change name chosen |
44
46
  | propose → review | `openspec validate --strict` passes ✓ |
45
- | review → apply | Reviewer writes explicit **Approve** |
47
+ | review → apply | Reviewer writes explicit **Approve** — enforced in CI by `gate-check` |
46
48
  | apply → verify | All `tasks.md` checkboxes `[x]`; local build OK |
47
- | verify → archive | CI green; PR merged |
49
+ | verify → archive | CI green; PR merged — check `agent-orchestrator status` for "ready to archive" |
48
50
 
49
51
  ## Context to Pin per Role
50
52
 
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env bash
2
+ # ──────────────────────────────────────────────────────────────
3
+ # Posts spec verifier verdict as a GitHub PR comment via gh CLI.
4
+ #
5
+ # Installed by agent-orchestrator-kit (init --ci github --spec-verify).
6
+ #
7
+ # Usage: ./scripts/post-pr-verdict-github.sh
8
+ # Env: GH_TOKEN / GITHUB_TOKEN — provided by GitHub Actions
9
+ # GITHUB_EVENT_PATH, GITHUB_REPOSITORY, GITHUB_RUN_ID — Actions runtime
10
+ # PR_NUMBER — optional override, auto-detected from the pull_request event otherwise
11
+ #
12
+ # Security: GH_TOKEN/GITHUB_TOKEN is never logged or echoed.
13
+ # ──────────────────────────────────────────────────────────────
14
+ set -euo pipefail
15
+
16
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
17
+ VERDICT_FILE="$ROOT/artifacts/verdict.json"
18
+
19
+ if [ ! -f "$VERDICT_FILE" ]; then
20
+ echo "No verdict file found — skipping PR comment."
21
+ exit 0
22
+ fi
23
+
24
+ TOKEN="${GH_TOKEN:-${GITHUB_TOKEN:-}}"
25
+ if [ -z "$TOKEN" ]; then
26
+ echo "GH_TOKEN/GITHUB_TOKEN not set — skipping PR comment."
27
+ exit 0
28
+ fi
29
+
30
+ if ! command -v gh &>/dev/null; then
31
+ echo "gh CLI not found — skipping PR comment."
32
+ exit 0
33
+ fi
34
+
35
+ PR_NUMBER="${PR_NUMBER:-}"
36
+ if [ -z "$PR_NUMBER" ] && [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -f "$GITHUB_EVENT_PATH" ]; then
37
+ PR_NUMBER=$(python3 -c "
38
+ import json
39
+ try:
40
+ data = json.load(open('$GITHUB_EVENT_PATH'))
41
+ print(data.get('pull_request', {}).get('number', ''))
42
+ except Exception:
43
+ print('')
44
+ " 2>/dev/null || true)
45
+ fi
46
+
47
+ if [ -z "$PR_NUMBER" ]; then
48
+ echo "Not running in a pull_request context — skipping PR comment."
49
+ exit 0
50
+ fi
51
+
52
+ export GH_TOKEN="$TOKEN"
53
+
54
+ PASS=$(python3 -c "import json; v=json.load(open('$VERDICT_FILE')); print(str(v.get('pass',True)).lower())")
55
+ SCORE=$(python3 -c "import json; v=json.load(open('$VERDICT_FILE')); print(v.get('score',0))")
56
+ SUMMARY=$(python3 -c "import json; v=json.load(open('$VERDICT_FILE')); print(v.get('summary',''))")
57
+ SKIPPED=$(python3 -c "import json; v=json.load(open('$VERDICT_FILE')); print(str(v.get('skipped',False)).lower())")
58
+
59
+ if [ "$SKIPPED" = "true" ]; then
60
+ ICON="⏭️"
61
+ STATUS="SKIPPED"
62
+ elif [ "$PASS" = "true" ]; then
63
+ ICON="✅"
64
+ STATUS="PASS"
65
+ else
66
+ ICON="❌"
67
+ STATUS="BLOCKED"
68
+ fi
69
+
70
+ # Build findings table
71
+ FINDINGS_TABLE=$(python3 <<'PYEOF'
72
+ import json, sys
73
+
74
+ with open("artifacts/verdict.json") as f:
75
+ v = json.load(f)
76
+
77
+ findings = v.get("findings", [])
78
+ if not findings:
79
+ print("_No findings._")
80
+ sys.exit(0)
81
+
82
+ severity_icons = {"error": "🔴", "warning": "🟡", "info": "🔵"}
83
+
84
+ print("| | Severity | Spec | Requirement | Message | File |")
85
+ print("|---|----------|------|-------------|---------|------|")
86
+ for f in findings:
87
+ icon = severity_icons.get(f.get("severity", "info"), "⚪")
88
+ sev = f.get("severity", "—")
89
+ spec = f.get("spec", "—")
90
+ req = f.get("requirement", "—")
91
+ msg = f.get("message", "—")
92
+ file = f.get("file", "—")
93
+ line = f.get("line")
94
+ if line:
95
+ file = f"{file}:{line}"
96
+ print(f"| {icon} | {sev} | {spec} | {req} | {msg} | {file} |")
97
+ PYEOF
98
+ )
99
+
100
+ # Build comment body
101
+ COMMENT_BODY="## ${ICON} Spec Verifier — ${STATUS}
102
+
103
+ **Score:** ${SCORE}/100
104
+ **Verdict:** ${STATUS}
105
+
106
+ ### Summary
107
+ ${SUMMARY}
108
+
109
+ ### Findings
110
+ ${FINDINGS_TABLE}
111
+
112
+ ---
113
+ _AI Spec Verifier • agent-orchestrator-kit • Run: ${GITHUB_RUN_ID:-local}_"
114
+
115
+ TMP_BODY_FILE=$(mktemp)
116
+ trap 'rm -f "$TMP_BODY_FILE"' EXIT
117
+ echo "$COMMENT_BODY" > "$TMP_BODY_FILE"
118
+
119
+ GH_REPO_FLAG=()
120
+ if [ -n "${GITHUB_REPOSITORY:-}" ]; then
121
+ GH_REPO_FLAG=(--repo "$GITHUB_REPOSITORY")
122
+ fi
123
+
124
+ # Security: token is passed via GH_TOKEN env var, never logged
125
+ if gh pr comment "$PR_NUMBER" "${GH_REPO_FLAG[@]}" --body-file "$TMP_BODY_FILE" >/dev/null 2>&1; then
126
+ echo "PR comment posted successfully"
127
+ else
128
+ echo "Failed to post PR comment"
129
+ fi