create-agentic-pdlc 2.4.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/.agentic-pdlc/hooks/pdlc-stage-gate.sh +37 -10
  2. package/.claude/settings.json +18 -0
  3. package/.coderabbit.yaml +41 -0
  4. package/.github/workflows/add-to-board.yml +55 -7
  5. package/.github/workflows/agent-trigger.yml +57 -25
  6. package/.github/workflows/board-reconciliation.yml +176 -0
  7. package/.github/workflows/pdlc-health-check.yml +81 -81
  8. package/.github/workflows/project-automation.yml +252 -259
  9. package/CLAUDE.md +1 -1
  10. package/README.md +33 -32
  11. package/adapters/claude-code/skill.md +12 -8
  12. package/bin/cli.js +607 -213
  13. package/docs/superpowers/plans/2026-06-04-spec-format-issue-template.md +160 -0
  14. package/docs/superpowers/plans/2026-06-04-two-tier-installer.md +1056 -0
  15. package/docs/superpowers/plans/2026-06-05-archive-card-on-issue-close.md +105 -0
  16. package/docs/superpowers/plans/2026-06-05-project-id-actions-variable.md +336 -0
  17. package/docs/superpowers/specs/2026-06-04-spec-format-issue-template-design.md +46 -0
  18. package/docs/superpowers/specs/2026-06-05-project-id-actions-variable-design.md +114 -0
  19. package/package.json +2 -2
  20. package/scripts/derive-column.js +20 -0
  21. package/templates/.github/workflows/add-to-board.yml +2 -2
  22. package/templates/.github/workflows/agent-trigger.yml +2 -2
  23. package/templates/.github/workflows/pdlc-health-check.yml +2 -2
  24. package/templates/.github/workflows/project-automation.yml +47 -8
  25. package/templates/full/CLAUDE.md +30 -0
  26. package/templates/lite/AGENTS.md +121 -0
  27. package/templates/lite/CLAUDE.md +44 -0
  28. package/tests/cli.test.js +118 -0
  29. package/.github/workflows/agentic-metrics.yml +0 -545
  30. package/.github/workflows/qa-agent.yml +0 -139
  31. package/.github/workflows/qa-gate.yml +0 -51
  32. /package/templates/{AGENTS.md → full/AGENTS.md} +0 -0
  33. /package/templates/{docs → full/docs}/pdlc.md +0 -0
@@ -6,7 +6,7 @@ on:
6
6
  - cron: '0 8 * * 1' # Every Monday at 8am
7
7
 
8
8
  env:
9
- PROJECT_ID: "{{PROJECT_ID}}"
9
+ PROJECT_ID: ${{ vars.PROJECT_ID }}
10
10
  STATUS_FIELD_ID: "{{STATUS_FIELD_ID}}"
11
11
  STATUS_BRAINSTORMING: "{{ID_BRAINSTORMING}}"
12
12
  STATUS_DETAILING: "{{ID_DETAILING}}"
@@ -26,7 +26,7 @@ jobs:
26
26
  issues: write
27
27
  steps:
28
28
  - name: Validate Board Configuration
29
- if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
29
+ if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '' }}
30
30
  uses: actions/github-script@v8
31
31
  with:
32
32
  github-token: ${{ env.PROJECT_TOKEN }}
@@ -9,7 +9,7 @@ on:
9
9
  types: [labeled, edited, closed]
10
10
 
11
11
  env:
12
- PROJECT_ID: "{{PROJECT_ID}}"
12
+ PROJECT_ID: ${{ vars.PROJECT_ID }}
13
13
  STATUS_FIELD_ID: "{{STATUS_FIELD_ID}}"
14
14
  STATUS_IDEA: "{{ID_IDEA}}"
15
15
  STATUS_BRAINSTORMING: "{{ID_BRAINSTORMING}}"
@@ -30,7 +30,7 @@ jobs:
30
30
  PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
31
31
  steps:
32
32
  - name: Detect Label and Move Issue
33
- if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
33
+ if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '' }}
34
34
  uses: actions/github-script@v8
35
35
  with:
36
36
  github-token: ${{ env.PROJECT_TOKEN }}
@@ -91,7 +91,7 @@ jobs:
91
91
  PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
92
92
  steps:
93
93
  - name: Check spec markers and swap labels
94
- if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
94
+ if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '' }}
95
95
  uses: actions/github-script@v8
96
96
  with:
97
97
  github-token: ${{ env.PROJECT_TOKEN }}
@@ -149,7 +149,7 @@ jobs:
149
149
  # runs-on: ubuntu-latest
150
150
  # steps:
151
151
  # - name: Move issue to Idea
152
- # if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
152
+ # if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '' }}
153
153
  # uses: actions/github-script@v8
154
154
  # with:
155
155
  # github-token: ${{ env.PROJECT_TOKEN }}
@@ -179,7 +179,7 @@ jobs:
179
179
  PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
180
180
  steps:
181
181
  - name: Move linked issue to Testing
182
- if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
182
+ if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '' }}
183
183
  uses: actions/github-script@v8
184
184
  with:
185
185
  github-token: ${{ env.PROJECT_TOKEN }}
@@ -233,7 +233,7 @@ jobs:
233
233
  PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
234
234
  steps:
235
235
  - name: Move linked issue to Code Review / PR
236
- if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
236
+ if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '' }}
237
237
  uses: actions/github-script@v8
238
238
  with:
239
239
  github-token: ${{ env.PROJECT_TOKEN }}
@@ -281,7 +281,7 @@ jobs:
281
281
  PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
282
282
  steps:
283
283
  - name: Swap PR labels
284
- if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
284
+ if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '' }}
285
285
  uses: actions/github-script@v8
286
286
  with:
287
287
  github-token: ${{ env.PROJECT_TOKEN }}
@@ -300,7 +300,7 @@ jobs:
300
300
  PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
301
301
  steps:
302
302
  - name: Move issue to Production
303
- if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
303
+ if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '' }}
304
304
  uses: actions/github-script@v8
305
305
  with:
306
306
  github-token: ${{ env.PROJECT_TOKEN }}
@@ -359,3 +359,42 @@ jobs:
359
359
  await github.rest.issues.removeLabel({ owner, repo, issue_number, name: label }).catch(() => {});
360
360
  }
361
361
  console.log(`Issue #${issue_number} labels cleaned up`);
362
+
363
+ move-card-on-issue-close:
364
+ name: Closed issue → Archive from board
365
+ if: github.event_name == 'issues' && github.event.action == 'closed'
366
+ runs-on: ubuntu-latest
367
+ env:
368
+ PROJECT_TOKEN: ${{ secrets.PROJECT_TOKEN }}
369
+ steps:
370
+ - name: Archive board card
371
+ if: ${{ env.PROJECT_TOKEN != '' && env.PROJECT_ID != '' }}
372
+ uses: actions/github-script@v8
373
+ with:
374
+ github-token: ${{ env.PROJECT_TOKEN }}
375
+ script: |
376
+ const nodeId = context.payload.issue.node_id;
377
+ let itemId;
378
+ try {
379
+ const added = await github.graphql(`
380
+ mutation($p: ID!, $c: ID!) {
381
+ addProjectV2ItemById(input: {projectId: $p, contentId: $c}) { item { id } }
382
+ }`, { p: process.env.PROJECT_ID, c: nodeId });
383
+ itemId = added.addProjectV2ItemById.item.id;
384
+ if (!itemId) {
385
+ console.log(`Could not extract itemId from add response`);
386
+ return;
387
+ }
388
+ } catch (e) {
389
+ console.log(`Could not add issue to project: ${e.message}`);
390
+ return;
391
+ }
392
+ try {
393
+ await github.graphql(`
394
+ mutation($p: ID!, $i: ID!) {
395
+ archiveProjectV2Item(input: {projectId: $p, itemId: $i}) { item { id } }
396
+ }`, { p: process.env.PROJECT_ID, i: itemId });
397
+ console.log(`Issue #${context.payload.issue.number} archived from board`);
398
+ } catch (e) {
399
+ console.log(`Could not archive item: ${e.message}`);
400
+ }
@@ -0,0 +1,30 @@
1
+ <!-- agentic-full -->
2
+ ## Multi-Agent Pipeline
3
+
4
+ This project uses automated agents beyond the spec gate:
5
+
6
+ | Agent | Role | Trigger |
7
+ |---|---|---|
8
+ | Implementation Agent (Jules or custom) | Implements spec after `spec:approved` | `agent-trigger.yml` |
9
+ | QA Agent | Verifies PR against ACs via GitHub Models | `project-automation.yml` Variant B |
10
+ | Sentinel | Architecture audit via Gemini Code Assist CI | `architecture-violation` label |
11
+
12
+ **QA Labels — automation-owned, never apply manually:**
13
+ - `qa:approved` — QA Agent passed; card moves to Code Review
14
+ - `qa:needs-work` — QA Agent found issues; PR flow halts
15
+ - `infra:qa-broken` — QA Agent error; requires manual review
16
+
17
+ **Board Automation:**
18
+ `project-automation.yml` moves cards between board columns based on labels and PR events.
19
+ Column Option IDs and Project IDs are documented in `docs/pdlc.md`.
20
+
21
+ Read `docs/pdlc.md` for the full board layout, column IDs, and label reference.
22
+
23
+ **Pipeline Updates:**
24
+ To add or configure optional agents (Jules, QA Agent, Sentinel):
25
+ ```bash
26
+ npx create-agentic-pdlc --update
27
+ ```
28
+
29
+ Run this when asked to "update the pipeline", "configure the agents", or "update the board".
30
+ It detects what is already configured and sets up what is missing — without touching user-owned files.
@@ -0,0 +1,121 @@
1
+ # {{PROJECT_NAME}} — AI Agent Instructions
2
+
3
+ This is the contract between the project and any external AI agent
4
+ (Claude Code, Cursor, Copilot, Codex, Sweep, etc.). Read this before committing any change.
5
+
6
+ ## Project Overview
7
+
8
+ {{PROJECT_DESCRIPTION}}
9
+
10
+ **Structure:**
11
+ {{PROJECT_STRUCTURE}}
12
+
13
+ ## Before Any Change
14
+
15
+ ```bash
16
+ git fetch origin && git checkout main && git pull
17
+ ```
18
+
19
+ Always start from the current `main` HEAD. Never work over stale snapshots.
20
+
21
+ ## Invariants / Non-negotiable business rules
22
+
23
+ {{INVARIANTS}}
24
+ <!-- Examples:
25
+ 1. **Minimum viable change** — implement exactly what the spec says; no future-proofing.
26
+ 2. **Human-in-the-Loop** — No external side-effect actions without explicit human approval.
27
+ 3. **Immutable Audit-Log** — INSERT only on audit_log; never UPDATE/DELETE.
28
+ -->
29
+
30
+ ## Mandatory Workflow
31
+
32
+ 0. **Identity**: Always prefix your GitHub comments with `🤖 **Agent:** ` to distinguish yourself.
33
+ 1. **Stage Check**: Run `gh issue view <N> --json labels,title` to determine current stage.
34
+ - `spec:approved` → begin implementation (gate already passed)
35
+ - `stage:approval` → spec written; wait for PM to add `spec:approved`
36
+ - Otherwise → follow the stage gate below
37
+ 2. Apply `stage:brainstorming` before reading any code: `gh issue edit <N> --add-label "stage:brainstorming"`
38
+ 3. Read the issue entirely — understand type and Acceptance Criteria.
39
+ 4. Read all files mentioned in the issue's technical context.
40
+ 5. Present problem summary + 2–3 solution options. **Stop and wait for PM choice.**
41
+ 6. Once PM selects an approach, write the complete spec into the issue body. Advance to `stage:approval`.
42
+ 7. Wait for PM to add `spec:approved`. Then implement the **minimum viable change** that satisfies the ACs.
43
+ 8. Run tests: `{{TEST_COMMAND}}`
44
+ 9. Create a Pull Request with `Closes #N` in the body.
45
+
46
+ ## Spec Format
47
+
48
+ **Destination: the issue body.** Write spec content using `gh issue edit <N> --body "..."` — not to a file.
49
+ Automation checks the issue body for `## Acceptance Criteria` and `## Files to Modify`.
50
+
51
+ ```
52
+ ## Problem
53
+ [1-3 sentences. What fails. Who affected. Measured impact.]
54
+
55
+ ## Sprint Goal / Success Metrics
56
+ | Metric | Baseline | Target | When |
57
+ |--------|----------|--------|------|
58
+
59
+ ## Solution
60
+ [Behavioral description of what is built. No implementation details.]
61
+
62
+ ## Acceptance Criteria
63
+ **AC1 — [name]**
64
+ - Given [precondition]
65
+ - When [action]
66
+ - Then [outcome]
67
+
68
+ ## Edge Cases
69
+ - EC1: [condition] → [expected behavior]
70
+
71
+ ## Out of Scope
72
+ - [item] — reason
73
+
74
+ ## Non-Functional Requirements
75
+ - Performance: [metric with number]
76
+ - Security: [constraint]
77
+ - Reliability: [constraint]
78
+ > For pure docs/markdown issues with zero runtime behavior, include the NFRs section and state "N/A".
79
+
80
+ ## Files to Modify
81
+ - `path/to/file` — what changes
82
+ ```
83
+
84
+ ## Stage Gate Labels
85
+
86
+ | Label | Who sets it | Meaning |
87
+ |---|---|---|
88
+ | `stage:brainstorming` | Agent | Brainstorming started — first action before reading code |
89
+ | `stage:detailing` | Agent | Spec is being written |
90
+ | `stage:approval` | Agent | Spec complete, awaiting PM review |
91
+ | `spec:approved` | **PM only** | Gate cleared — implement |
92
+
93
+ **NEVER apply `spec:approved`, `stage:development`, or `qa:*`.** These are owned by the PM and automation.
94
+
95
+ ## Stage Transition Rules (non-negotiable)
96
+
97
+ MUST apply `stage:brainstorming` immediately on starting work — before reading any code,
98
+ before invoking any skill.
99
+
100
+ MUST NOT add `stage:detailing` until the user has explicitly selected an approach
101
+ in the current conversation turn.
102
+
103
+ MUST NOT add `spec:approved` or any approval-trigger label under any circumstances.
104
+
105
+ Each stage transition requires a fresh explicit signal from the user in the same session.
106
+
107
+ ## What NOT to Do
108
+
109
+ - Never commit directly to `main`.
110
+ - Never open a PR without `spec:approved` on the linked issue.
111
+ - Never implement beyond the immediate scope of the issue.
112
+ - Never create future-proofing abstractions for hypothetical features.
113
+ {{EXTRA_DONT}}
114
+
115
+ ## Project Standards
116
+
117
+ - **Tests:** `{{TEST_COMMAND}}`
118
+ - **Lint/Types:** `{{LINT_COMMAND}}`
119
+ - **Typecheck:** `{{TYPECHECK_COMMAND}}`
120
+ - **Build:** `{{BUILD_COMMAND}}`
121
+ {{EXTRA_PATTERNS}}
@@ -0,0 +1,44 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ **What it is:** {{PROJECT_DESCRIPTION}}
4
+
5
+ ## Non-Negotiable Invariants
6
+
7
+ 1. **Minimum viable change** — implement exactly what the spec says. No refactoring beyond scope, no future-proofing, no abstractions for hypothetical needs.
8
+ 2. **Spec before code** — never edit files, create branches, or commit unless the linked issue has label `spec:approved` (set by PM only) or the branch starts with `hotfix/`.
9
+
10
+ ## Stage Gate
11
+
12
+ Three labels gate every issue:
13
+
14
+ | Label | Who sets it | What it unlocks |
15
+ |---|---|---|
16
+ | `stage:brainstorming` | Agent (first action) | Reading code + presenting options |
17
+ | `stage:approval` | Agent (after spec is written) | Signals spec is ready for PM review |
18
+ | `spec:approved` | **PM only** | Clears agent to implement |
19
+
20
+ **NEVER apply `spec:approved`.** The PM sets it after reviewing the spec in the issue body. Applying it triggers irreversible automation.
21
+
22
+ ## Workflow
23
+
24
+ 1. Apply `stage:brainstorming` immediately — before reading code or invoking any tool.
25
+ 2. Read the issue. Present problem summary + 2–3 solution options. **Stop and wait for PM to choose.**
26
+ 3. Once PM selects an approach, write the complete spec into the issue body (`gh issue edit <N> --body "..."`). Advance to `stage:approval`.
27
+ 4. **Stop.** Wait for PM to add `spec:approved`.
28
+ 5. After `spec:approved`: create branch, implement minimum viable change, open PR with `Closes #N`.
29
+
30
+ ## Hook Behavior
31
+
32
+ A `PreToolUse` hook blocks `gh pr create` unless:
33
+ - The linked issue has `spec:approved`, **or**
34
+ - The branch name starts with `hotfix/`
35
+
36
+ If blocked: check the issue labels before retrying.
37
+
38
+ ## What NOT to Do
39
+
40
+ - Never commit to `main` directly.
41
+ - Never open a PR without `spec:approved` on the linked issue.
42
+ - Never implement beyond the immediate scope of the spec.
43
+ - Never add abstractions, error handling, or features for hypothetical future needs.
44
+ - Never apply `spec:approved`, `stage:development`, or `qa:*` — these are PM/automation only.
@@ -0,0 +1,118 @@
1
+ const { describe, it } = require('node:test');
2
+ const assert = require('node:assert/strict');
3
+ const os = require('os');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const crypto = require('crypto');
7
+
8
+ const { resolveMode } = require('../bin/cli.js');
9
+
10
+ describe('resolveMode', () => {
11
+ it('returns lite when no flags', () => {
12
+ assert.equal(resolveMode([]), 'lite');
13
+ });
14
+ it('returns full for --agentic', () => {
15
+ assert.equal(resolveMode(['--agentic']), 'full');
16
+ });
17
+ it('returns update for --update', () => {
18
+ assert.equal(resolveMode(['--update']), 'update');
19
+ });
20
+ it('returns upgrade for --upgrade-to-agentic', () => {
21
+ assert.equal(resolveMode(['--upgrade-to-agentic']), 'upgrade');
22
+ });
23
+ it('--update takes precedence over --agentic', () => {
24
+ assert.equal(resolveMode(['--update', '--agentic']), 'update');
25
+ });
26
+ });
27
+
28
+ describe('buildFullClaudeContent', () => {
29
+ it('concatenates lite and full with a newline separator', () => {
30
+ const lite = '# Lite\ncontent';
31
+ const full = '## Extra\nmore';
32
+ const result = lite + '\n' + full;
33
+ assert.ok(result.startsWith('# Lite'));
34
+ assert.ok(result.includes('## Extra'));
35
+ });
36
+ });
37
+
38
+ describe('setActionsVariable', () => {
39
+ it('calls PATCH first', () => {
40
+ const calls = [];
41
+ const execFn = (cmd, args, _opts) => { calls.push([...args]); };
42
+ const { setActionsVariable } = require('../bin/cli.js');
43
+ setActionsVariable('owner/repo', 'PROJECT_ID', 'PVT_abc', execFn);
44
+ assert.equal(calls.length, 1);
45
+ assert.ok(calls[0].includes('--method'));
46
+ assert.ok(calls[0].includes('PATCH'));
47
+ assert.ok(calls[0].some(a => a.includes('PROJECT_ID')));
48
+ assert.ok(calls[0].some(a => a.includes('PVT_abc')));
49
+ });
50
+
51
+ it('falls back to POST on 404', () => {
52
+ const calls = [];
53
+ let callCount = 0;
54
+ const execFn = (cmd, args, _opts) => {
55
+ calls.push([...args]);
56
+ callCount++;
57
+ if (callCount === 1) {
58
+ const err = new Error('Not Found');
59
+ err.stderr = Buffer.from('Not Found');
60
+ throw err;
61
+ }
62
+ };
63
+ const { setActionsVariable } = require('../bin/cli.js');
64
+ setActionsVariable('owner/repo', 'PROJECT_ID', 'PVT_abc', execFn);
65
+ assert.equal(calls.length, 2);
66
+ assert.ok(calls[0].includes('PATCH'));
67
+ assert.ok(calls[0].some(a => a.includes('PVT_abc')));
68
+ assert.ok(calls[1].includes('POST'));
69
+ assert.ok(calls[1].some(a => a.includes('PVT_abc')));
70
+ });
71
+
72
+ it('throws on 403', () => {
73
+ const execFn = () => {
74
+ const err = new Error('Forbidden');
75
+ err.stderr = Buffer.from('Forbidden');
76
+ throw err;
77
+ };
78
+ const { setActionsVariable } = require('../bin/cli.js');
79
+ assert.throws(
80
+ () => setActionsVariable('owner/repo', 'PROJECT_ID', 'PVT_abc', execFn),
81
+ /Forbidden/
82
+ );
83
+ });
84
+ });
85
+
86
+ describe('scaffoldLiteTemplates', () => {
87
+ it('copies CLAUDE.md and AGENTS.md but excludes .github/workflows', () => {
88
+ const { scaffoldLiteTemplates } = require('../bin/cli.js');
89
+ const src = path.join(__dirname, '..');
90
+ const tmp = path.join(os.tmpdir(), `pdlc-lite-${crypto.randomBytes(4).toString('hex')}`);
91
+ try {
92
+ scaffoldLiteTemplates(src, tmp);
93
+ const base = path.join(tmp, '.agentic-pdlc', 'templates');
94
+ assert.ok(fs.existsSync(path.join(base, 'CLAUDE.md')), 'CLAUDE.md should exist');
95
+ assert.ok(fs.existsSync(path.join(base, 'AGENTS.md')), 'AGENTS.md should exist');
96
+ assert.ok(!fs.existsSync(path.join(base, '.github', 'workflows')), '.github/workflows must not exist in lite');
97
+ } finally {
98
+ fs.rmSync(tmp, { recursive: true, force: true });
99
+ }
100
+ });
101
+ });
102
+
103
+ describe('scaffoldFullTemplates', () => {
104
+ it('copies .github/workflows with at least one yml file', () => {
105
+ const { scaffoldFullTemplates } = require('../bin/cli.js');
106
+ const src = path.join(__dirname, '..');
107
+ const tmp = path.join(os.tmpdir(), `pdlc-full-${crypto.randomBytes(4).toString('hex')}`);
108
+ try {
109
+ scaffoldFullTemplates(src, tmp, null, null, {}, 'owner', 'repo');
110
+ const wfDir = path.join(tmp, '.agentic-pdlc', 'templates', '.github', 'workflows');
111
+ assert.ok(fs.existsSync(wfDir), '.github/workflows should exist in full');
112
+ const ymls = fs.readdirSync(wfDir).filter(f => f.endsWith('.yml'));
113
+ assert.ok(ymls.length > 0, 'should contain at least one .yml file');
114
+ } finally {
115
+ fs.rmSync(tmp, { recursive: true, force: true });
116
+ }
117
+ });
118
+ });