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.
- package/.agentic-pdlc/hooks/pdlc-stage-gate.sh +37 -10
- package/.claude/settings.json +18 -0
- package/.coderabbit.yaml +41 -0
- package/.github/workflows/add-to-board.yml +55 -7
- package/.github/workflows/agent-trigger.yml +57 -25
- package/.github/workflows/board-reconciliation.yml +176 -0
- package/.github/workflows/pdlc-health-check.yml +81 -81
- package/.github/workflows/project-automation.yml +252 -259
- package/CLAUDE.md +1 -1
- package/README.md +33 -32
- package/adapters/claude-code/skill.md +12 -8
- package/bin/cli.js +607 -213
- package/docs/superpowers/plans/2026-06-04-spec-format-issue-template.md +160 -0
- package/docs/superpowers/plans/2026-06-04-two-tier-installer.md +1056 -0
- package/docs/superpowers/plans/2026-06-05-archive-card-on-issue-close.md +105 -0
- package/docs/superpowers/plans/2026-06-05-project-id-actions-variable.md +336 -0
- package/docs/superpowers/specs/2026-06-04-spec-format-issue-template-design.md +46 -0
- package/docs/superpowers/specs/2026-06-05-project-id-actions-variable-design.md +114 -0
- package/package.json +2 -2
- package/scripts/derive-column.js +20 -0
- package/templates/.github/workflows/add-to-board.yml +2 -2
- package/templates/.github/workflows/agent-trigger.yml +2 -2
- package/templates/.github/workflows/pdlc-health-check.yml +2 -2
- package/templates/.github/workflows/project-automation.yml +47 -8
- package/templates/full/CLAUDE.md +30 -0
- package/templates/lite/AGENTS.md +121 -0
- package/templates/lite/CLAUDE.md +44 -0
- package/tests/cli.test.js +118 -0
- package/.github/workflows/agentic-metrics.yml +0 -545
- package/.github/workflows/qa-agent.yml +0 -139
- package/.github/workflows/qa-gate.yml +0 -51
- /package/templates/{AGENTS.md → full/AGENTS.md} +0 -0
- /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:
|
|
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 != '
|
|
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:
|
|
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 != '
|
|
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 != '
|
|
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 != '
|
|
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 != '
|
|
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 != '
|
|
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 != '
|
|
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 != '
|
|
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
|
+
});
|