agent-orchestrator-kit 0.1.4 → 0.1.6
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 +27 -0
- package/README.md +90 -4
- package/bin/agent-orchestrator.js +164 -13
- package/package.json +1 -1
- package/templates/.gitlab/agent-verify.yml +36 -0
- package/templates/.gitlab/spec-verify.yml +47 -0
- package/templates/.gitlab-ci.starter.yml.example +7 -0
- package/templates/AGENTS.md +4 -0
- package/templates/scripts/post-mr-verdict.sh +116 -0
- package/templates/scripts/verify-specs.sh +196 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.1.6] - 2026-07-02
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **`init --spec-verify`** — opt-in AI Spec Verifier for GitLab consumers: on MRs changing `src/`, an Amp agent verifies code against `openspec/specs/`, posts PASS/BLOCKED to the MR, and fails the pipeline on BLOCKED
|
|
9
|
+
- **Templates** — `.gitlab/spec-verify.yml` (blocking job, commented Phase 1 `allow_failure` fallback), `scripts/verify-specs.sh` (stack-agnostic prompt with project context from `openspec/config.yaml`, graceful skips, secret-safe), `scripts/post-mr-verdict.sh` (GitLab MR comment)
|
|
10
|
+
- **Orchestrator gate** — `spec-verify-blocking` auto-added to `roles.verifier.gates` (idempotent)
|
|
11
|
+
- **OpenSpec spec** — `spec-verify-consumer`
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- **`update`** refreshes spec-verify files via `KIT_OPTIN_PATHS` — only in projects that already installed them
|
|
15
|
+
- **README / AGENTS.md template** — AI Spec Verifier documented (install, CI variables, verdict schema, Phase 1 rollout)
|
|
16
|
+
|
|
17
|
+
## [0.1.5] - 2026-06-27
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- **`init --ci gitlab|github|none`** — CI provider flag (default: `github`)
|
|
21
|
+
- **GitLab verify** — `.gitlab/agent-verify.yml` fragment with multi-PM detect (npm/yarn/pnpm)
|
|
22
|
+
- **PM-aware prebuild hook** — `verify:openspec` + `prebuild` injection on `--ci gitlab` (zero DevOps config via `npm run build`)
|
|
23
|
+
- **Starter example** — `templates/.gitlab-ci.starter.yml.example` for early push before DevOps owns root CI
|
|
24
|
+
- **OpenSpec specs** — `gitlab-consumer-verify`, `kit-ci-verify` synced to `openspec/specs/`
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- **`update`** refreshes `.gitlab/agent-verify.yml` via `KIT_MANAGED_PATHS`
|
|
28
|
+
- **README + AGENTS.md** — GitLab verifier path documented (prebuild hook, not GitHub Actions)
|
|
29
|
+
- **`printNextSteps`** — GitLab hints for `--ci gitlab` users
|
|
30
|
+
|
|
5
31
|
## [0.1.4] - 2026-06-27
|
|
6
32
|
|
|
7
33
|
### Added
|
|
@@ -49,6 +75,7 @@ All notable changes to this project will be documented in this file.
|
|
|
49
75
|
### Added
|
|
50
76
|
- Initial release: 5-role orchestration pipeline, `/opsx:*` commands, IDE sync
|
|
51
77
|
|
|
78
|
+
[0.1.5]: https://github.com/makshc2/agent-orchestrator-kit/compare/v0.1.4...v0.1.5
|
|
52
79
|
[0.1.4]: https://github.com/makshc2/agent-orchestrator-kit/compare/v0.1.3...v0.1.4
|
|
53
80
|
[0.1.3]: https://github.com/makshc2/agent-orchestrator-kit/compare/v0.1.2...v0.1.3
|
|
54
81
|
[0.1.2]: https://github.com/makshc2/agent-orchestrator-kit/compare/v0.1.1...v0.1.2
|
package/README.md
CHANGED
|
@@ -64,6 +64,24 @@ npx agent-orchestrator-kit init \
|
|
|
64
64
|
--lang uk
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
For GitLab-hosted projects (verify via `prebuild` hook — no GitHub Actions):
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npx agent-orchestrator-kit init --ci gitlab
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
This installs `.gitlab/agent-verify.yml`, injects `verify:openspec` and PM-aware `prebuild` into `package.json`. When DevOps runs `npm run build` (or yarn/pnpm build), npm lifecycle runs `prebuild` first → `npx openspec validate --all --strict` executes automatically.
|
|
74
|
+
|
|
75
|
+
Optional dev-controlled CI before DevOps setup: copy `templates/.gitlab-ci.starter.yml.example` from the kit to `.gitlab-ci.yml` and adjust stages as needed.
|
|
76
|
+
|
|
77
|
+
Skip CI files entirely:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npx agent-orchestrator-kit init --ci none
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Default remains GitHub Actions (`--ci github`).
|
|
84
|
+
|
|
67
85
|
### Sync to local IDEs
|
|
68
86
|
|
|
69
87
|
After init (and after every update):
|
|
@@ -80,7 +98,9 @@ This copies `.agents/` to your local IDE directories (not committed to git).
|
|
|
80
98
|
your-project/
|
|
81
99
|
├── AGENTS.md
|
|
82
100
|
├── CLAUDE.md
|
|
83
|
-
├── .github/workflows/agent-verify.yml # CI
|
|
101
|
+
├── .github/workflows/agent-verify.yml # CI (default --ci github)
|
|
102
|
+
├── .gitlab/agent-verify.yml # CI fragment (--ci gitlab)
|
|
103
|
+
├── .gitlab/spec-verify.yml # AI Spec Verifier (--spec-verify, opt-in)
|
|
84
104
|
├── .agents/
|
|
85
105
|
│ ├── orchestrator.yaml
|
|
86
106
|
│ ├── mcp.json.example # Cursor MCP template
|
|
@@ -96,7 +116,8 @@ your-project/
|
|
|
96
116
|
│ ├── openspec-archive-change/
|
|
97
117
|
│ ├── openspec-sync-specs/
|
|
98
118
|
│ └── spec-workflow-openspec/
|
|
99
|
-
|
|
119
|
+
├── scripts/sync-local-agent-skills.sh
|
|
120
|
+
└── scripts/verify-specs.sh + post-mr-verdict.sh # (--spec-verify, opt-in)
|
|
100
121
|
```
|
|
101
122
|
|
|
102
123
|
### Included in kit
|
|
@@ -106,7 +127,8 @@ your-project/
|
|
|
106
127
|
| Orchestration | 5-role pipeline, `AGENTS.md`, `orchestrator.yaml`, review command |
|
|
107
128
|
| OpenSpec skills | All 7 skills for `/opsx:*` workflow |
|
|
108
129
|
| IDE sync | Cursor + Claude Code sync script |
|
|
109
|
-
| CI | `agent-verify.yml` (
|
|
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`) |
|
|
110
132
|
| MCP templates | Memory MCP for Cursor and Amp |
|
|
111
133
|
|
|
112
134
|
### Not included (install separately)
|
|
@@ -285,7 +307,7 @@ npm run lint # must pass
|
|
|
285
307
|
|
|
286
308
|
### Role 5: Verifier — CI (automatic)
|
|
287
309
|
|
|
288
|
-
|
|
310
|
+
**GitHub (default `--ci github`):** installed at `.github/workflows/agent-verify.yml`:
|
|
289
311
|
|
|
290
312
|
```yaml
|
|
291
313
|
- run: npx openspec validate --all --strict
|
|
@@ -294,8 +316,54 @@ Installed at `.github/workflows/agent-verify.yml`:
|
|
|
294
316
|
- run: npm test --if-present
|
|
295
317
|
```
|
|
296
318
|
|
|
319
|
+
**GitLab (`--ci gitlab`):** verify runs through the package manager build lifecycle — no GitHub Actions:
|
|
320
|
+
|
|
321
|
+
```json
|
|
322
|
+
"verify:openspec": "npx openspec validate --all --strict",
|
|
323
|
+
"prebuild": "npm run verify:openspec"
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
When CI or a developer runs `npm run build`, npm executes `prebuild` first. DevOps pipelines that already call `npm run build` get OpenSpec validate with zero config changes.
|
|
327
|
+
|
|
328
|
+
Optional: include `.gitlab/agent-verify.yml` in `.gitlab-ci.yml` for full lint/build/test verify before DevOps owns the root CI file. See kit `templates/.gitlab-ci.starter.yml.example`.
|
|
329
|
+
|
|
297
330
|
Blocks merge if any gate fails.
|
|
298
331
|
|
|
332
|
+
#### AI Spec Verifier (GitLab, opt-in)
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
npx agent-orchestrator-kit init --ci gitlab --spec-verify
|
|
336
|
+
```
|
|
337
|
+
|
|
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.
|
|
339
|
+
|
|
340
|
+
Installed files:
|
|
341
|
+
|
|
342
|
+
| File | Purpose |
|
|
343
|
+
|------|---------|
|
|
344
|
+
| `.gitlab/spec-verify.yml` | CI fragment — hidden `.spec-verify-base` + blocking `spec-verify` job (MR + `src/**/*` only) |
|
|
345
|
+
| `scripts/verify-specs.sh` | Collects changed files + specs, builds prompt (project context from `openspec/config.yaml`), calls `amp -x`, writes `artifacts/verdict.json` |
|
|
346
|
+
| `scripts/post-mr-verdict.sh` | Posts the verdict as an MR comment via GitLab API |
|
|
347
|
+
|
|
348
|
+
The flag also adds `spec-verify-blocking` to `roles.verifier.gates` in `.agents/orchestrator.yaml`.
|
|
349
|
+
|
|
350
|
+
Setup after install:
|
|
351
|
+
|
|
352
|
+
1. Include the fragment from `.gitlab-ci.yml`:
|
|
353
|
+
|
|
354
|
+
```yaml
|
|
355
|
+
include:
|
|
356
|
+
- local: '.gitlab/spec-verify.yml'
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
2. Add CI/CD variables (Settings → CI/CD → Variables, masked): `AMP_API_KEY`, `GITLAB_VERIFIER_TOKEN` (project access token with `api` scope).
|
|
360
|
+
|
|
361
|
+
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
|
+
|
|
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).
|
|
364
|
+
|
|
365
|
+
`update` refreshes the three spec-verify files only in projects that already installed them — the feature stays opt-in.
|
|
366
|
+
|
|
299
367
|
---
|
|
300
368
|
|
|
301
369
|
### Archive — `/opsx:archive`
|
|
@@ -429,6 +497,8 @@ npx agent-orchestrator-kit init [options]
|
|
|
429
497
|
--profile <name> Stack profile: generic | vue3 | node | mvp
|
|
430
498
|
--lang <code> Agent language: en | uk | ...
|
|
431
499
|
--name <name> Project name (default: directory name)
|
|
500
|
+
--ci <provider> CI provider: gitlab | github | none (default: github)
|
|
501
|
+
--spec-verify Install AI Spec Verifier blocking gate (GitLab only)
|
|
432
502
|
--force Overwrite existing files
|
|
433
503
|
|
|
434
504
|
npx agent-orchestrator-kit update
|
|
@@ -470,6 +540,22 @@ openspec/ # Committed — spec-driven workflow
|
|
|
470
540
|
|
|
471
541
|
## Changelog
|
|
472
542
|
|
|
543
|
+
### 0.1.6
|
|
544
|
+
- `init --ci gitlab --spec-verify` — opt-in AI Spec Verifier: blocking MR gate via Amp CLI
|
|
545
|
+
- Templates: `.gitlab/spec-verify.yml`, `scripts/verify-specs.sh`, `scripts/post-mr-verdict.sh`
|
|
546
|
+
- `spec-verify-blocking` gate auto-added to `roles.verifier.gates`
|
|
547
|
+
- `update` refreshes spec-verify files only where already installed
|
|
548
|
+
|
|
549
|
+
### 0.1.5
|
|
550
|
+
- `init --ci gitlab|github|none` — GitLab verify via prebuild hook + CI fragment
|
|
551
|
+
- PM-aware `verify:openspec` / `prebuild` injection for GitLab projects
|
|
552
|
+
- `.gitlab/agent-verify.yml` template + starter example
|
|
553
|
+
- `update` refreshes GitLab fragment; docs for GitLab verifier path
|
|
554
|
+
|
|
555
|
+
### 0.1.4
|
|
556
|
+
- Kit repo CI — `.github/workflows/agent-verify.yml`
|
|
557
|
+
- OpenSpec devDependency for local and CI validation
|
|
558
|
+
|
|
473
559
|
### 0.1.3
|
|
474
560
|
- Fix gitignore dedup (exact line match, not substring)
|
|
475
561
|
- Add `.claude` to gitignore on init
|
|
@@ -28,9 +28,20 @@ const KIT_MANAGED_PATHS = [
|
|
|
28
28
|
'.agents/rules',
|
|
29
29
|
...KIT_SKILL_DIRS.map((s) => `.agents/skills/${s}`),
|
|
30
30
|
'.github/workflows/agent-verify.yml',
|
|
31
|
+
'.gitlab/agent-verify.yml',
|
|
31
32
|
'scripts/sync-local-agent-skills.sh',
|
|
32
33
|
];
|
|
33
34
|
|
|
35
|
+
// Opt-in files: refreshed by `update` only when already present in the project
|
|
36
|
+
const KIT_OPTIN_PATHS = [
|
|
37
|
+
'.gitlab/spec-verify.yml',
|
|
38
|
+
'scripts/verify-specs.sh',
|
|
39
|
+
'scripts/post-mr-verdict.sh',
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const VALID_CI_PROVIDERS = ['gitlab', 'github', 'none'];
|
|
43
|
+
const VERIFY_OPENSPEC_SCRIPT = 'npx openspec validate --all --strict';
|
|
44
|
+
|
|
34
45
|
const GITIGNORE_LINES = ['.cursor', '.cursor/memory.json', '.amp/settings.json', '.claude'];
|
|
35
46
|
|
|
36
47
|
const log = {
|
|
@@ -146,6 +157,114 @@ function installOpenspecConfigExample(projectDir, profile, vars, force) {
|
|
|
146
157
|
}
|
|
147
158
|
}
|
|
148
159
|
|
|
160
|
+
function resolveCiProvider(ci) {
|
|
161
|
+
if (VALID_CI_PROVIDERS.includes(ci)) return ci;
|
|
162
|
+
log.warn(`Unknown --ci value "${ci}". Valid: ${VALID_CI_PROVIDERS.join(', ')}. Using github.`);
|
|
163
|
+
return 'github';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function injectVerifyScripts(projectDir, { pm }) {
|
|
167
|
+
const pkgPath = join(projectDir, 'package.json');
|
|
168
|
+
if (!existsSync(pkgPath)) {
|
|
169
|
+
log.warn('skip script injection: no package.json');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
174
|
+
if (!pkg.scripts) pkg.scripts = {};
|
|
175
|
+
|
|
176
|
+
if (pkg.scripts['verify:openspec']) {
|
|
177
|
+
log.warn('skip (exists): verify:openspec script');
|
|
178
|
+
} else {
|
|
179
|
+
pkg.scripts['verify:openspec'] = VERIFY_OPENSPEC_SCRIPT;
|
|
180
|
+
log.ok('verify:openspec script added');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const runCmd = `${pm} run verify:openspec`;
|
|
184
|
+
const existingPrebuild = pkg.scripts.prebuild;
|
|
185
|
+
|
|
186
|
+
if (existingPrebuild && existingPrebuild.includes('verify:openspec')) {
|
|
187
|
+
log.warn('skip (exists): prebuild already chains verify:openspec');
|
|
188
|
+
} else if (existingPrebuild) {
|
|
189
|
+
pkg.scripts.prebuild = `${runCmd} && ${existingPrebuild}`;
|
|
190
|
+
log.ok('prebuild script chained');
|
|
191
|
+
} else {
|
|
192
|
+
pkg.scripts.prebuild = runCmd;
|
|
193
|
+
log.ok('prebuild script added');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function installCi(projectDir, templateDir, ci, force) {
|
|
200
|
+
if (ci === 'none') {
|
|
201
|
+
log.info('CI install skipped (--ci none)');
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (ci === 'github') {
|
|
206
|
+
const githubWorkflow = join(templateDir, '.github', 'workflows', 'agent-verify.yml');
|
|
207
|
+
const githubDest = join(projectDir, '.github', 'workflows', 'agent-verify.yml');
|
|
208
|
+
if (!existsSync(githubWorkflow)) return;
|
|
209
|
+
if (!force && existsSync(githubDest)) {
|
|
210
|
+
log.warn('skip (exists): .github/workflows/agent-verify.yml');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
mkdirSync(dirname(githubDest), { recursive: true });
|
|
214
|
+
copyFileSync(githubWorkflow, githubDest);
|
|
215
|
+
log.ok('.github/workflows/agent-verify.yml');
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (ci === 'gitlab') {
|
|
220
|
+
const gitlabFragment = join(templateDir, '.gitlab', 'agent-verify.yml');
|
|
221
|
+
const gitlabDest = join(projectDir, '.gitlab', 'agent-verify.yml');
|
|
222
|
+
if (!existsSync(gitlabFragment)) return;
|
|
223
|
+
if (!force && existsSync(gitlabDest)) {
|
|
224
|
+
log.warn('skip (exists): .gitlab/agent-verify.yml');
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
mkdirSync(dirname(gitlabDest), { recursive: true });
|
|
228
|
+
copyFileSync(gitlabFragment, gitlabDest);
|
|
229
|
+
log.ok('.gitlab/agent-verify.yml');
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function installSpecVerify(projectDir, templateDir, force) {
|
|
234
|
+
for (const rel of KIT_OPTIN_PATHS) {
|
|
235
|
+
const src = join(templateDir, rel);
|
|
236
|
+
const dest = join(projectDir, rel);
|
|
237
|
+
if (!existsSync(src)) continue;
|
|
238
|
+
if (!force && existsSync(dest)) {
|
|
239
|
+
log.warn(`skip (exists): ${rel}`);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
243
|
+
copyFileSync(src, dest);
|
|
244
|
+
log.ok(rel);
|
|
245
|
+
}
|
|
246
|
+
try {
|
|
247
|
+
execSync(`chmod +x ${join(projectDir, 'scripts', 'verify-specs.sh')} ${join(projectDir, 'scripts', 'post-mr-verdict.sh')}`);
|
|
248
|
+
} catch {}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function patchOrchestratorSpecVerify(projectDir) {
|
|
252
|
+
const orchPath = join(projectDir, '.agents', 'orchestrator.yaml');
|
|
253
|
+
if (!existsSync(orchPath)) return;
|
|
254
|
+
|
|
255
|
+
let content = readFileSync(orchPath, 'utf-8');
|
|
256
|
+
if (content.includes('spec-verify-blocking')) return;
|
|
257
|
+
|
|
258
|
+
const anchor = /^(\s*)- openspec-validate-strict\s*$/m;
|
|
259
|
+
if (!anchor.test(content)) {
|
|
260
|
+
log.warn('could not add spec-verify-blocking gate: openspec-validate-strict anchor not found in orchestrator.yaml');
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
content = content.replace(anchor, '$1- openspec-validate-strict\n$1- spec-verify-blocking');
|
|
264
|
+
writeFileSync(orchPath, content);
|
|
265
|
+
log.ok('spec-verify-blocking gate added to orchestrator.yaml');
|
|
266
|
+
}
|
|
267
|
+
|
|
149
268
|
function patchOrchestratorVerifier(projectDir, pm) {
|
|
150
269
|
const orchPath = join(projectDir, '.agents', 'orchestrator.yaml');
|
|
151
270
|
if (!existsSync(orchPath)) return;
|
|
@@ -168,7 +287,7 @@ function patchOrchestratorVerifier(projectDir, pm) {
|
|
|
168
287
|
writeFileSync(orchPath, content);
|
|
169
288
|
}
|
|
170
289
|
|
|
171
|
-
function printNextSteps(profile, projectDir) {
|
|
290
|
+
function printNextSteps(profile, projectDir, ci = 'github', specVerify = false) {
|
|
172
291
|
const pm = detectPackageManager(projectDir);
|
|
173
292
|
const openspecReady = hasOpenSpec(projectDir);
|
|
174
293
|
const lines = [`${pc.bold('Next steps:')}`];
|
|
@@ -206,6 +325,18 @@ function printNextSteps(profile, projectDir) {
|
|
|
206
325
|
lines.push(` ${pc.dim(`Detected package manager: ${pm} (verifier commands updated in orchestrator.yaml)`)}`);
|
|
207
326
|
}
|
|
208
327
|
|
|
328
|
+
if (ci === 'gitlab') {
|
|
329
|
+
lines.push(` ${pc.dim(`GitLab verify: ${pm} run build triggers prebuild → verify:openspec automatically`)}`);
|
|
330
|
+
lines.push(` ${pc.dim('Optional dev CI: include local .gitlab/agent-verify.yml (see kit templates/.gitlab-ci.starter.yml.example)')}`);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (specVerify) {
|
|
334
|
+
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)`);
|
|
338
|
+
}
|
|
339
|
+
|
|
209
340
|
console.log('\n' + lines.join('\n') + '\n');
|
|
210
341
|
}
|
|
211
342
|
|
|
@@ -236,17 +367,21 @@ program
|
|
|
236
367
|
.option('--lang <lang>', 'Agent response language (en | uk | ...)', 'en')
|
|
237
368
|
.option('--name <name>', 'Project name (defaults to directory name)')
|
|
238
369
|
.option('--force', 'Overwrite existing files', false)
|
|
370
|
+
.option('--ci <provider>', 'CI provider: gitlab | github | none', 'github')
|
|
371
|
+
.option('--spec-verify', 'Install AI Spec Verifier blocking gate (GitLab only)', false)
|
|
239
372
|
.action((opts) => {
|
|
240
373
|
const projectDir = process.cwd();
|
|
241
374
|
const projectName = opts.name || basename(projectDir);
|
|
242
375
|
const profile = resolveProfile(opts.profile);
|
|
243
376
|
const pm = detectPackageManager(projectDir);
|
|
377
|
+
const ci = resolveCiProvider(opts.ci);
|
|
244
378
|
|
|
245
379
|
log.title(`agent-orchestrator init v${KIT_VERSION}`);
|
|
246
380
|
log.info(`Project: ${projectName}`);
|
|
247
381
|
log.info(`Profile: ${profile}`);
|
|
248
382
|
log.info(`Language: ${opts.lang}`);
|
|
249
383
|
log.info(`Package manager: ${pm}`);
|
|
384
|
+
log.info(`CI provider: ${ci}`);
|
|
250
385
|
|
|
251
386
|
const templateDir = join(KIT_ROOT, 'templates');
|
|
252
387
|
const profileDir = join(KIT_ROOT, 'profiles', profile);
|
|
@@ -259,22 +394,27 @@ program
|
|
|
259
394
|
}
|
|
260
395
|
|
|
261
396
|
log.title('Installing scripts/');
|
|
262
|
-
copyDir(join(templateDir, 'scripts'), join(projectDir, 'scripts'), {
|
|
397
|
+
copyDir(join(templateDir, 'scripts'), join(projectDir, 'scripts'), {
|
|
398
|
+
overwrite: opts.force,
|
|
399
|
+
skip: ['verify-specs.sh', 'post-mr-verdict.sh'],
|
|
400
|
+
});
|
|
263
401
|
try {
|
|
264
402
|
execSync(`chmod +x ${join(projectDir, 'scripts', 'sync-local-agent-skills.sh')}`);
|
|
265
403
|
} catch {}
|
|
266
404
|
|
|
267
405
|
log.title('Installing CI workflow');
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
406
|
+
installCi(projectDir, templateDir, ci, opts.force);
|
|
407
|
+
if (ci === 'gitlab') {
|
|
408
|
+
injectVerifyScripts(projectDir, { pm });
|
|
409
|
+
}
|
|
410
|
+
|
|
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');
|
|
414
|
+
}
|
|
415
|
+
if (specVerify) {
|
|
416
|
+
log.title('Installing AI Spec Verifier (opt-in)');
|
|
417
|
+
installSpecVerify(projectDir, templateDir, opts.force);
|
|
278
418
|
}
|
|
279
419
|
|
|
280
420
|
log.title('Installing root files');
|
|
@@ -302,6 +442,9 @@ program
|
|
|
302
442
|
patchOrchestratorVerifier(projectDir, pm);
|
|
303
443
|
log.ok('.agents/orchestrator.yaml');
|
|
304
444
|
}
|
|
445
|
+
if (specVerify) {
|
|
446
|
+
patchOrchestratorSpecVerify(projectDir);
|
|
447
|
+
}
|
|
305
448
|
|
|
306
449
|
log.title('OpenSpec config template');
|
|
307
450
|
installOpenspecConfigExample(projectDir, profile, vars, opts.force);
|
|
@@ -311,7 +454,7 @@ program
|
|
|
311
454
|
|
|
312
455
|
log.title('Done');
|
|
313
456
|
log.ok(`agent-orchestrator-kit v${KIT_VERSION} installed`);
|
|
314
|
-
printNextSteps(profile, projectDir);
|
|
457
|
+
printNextSteps(profile, projectDir, ci, specVerify);
|
|
315
458
|
});
|
|
316
459
|
|
|
317
460
|
program
|
|
@@ -336,6 +479,14 @@ program
|
|
|
336
479
|
}
|
|
337
480
|
}
|
|
338
481
|
|
|
482
|
+
for (const rel of KIT_OPTIN_PATHS) {
|
|
483
|
+
const src = join(templateDir, rel);
|
|
484
|
+
const dest = join(projectDir, rel);
|
|
485
|
+
if (!existsSync(src) || !existsSync(dest)) continue;
|
|
486
|
+
copyFileSync(src, dest);
|
|
487
|
+
log.ok(`${rel} (opt-in)`);
|
|
488
|
+
}
|
|
489
|
+
|
|
339
490
|
log.ok(`Updated to v${KIT_VERSION}`);
|
|
340
491
|
log.info('Run ./scripts/sync-local-agent-skills.sh to sync to local IDE');
|
|
341
492
|
});
|
package/package.json
CHANGED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
.agent-verify-base:
|
|
2
|
+
image: node:20
|
|
3
|
+
before_script:
|
|
4
|
+
- |
|
|
5
|
+
if [ -f pnpm-lock.yaml ]; then
|
|
6
|
+
export PM=pnpm
|
|
7
|
+
corepack enable
|
|
8
|
+
pnpm install --frozen-lockfile
|
|
9
|
+
elif [ -f yarn.lock ]; then
|
|
10
|
+
export PM=yarn
|
|
11
|
+
yarn install --frozen-lockfile
|
|
12
|
+
else
|
|
13
|
+
export PM=npm
|
|
14
|
+
npm ci
|
|
15
|
+
fi
|
|
16
|
+
script:
|
|
17
|
+
- npx openspec validate --all --strict
|
|
18
|
+
- |
|
|
19
|
+
run_pm_script() {
|
|
20
|
+
local name=$1
|
|
21
|
+
if [ "$PM" = "npm" ]; then
|
|
22
|
+
npm run "$name" --if-present
|
|
23
|
+
elif node -e "process.exit(require('./package.json').scripts?.['${name}'] ? 0 : 1)"; then
|
|
24
|
+
$PM run "$name"
|
|
25
|
+
fi
|
|
26
|
+
}
|
|
27
|
+
run_pm_script lint
|
|
28
|
+
run_pm_script build
|
|
29
|
+
run_pm_script test
|
|
30
|
+
|
|
31
|
+
agent-verify:
|
|
32
|
+
extends: .agent-verify-base
|
|
33
|
+
rules:
|
|
34
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
35
|
+
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
|
36
|
+
- if: $CI_COMMIT_BRANCH == "develop"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# ──────────────────────────────────────────────────────────────
|
|
2
|
+
# AI Spec Verifier — blocking gate on merge requests.
|
|
3
|
+
# Verifies that changed src/ code complies with openspec/specs/
|
|
4
|
+
# via Amp CLI, posts a PASS / BLOCKED comment to the MR, and
|
|
5
|
+
# fails the pipeline on a BLOCKED verdict.
|
|
6
|
+
#
|
|
7
|
+
# Installed by agent-orchestrator-kit (init --ci gitlab --spec-verify).
|
|
8
|
+
# Include from .gitlab-ci.yml:
|
|
9
|
+
# include:
|
|
10
|
+
# - local: '.gitlab/spec-verify.yml'
|
|
11
|
+
#
|
|
12
|
+
# Required CI/CD variables (Settings → CI/CD → Variables):
|
|
13
|
+
# AMP_API_KEY — Amp API key (masked, protected)
|
|
14
|
+
# GITLAB_VERIFIER_TOKEN — Project token with api scope (masked)
|
|
15
|
+
# ──────────────────────────────────────────────────────────────
|
|
16
|
+
.spec-verify-base:
|
|
17
|
+
image: node:20
|
|
18
|
+
script:
|
|
19
|
+
- apt-get update -qq && apt-get install -y -qq python3 curl git > /dev/null
|
|
20
|
+
- npm install -g @sourcegraph/amp@latest
|
|
21
|
+
- chmod +x scripts/verify-specs.sh scripts/post-mr-verdict.sh
|
|
22
|
+
- bash scripts/verify-specs.sh
|
|
23
|
+
- bash scripts/post-mr-verdict.sh
|
|
24
|
+
# Evaluate verdict — exit 1 if verifier says fail
|
|
25
|
+
- |
|
|
26
|
+
PASS=$(python3 -c "import json; v=json.load(open('artifacts/verdict.json')); print(str(v.get('pass',True)).lower())")
|
|
27
|
+
if [ "$PASS" != "true" ]; then
|
|
28
|
+
echo "Spec verifier found errors. See MR comment for details."
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
|
31
|
+
echo "Spec verification passed."
|
|
32
|
+
artifacts:
|
|
33
|
+
paths:
|
|
34
|
+
- artifacts/verdict.json
|
|
35
|
+
- artifacts/verifier-prompt.md
|
|
36
|
+
expire_in: 7 days
|
|
37
|
+
when: always
|
|
38
|
+
|
|
39
|
+
spec-verify:
|
|
40
|
+
extends: .spec-verify-base
|
|
41
|
+
rules:
|
|
42
|
+
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
43
|
+
changes:
|
|
44
|
+
- "src/**/*"
|
|
45
|
+
# Phase 1 (warning-only rollout): uncomment the next line to let
|
|
46
|
+
# the pipeline stay green while the team builds trust in verdicts.
|
|
47
|
+
# allow_failure: true
|
package/templates/AGENTS.md
CHANGED
|
@@ -24,6 +24,10 @@ Never mix phases in one chat — this is the single most important rule.
|
|
|
24
24
|
| Implementer | `/opsx:apply <name>` | writes `src/` | strong |
|
|
25
25
|
| Verifier | CI (automatic) | scripts only | — |
|
|
26
26
|
|
|
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
|
+
|
|
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`).
|
|
30
|
+
|
|
27
31
|
## Hard Rules
|
|
28
32
|
|
|
29
33
|
- **One active change per developer** at a time.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ──────────────────────────────────────────────────────────────
|
|
3
|
+
# Posts spec verifier verdict as a GitLab MR comment.
|
|
4
|
+
#
|
|
5
|
+
# Installed by agent-orchestrator-kit (init --ci gitlab --spec-verify).
|
|
6
|
+
#
|
|
7
|
+
# Usage: ./scripts/post-mr-verdict.sh
|
|
8
|
+
# Env: CI_API_V4_URL, CI_PROJECT_ID, CI_MERGE_REQUEST_IID,
|
|
9
|
+
# GITLAB_VERIFIER_TOKEN (CI/CD variable, masked)
|
|
10
|
+
#
|
|
11
|
+
# Security: GITLAB_VERIFIER_TOKEN is a project access token with
|
|
12
|
+
# api scope. It 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 MR comment."
|
|
21
|
+
exit 0
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
if [ -z "${CI_API_V4_URL:-}" ] || [ -z "${CI_PROJECT_ID:-}" ] || [ -z "${CI_MERGE_REQUEST_IID:-}" ]; then
|
|
25
|
+
echo "Not running in MR context — skipping MR comment."
|
|
26
|
+
exit 0
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
if [ -z "${GITLAB_VERIFIER_TOKEN:-}" ]; then
|
|
30
|
+
echo "GITLAB_VERIFIER_TOKEN not set — skipping MR comment."
|
|
31
|
+
exit 0
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Parse verdict
|
|
35
|
+
PASS=$(python3 -c "import json,sys; v=json.load(open('$VERDICT_FILE')); print(str(v.get('pass',True)).lower())")
|
|
36
|
+
SCORE=$(python3 -c "import json,sys; v=json.load(open('$VERDICT_FILE')); print(v.get('score',0))")
|
|
37
|
+
SUMMARY=$(python3 -c "import json,sys; v=json.load(open('$VERDICT_FILE')); print(v.get('summary',''))")
|
|
38
|
+
SKIPPED=$(python3 -c "import json,sys; v=json.load(open('$VERDICT_FILE')); print(str(v.get('skipped',False)).lower())")
|
|
39
|
+
|
|
40
|
+
if [ "$SKIPPED" = "true" ]; then
|
|
41
|
+
ICON="⏭️"
|
|
42
|
+
STATUS="SKIPPED"
|
|
43
|
+
elif [ "$PASS" = "true" ]; then
|
|
44
|
+
ICON="✅"
|
|
45
|
+
STATUS="PASS"
|
|
46
|
+
else
|
|
47
|
+
ICON="❌"
|
|
48
|
+
STATUS="BLOCKED"
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
# Build findings table
|
|
52
|
+
FINDINGS_TABLE=$(python3 <<'PYEOF'
|
|
53
|
+
import json, sys
|
|
54
|
+
|
|
55
|
+
with open("artifacts/verdict.json") as f:
|
|
56
|
+
v = json.load(f)
|
|
57
|
+
|
|
58
|
+
findings = v.get("findings", [])
|
|
59
|
+
if not findings:
|
|
60
|
+
print("_No findings._")
|
|
61
|
+
sys.exit(0)
|
|
62
|
+
|
|
63
|
+
severity_icons = {"error": "🔴", "warning": "🟡", "info": "🔵"}
|
|
64
|
+
|
|
65
|
+
print("| | Severity | Spec | Requirement | Message | File |")
|
|
66
|
+
print("|---|----------|------|-------------|---------|------|")
|
|
67
|
+
for f in findings:
|
|
68
|
+
icon = severity_icons.get(f.get("severity", "info"), "⚪")
|
|
69
|
+
sev = f.get("severity", "—")
|
|
70
|
+
spec = f.get("spec", "—")
|
|
71
|
+
req = f.get("requirement", "—")
|
|
72
|
+
msg = f.get("message", "—")
|
|
73
|
+
file = f.get("file", "—")
|
|
74
|
+
line = f.get("line")
|
|
75
|
+
if line:
|
|
76
|
+
file = f"{file}:{line}"
|
|
77
|
+
print(f"| {icon} | {sev} | {spec} | {req} | {msg} | {file} |")
|
|
78
|
+
PYEOF
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Build comment body
|
|
82
|
+
COMMENT_BODY="## ${ICON} Spec Verifier — ${STATUS}
|
|
83
|
+
|
|
84
|
+
**Score:** ${SCORE}/100
|
|
85
|
+
**Verdict:** ${STATUS}
|
|
86
|
+
|
|
87
|
+
### Summary
|
|
88
|
+
${SUMMARY}
|
|
89
|
+
|
|
90
|
+
### Findings
|
|
91
|
+
${FINDINGS_TABLE}
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
_AI Spec Verifier • agent-orchestrator-kit • Pipeline: ${CI_PIPELINE_ID:-local}_"
|
|
95
|
+
|
|
96
|
+
# Escape for JSON
|
|
97
|
+
COMMENT_JSON=$(python3 -c "
|
|
98
|
+
import json, sys
|
|
99
|
+
body = sys.stdin.read()
|
|
100
|
+
print(json.dumps({'body': body}))
|
|
101
|
+
" <<< "$COMMENT_BODY")
|
|
102
|
+
|
|
103
|
+
# Post to GitLab API
|
|
104
|
+
# Security: token is passed via header, never logged
|
|
105
|
+
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
106
|
+
--request POST \
|
|
107
|
+
--header "PRIVATE-TOKEN: ${GITLAB_VERIFIER_TOKEN}" \
|
|
108
|
+
--header "Content-Type: application/json" \
|
|
109
|
+
--data "$COMMENT_JSON" \
|
|
110
|
+
"${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}/notes")
|
|
111
|
+
|
|
112
|
+
if [ "$HTTP_STATUS" -ge 200 ] && [ "$HTTP_STATUS" -lt 300 ]; then
|
|
113
|
+
echo "MR comment posted successfully (HTTP $HTTP_STATUS)"
|
|
114
|
+
else
|
|
115
|
+
echo "Failed to post MR comment (HTTP $HTTP_STATUS)"
|
|
116
|
+
fi
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ──────────────────────────────────────────────────────────────
|
|
3
|
+
# AI Spec Verifier — collects changed src/ files, concatenates
|
|
4
|
+
# openspec/specs/, builds a prompt, calls Amp CLI, and produces
|
|
5
|
+
# artifacts/verdict.json.
|
|
6
|
+
#
|
|
7
|
+
# Installed by agent-orchestrator-kit (init --ci gitlab --spec-verify).
|
|
8
|
+
#
|
|
9
|
+
# Usage: ./scripts/verify-specs.sh
|
|
10
|
+
# Env: CI_MERGE_REQUEST_DIFF_BASE_SHA (GitLab CI provides it)
|
|
11
|
+
# AMP_API_KEY — Amp API key (CI/CD variable, masked)
|
|
12
|
+
# SRC_GLOB — source path filter (default: src/)
|
|
13
|
+
#
|
|
14
|
+
# Security: this script NEVER logs tokens, keys, or .env content.
|
|
15
|
+
# ──────────────────────────────────────────────────────────────
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
19
|
+
ARTIFACTS_DIR="$ROOT/artifacts"
|
|
20
|
+
SPECS_DIR="$ROOT/openspec/specs"
|
|
21
|
+
PROJECT_CONFIG="$ROOT/openspec/config.yaml"
|
|
22
|
+
VERDICT_FILE="$ARTIFACTS_DIR/verdict.json"
|
|
23
|
+
PROMPT_FILE="$ARTIFACTS_DIR/verifier-prompt.md"
|
|
24
|
+
SRC_GLOB="${SRC_GLOB:-src/}"
|
|
25
|
+
|
|
26
|
+
mkdir -p "$ARTIFACTS_DIR"
|
|
27
|
+
|
|
28
|
+
write_skipped_verdict() {
|
|
29
|
+
local summary="$1"
|
|
30
|
+
cat > "$VERDICT_FILE" <<EOF
|
|
31
|
+
{
|
|
32
|
+
"pass": true,
|
|
33
|
+
"score": 100,
|
|
34
|
+
"skipped": true,
|
|
35
|
+
"summary": "${summary}",
|
|
36
|
+
"findings": []
|
|
37
|
+
}
|
|
38
|
+
EOF
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# ── 1. Collect changed source files ──────────────────────────
|
|
42
|
+
BASE_SHA="${CI_MERGE_REQUEST_DIFF_BASE_SHA:-HEAD~1}"
|
|
43
|
+
|
|
44
|
+
CHANGED_FILES=$(git diff --name-only "$BASE_SHA"...HEAD -- "$SRC_GLOB" || true)
|
|
45
|
+
|
|
46
|
+
if [ -z "$CHANGED_FILES" ]; then
|
|
47
|
+
echo "No ${SRC_GLOB} files changed — skipping spec verification."
|
|
48
|
+
write_skipped_verdict "No ${SRC_GLOB} files changed — verification skipped."
|
|
49
|
+
exit 0
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
echo "Changed files:"
|
|
53
|
+
echo "$CHANGED_FILES"
|
|
54
|
+
|
|
55
|
+
# ── 2. Collect all spec files ────────────────────────────────
|
|
56
|
+
SPEC_FILES=$(find "$SPECS_DIR" -name '*.md' -type f 2>/dev/null || true)
|
|
57
|
+
|
|
58
|
+
if [ -z "$SPEC_FILES" ]; then
|
|
59
|
+
echo "No spec files found in $SPECS_DIR — skipping."
|
|
60
|
+
write_skipped_verdict "No spec files found — verification skipped."
|
|
61
|
+
exit 0
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# ── 3. Build spec content block ──────────────────────────────
|
|
65
|
+
SPECS_CONTENT=""
|
|
66
|
+
for spec_file in $SPEC_FILES; do
|
|
67
|
+
rel_path="${spec_file#$ROOT/}"
|
|
68
|
+
SPECS_CONTENT+="
|
|
69
|
+
--- FILE: $rel_path ---
|
|
70
|
+
$(cat "$spec_file")
|
|
71
|
+
"
|
|
72
|
+
done
|
|
73
|
+
|
|
74
|
+
# ── 4. Build changed file content block ──────────────────────
|
|
75
|
+
# Security: skip .env, secrets, tokens, keys from content
|
|
76
|
+
CHANGED_CONTENT=""
|
|
77
|
+
for file in $CHANGED_FILES; do
|
|
78
|
+
full_path="$ROOT/$file"
|
|
79
|
+
if [ -f "$full_path" ]; then
|
|
80
|
+
case "$file" in
|
|
81
|
+
*.env*|*secret*|*token*|*key*|*.pem|*.p12) continue ;;
|
|
82
|
+
esac
|
|
83
|
+
CHANGED_CONTENT+="
|
|
84
|
+
--- FILE: $file ---
|
|
85
|
+
$(cat "$full_path")
|
|
86
|
+
"
|
|
87
|
+
fi
|
|
88
|
+
done
|
|
89
|
+
|
|
90
|
+
# ── 5. Project context from openspec/config.yaml ─────────────
|
|
91
|
+
PROJECT_CONTEXT=""
|
|
92
|
+
if [ -f "$PROJECT_CONFIG" ]; then
|
|
93
|
+
PROJECT_CONTEXT="
|
|
94
|
+
## Project Context (openspec/config.yaml)
|
|
95
|
+
|
|
96
|
+
$(cat "$PROJECT_CONFIG")
|
|
97
|
+
"
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# ── 6. Build verifier prompt ─────────────────────────────────
|
|
101
|
+
cat > "$PROMPT_FILE" <<PROMPT
|
|
102
|
+
You are a SPEC VERIFIER.
|
|
103
|
+
Your job: verify that changed source code complies with project specifications.
|
|
104
|
+
${PROJECT_CONTEXT}
|
|
105
|
+
## Instructions
|
|
106
|
+
1. Read ALL specs below carefully.
|
|
107
|
+
2. Read ALL changed source files below.
|
|
108
|
+
3. For each changed file, check if it relates to any spec requirement.
|
|
109
|
+
4. Verify that every relevant requirement/scenario from specs is satisfied.
|
|
110
|
+
5. Respect project conventions from the project context above, if provided.
|
|
111
|
+
|
|
112
|
+
## Output
|
|
113
|
+
Return ONLY valid JSON (no markdown fences, no extra text) with this structure:
|
|
114
|
+
{
|
|
115
|
+
"pass": true|false,
|
|
116
|
+
"score": 0-100,
|
|
117
|
+
"summary": "Brief summary",
|
|
118
|
+
"findings": [
|
|
119
|
+
{
|
|
120
|
+
"severity": "error"|"warning"|"info",
|
|
121
|
+
"spec": "spec file path",
|
|
122
|
+
"requirement": "requirement name from spec",
|
|
123
|
+
"message": "what is wrong or missing",
|
|
124
|
+
"file": "affected source file",
|
|
125
|
+
"line": null
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
Rules for verdict:
|
|
131
|
+
- "pass": false if ANY finding has severity "error"
|
|
132
|
+
- "pass": true if only "warning" or "info" findings, or no findings
|
|
133
|
+
- "score": 100 minus 10 per error, 3 per warning (minimum 0)
|
|
134
|
+
- If a changed file has no related spec, add an "info" finding noting it
|
|
135
|
+
|
|
136
|
+
## SPECIFICATIONS
|
|
137
|
+
|
|
138
|
+
${SPECS_CONTENT}
|
|
139
|
+
|
|
140
|
+
## CHANGED SOURCE FILES
|
|
141
|
+
|
|
142
|
+
${CHANGED_CONTENT}
|
|
143
|
+
PROMPT
|
|
144
|
+
|
|
145
|
+
echo "Verifier prompt built ($(wc -c < "$PROMPT_FILE") bytes)"
|
|
146
|
+
|
|
147
|
+
# ── 7. Call Amp CLI ──────────────────────────────────────────
|
|
148
|
+
if ! command -v amp &>/dev/null; then
|
|
149
|
+
echo "amp CLI not found — writing fallback verdict."
|
|
150
|
+
write_skipped_verdict "amp CLI not installed — verification skipped."
|
|
151
|
+
exit 0
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
if [ -z "${AMP_API_KEY:-}" ]; then
|
|
155
|
+
echo "AMP_API_KEY not set — skipping Amp call."
|
|
156
|
+
write_skipped_verdict "AMP_API_KEY not set in CI — Amp verification skipped."
|
|
157
|
+
exit 0
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
echo "Running Amp verifier agent..."
|
|
161
|
+
export AMP_API_KEY
|
|
162
|
+
AMP_RESPONSE=$(amp -x < "$PROMPT_FILE" 2>/dev/null || true)
|
|
163
|
+
|
|
164
|
+
# ── 8. Extract JSON from response ────────────────────────────
|
|
165
|
+
# The response might contain markdown fences — strip them
|
|
166
|
+
CLEAN_JSON=$(echo "$AMP_RESPONSE" | sed -n '/^{/,/^}/p' | head -200)
|
|
167
|
+
|
|
168
|
+
if echo "$CLEAN_JSON" | python3 -m json.tool > /dev/null 2>&1; then
|
|
169
|
+
echo "$CLEAN_JSON" > "$VERDICT_FILE"
|
|
170
|
+
else
|
|
171
|
+
echo "Failed to parse verifier response as JSON."
|
|
172
|
+
echo "Raw response (first 500 chars):"
|
|
173
|
+
echo "$AMP_RESPONSE" | head -c 500
|
|
174
|
+
# Write a cautious pass — don't block on verifier infrastructure failure
|
|
175
|
+
cat > "$VERDICT_FILE" <<'EOF'
|
|
176
|
+
{
|
|
177
|
+
"pass": true,
|
|
178
|
+
"score": 50,
|
|
179
|
+
"skipped": false,
|
|
180
|
+
"summary": "Verifier did not return valid JSON — result inconclusive.",
|
|
181
|
+
"findings": [
|
|
182
|
+
{
|
|
183
|
+
"severity": "warning",
|
|
184
|
+
"spec": "N/A",
|
|
185
|
+
"requirement": "N/A",
|
|
186
|
+
"message": "Verifier agent response was not valid JSON. Manual review recommended.",
|
|
187
|
+
"file": "N/A",
|
|
188
|
+
"line": null
|
|
189
|
+
}
|
|
190
|
+
]
|
|
191
|
+
}
|
|
192
|
+
EOF
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
echo "Verdict written to $VERDICT_FILE"
|
|
196
|
+
cat "$VERDICT_FILE" | python3 -m json.tool 2>/dev/null || cat "$VERDICT_FILE"
|