create-agentic-pdlc 2.1.5 → 2.2.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/SETUP_PROMPT.md +73 -0
- package/.agentic-pdlc/hooks/pdlc-stage-gate.sh +39 -0
- package/.agentic-pdlc/metrics/.gitkeep +0 -0
- package/.agentic-pdlc/metrics/2026-W19.md +21 -0
- package/.agentic-pdlc/metrics/raw/2026-W19.jsonl +6 -0
- package/.agentic-pdlc/metrics/raw/2026-W20.jsonl +1 -0
- package/.agentic-pdlc/templates/.github/CODEOWNERS +5 -0
- package/.agentic-pdlc/templates/.github/copilot-instructions.md +12 -0
- package/.agentic-pdlc/templates/.github/workflows/add-to-board.yml +38 -0
- package/.agentic-pdlc/templates/.github/workflows/agent-trigger.yml +146 -0
- package/.agentic-pdlc/templates/.github/workflows/agentic-metrics.yml +412 -0
- package/.agentic-pdlc/templates/.github/workflows/auto-approve.yml +16 -0
- package/.agentic-pdlc/templates/.github/workflows/ci.yml +40 -0
- package/.agentic-pdlc/templates/.github/workflows/pdlc-health-check.yml +123 -0
- package/.agentic-pdlc/templates/.github/workflows/pdlc-stage-gate.yml +51 -0
- package/.agentic-pdlc/templates/.github/workflows/project-automation.yml +278 -0
- package/.agentic-pdlc/templates/.github/workflows/protect-workflows.yml +21 -0
- package/.agentic-pdlc/templates/.github/workflows/qa-agent.yml +128 -0
- package/.agentic-pdlc/templates/AGENTS.md +81 -0
- package/.agentic-pdlc/templates/docs/pdlc.md +15 -5
- package/.agentic-setup-prompt.md +73 -0
- package/.agentic-setup.md +73 -0
- package/.claude/settings.json +15 -0
- package/.cursorrules +9 -0
- package/.github/ISSUE_TEMPLATE/pulse-feedback.md +11 -0
- package/.github/workflows/add-to-board.yml +38 -0
- package/.github/workflows/agent-trigger.yml +30 -43
- package/.github/workflows/agentic-metrics.yml +412 -0
- package/.github/workflows/pdlc-health-check.yml +10 -10
- package/.github/workflows/pdlc-stage-gate.yml +51 -0
- package/.github/workflows/project-automation.yml +68 -18
- package/.github/workflows/qa-agent.yml +112 -11
- package/CLAUDE.md +9 -0
- package/README.md +8 -0
- package/SETUP.md +28 -0
- package/adapters/claude-code/skill.md +41 -3
- package/adapters/hooks/pdlc-stage-gate.sh +44 -0
- package/bin/cli.js +28 -5
- package/docs/pdlc.md +15 -5
- package/package.json +1 -1
- package/pr_comments.json +20 -0
- package/templates/.github/workflows/add-to-board.yml +38 -0
- package/templates/.github/workflows/agent-trigger.yml +34 -4
- package/templates/.github/workflows/pdlc-stage-gate.yml +51 -0
- package/templates/.github/workflows/project-automation.yml +78 -54
- package/templates/.github/workflows/qa-agent.yml +14 -13
- package/templates/AGENTS.md +10 -0
- package/templates/docs/pdlc.md +15 -5
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
name: PDLC Board Automation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, reopened, closed, labeled]
|
|
6
|
+
pull_request_review:
|
|
7
|
+
types: [submitted]
|
|
8
|
+
issues:
|
|
9
|
+
types: [labeled]
|
|
10
|
+
|
|
11
|
+
env:
|
|
12
|
+
PROJECT_ID: "{{PROJECT_ID}}"
|
|
13
|
+
STATUS_FIELD_ID: "{{STATUS_FIELD_ID}}"
|
|
14
|
+
STATUS_IDEA: "{{ID_IDEA}}"
|
|
15
|
+
STATUS_EXPLORATION: "{{ID_EXPLORATION}}"
|
|
16
|
+
STATUS_BRAINSTORMING: "{{ID_BRAINSTORMING}}"
|
|
17
|
+
STATUS_DETAILING: "{{ID_DETAILING}}"
|
|
18
|
+
STATUS_APPROVAL: "{{ID_APPROVAL}}"
|
|
19
|
+
STATUS_DEVELOPMENT: "{{ID_DEVELOPMENT}}"
|
|
20
|
+
STATUS_TESTING: "{{ID_TESTING}}"
|
|
21
|
+
STATUS_CODE_REVIEW_PR: "{{ID_CODE_REVIEW_PR}}"
|
|
22
|
+
STATUS_PRODUCTION: "{{ID_PRODUCTION}}"
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
# Issue Labeled → Move Upstream
|
|
26
|
+
move-card-on-label:
|
|
27
|
+
name: Upstream Label → Move Card
|
|
28
|
+
if: github.event_name == 'issues' && github.event.action == 'labeled'
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
env:
|
|
31
|
+
PROJECT_PAT: ${{ secrets.PROJECT_PAT }}
|
|
32
|
+
steps:
|
|
33
|
+
- name: Detect Label and Move Issue
|
|
34
|
+
if: ${{ env.PROJECT_PAT != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
35
|
+
uses: actions/github-script@v7
|
|
36
|
+
with:
|
|
37
|
+
github-token: ${{ env.PROJECT_PAT }}
|
|
38
|
+
script: |
|
|
39
|
+
const labelName = context.payload.label.name;
|
|
40
|
+
let targetStatusId = null;
|
|
41
|
+
let stageName = null;
|
|
42
|
+
|
|
43
|
+
if (labelName === 'stage:exploration') {
|
|
44
|
+
targetStatusId = process.env.STATUS_EXPLORATION;
|
|
45
|
+
stageName = 'Exploration';
|
|
46
|
+
} else if (labelName === 'stage:brainstorming') {
|
|
47
|
+
targetStatusId = process.env.STATUS_BRAINSTORMING;
|
|
48
|
+
stageName = 'Brainstorming';
|
|
49
|
+
} else if (labelName === 'stage:detailing') {
|
|
50
|
+
targetStatusId = process.env.STATUS_DETAILING;
|
|
51
|
+
stageName = 'Detailing';
|
|
52
|
+
} else if (labelName === 'stage:approval') {
|
|
53
|
+
targetStatusId = process.env.STATUS_APPROVAL;
|
|
54
|
+
stageName = 'Approval';
|
|
55
|
+
} else if (labelName === 'stage:development') {
|
|
56
|
+
targetStatusId = process.env.STATUS_DEVELOPMENT;
|
|
57
|
+
stageName = 'Development';
|
|
58
|
+
} else if (labelName === 'stage:testing') {
|
|
59
|
+
targetStatusId = process.env.STATUS_TESTING;
|
|
60
|
+
stageName = 'Testing';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!targetStatusId) {
|
|
64
|
+
console.log('No stage PDLC label found. Skipping.');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const { issue: { number, node_id } } = context.payload;
|
|
69
|
+
|
|
70
|
+
const moveItem = async (nodeId, statusId) => {
|
|
71
|
+
const { addProjectV2ItemById: { item } } = await github.graphql(`
|
|
72
|
+
mutation($p: ID!, $c: ID!) {
|
|
73
|
+
addProjectV2ItemById(input: {projectId: $p, contentId: $c}) { item { id } }
|
|
74
|
+
}`, { p: process.env.PROJECT_ID, c: nodeId });
|
|
75
|
+
|
|
76
|
+
await github.graphql(`
|
|
77
|
+
mutation($p: ID!, $i: ID!, $f: ID!, $v: ProjectV2FieldValue!) {
|
|
78
|
+
updateProjectV2ItemFieldValue(input: {projectId: $p, itemId: $i, fieldId: $f, value: $v}) {
|
|
79
|
+
projectV2Item { id }
|
|
80
|
+
}
|
|
81
|
+
}`, {
|
|
82
|
+
p: process.env.PROJECT_ID, i: item.id, f: process.env.STATUS_FIELD_ID,
|
|
83
|
+
v: { singleSelectOptionId: statusId }
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
await moveItem(node_id, targetStatusId);
|
|
88
|
+
console.log(`Issue #${number} moved to ${stageName}`);
|
|
89
|
+
|
|
90
|
+
# OPTIONAL: Uncomment to enable architecture-violation → Idea
|
|
91
|
+
# move-violation-to-board:
|
|
92
|
+
# name: architecture-violation → 💡 Idea
|
|
93
|
+
# if: github.event_name == 'issues' && github.event.action == 'labeled' && github.event.label.name == 'architecture-violation'
|
|
94
|
+
# runs-on: ubuntu-latest
|
|
95
|
+
# steps:
|
|
96
|
+
# - name: Move issue to Idea
|
|
97
|
+
# if: ${{ env.PROJECT_PAT != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
98
|
+
# uses: actions/github-script@v7
|
|
99
|
+
# with:
|
|
100
|
+
# github-token: ${{ env.PROJECT_PAT }}
|
|
101
|
+
# script: |
|
|
102
|
+
# const { issue: { number, node_id } } = context.payload;
|
|
103
|
+
# const { addProjectV2ItemById: { item } } = await github.graphql(`
|
|
104
|
+
# mutation($p: ID!, $c: ID!) {
|
|
105
|
+
# addProjectV2ItemById(input: {projectId: $p, contentId: $c}) { item { id } }
|
|
106
|
+
# }`, { p: process.env.PROJECT_ID, c: node_id });
|
|
107
|
+
# await github.graphql(`
|
|
108
|
+
# mutation($p: ID!, $i: ID!, $f: ID!, $v: ProjectV2FieldValue!) {
|
|
109
|
+
# updateProjectV2ItemFieldValue(input: {projectId: $p, itemId: $i, fieldId: $f, value: $v}) {
|
|
110
|
+
# projectV2Item { id }
|
|
111
|
+
# }
|
|
112
|
+
# }`, {
|
|
113
|
+
# p: process.env.PROJECT_ID, i: item.id, f: process.env.STATUS_FIELD_ID,
|
|
114
|
+
# v: { singleSelectOptionId: process.env.STATUS_IDEA }
|
|
115
|
+
# });
|
|
116
|
+
# console.log(`Issue #${number} moved to Idea`);
|
|
117
|
+
|
|
118
|
+
# PR Opened → Move linked issue to Testing (Variant B — QA Agent enabled)
|
|
119
|
+
move-card-on-pr-open:
|
|
120
|
+
name: Open PR → Testing
|
|
121
|
+
if: github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'reopened')
|
|
122
|
+
runs-on: ubuntu-latest
|
|
123
|
+
env:
|
|
124
|
+
PROJECT_PAT: ${{ secrets.PROJECT_PAT }}
|
|
125
|
+
steps:
|
|
126
|
+
- name: Move linked issue to Testing
|
|
127
|
+
if: ${{ env.PROJECT_PAT != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
128
|
+
uses: actions/github-script@v7
|
|
129
|
+
with:
|
|
130
|
+
github-token: ${{ env.PROJECT_PAT }}
|
|
131
|
+
script: |
|
|
132
|
+
const prNumber = context.payload.pull_request.number;
|
|
133
|
+
const { owner, repo } = context.repo;
|
|
134
|
+
|
|
135
|
+
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
|
|
136
|
+
const body = pr.body ?? '';
|
|
137
|
+
|
|
138
|
+
// Extract issues linked via "Closes #N", "Fixes #N", "Resolves #N"
|
|
139
|
+
const linkedIssues = [...body.matchAll(/(?:Closes?|Fixes?|Resolves?)\s+#(\d+)/gi)]
|
|
140
|
+
.map(m => parseInt(m[1]));
|
|
141
|
+
|
|
142
|
+
const moveItem = async (nodeId) => {
|
|
143
|
+
const { addProjectV2ItemById: { item } } = await github.graphql(`
|
|
144
|
+
mutation($p: ID!, $c: ID!) {
|
|
145
|
+
addProjectV2ItemById(input: {projectId: $p, contentId: $c}) { item { id } }
|
|
146
|
+
}`, { p: process.env.PROJECT_ID, c: nodeId });
|
|
147
|
+
|
|
148
|
+
await github.graphql(`
|
|
149
|
+
mutation($p: ID!, $i: ID!, $f: ID!, $v: ProjectV2FieldValue!) {
|
|
150
|
+
updateProjectV2ItemFieldValue(input: {projectId: $p, itemId: $i, fieldId: $f, value: $v}) {
|
|
151
|
+
projectV2Item { id }
|
|
152
|
+
}
|
|
153
|
+
}`, {
|
|
154
|
+
p: process.env.PROJECT_ID, i: item.id, f: process.env.STATUS_FIELD_ID,
|
|
155
|
+
v: { singleSelectOptionId: process.env.STATUS_TESTING }
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
if (linkedIssues.length > 0) {
|
|
160
|
+
for (const n of linkedIssues) {
|
|
161
|
+
const { data: issue } = await github.rest.issues.get({ owner, repo, issue_number: n });
|
|
162
|
+
await moveItem(issue.node_id);
|
|
163
|
+
console.log(`Issue #${n} → Testing`);
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
await moveItem(pr.node_id);
|
|
167
|
+
console.log(`PR #${prNumber} → Testing (no linked issue)`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
await github.rest.issues.addLabels({ owner, repo, issue_number: prNumber, labels: ['pr:in-review'] }).catch(() => {});
|
|
171
|
+
|
|
172
|
+
# QA Approved → Move linked issue to Code Review / PR
|
|
173
|
+
move-card-on-qa-pass:
|
|
174
|
+
name: qa:approved → Code Review / PR
|
|
175
|
+
if: github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'qa:approved'
|
|
176
|
+
runs-on: ubuntu-latest
|
|
177
|
+
env:
|
|
178
|
+
PROJECT_PAT: ${{ secrets.PROJECT_PAT }}
|
|
179
|
+
steps:
|
|
180
|
+
- name: Move linked issue to Code Review / PR
|
|
181
|
+
if: ${{ env.PROJECT_PAT != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
182
|
+
uses: actions/github-script@v7
|
|
183
|
+
with:
|
|
184
|
+
github-token: ${{ env.PROJECT_PAT }}
|
|
185
|
+
script: |
|
|
186
|
+
const prNumber = context.payload.pull_request.number;
|
|
187
|
+
const { owner, repo } = context.repo;
|
|
188
|
+
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
|
|
189
|
+
const body = pr.body ?? '';
|
|
190
|
+
const linkedIssues = [...body.matchAll(/(?:Closes?|Fixes?|Resolves?)\s+#(\d+)/gi)].map(m => parseInt(m[1]));
|
|
191
|
+
const moveItem = async (nodeId) => {
|
|
192
|
+
const { addProjectV2ItemById: { item } } = await github.graphql(`
|
|
193
|
+
mutation($p: ID!, $c: ID!) {
|
|
194
|
+
addProjectV2ItemById(input: {projectId: $p, contentId: $c}) { item { id } }
|
|
195
|
+
}`, { p: process.env.PROJECT_ID, c: nodeId });
|
|
196
|
+
await github.graphql(`
|
|
197
|
+
mutation($p: ID!, $i: ID!, $f: ID!, $v: ProjectV2FieldValue!) {
|
|
198
|
+
updateProjectV2ItemFieldValue(input: {projectId: $p, itemId: $i, fieldId: $f, value: $v}) {
|
|
199
|
+
projectV2Item { id }
|
|
200
|
+
}
|
|
201
|
+
}`, {
|
|
202
|
+
p: process.env.PROJECT_ID, i: item.id, f: process.env.STATUS_FIELD_ID,
|
|
203
|
+
v: { singleSelectOptionId: process.env.STATUS_CODE_REVIEW_PR }
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
if (linkedIssues.length > 0) {
|
|
207
|
+
for (const n of linkedIssues) {
|
|
208
|
+
const { data: issue } = await github.rest.issues.get({ owner, repo, issue_number: n });
|
|
209
|
+
await moveItem(issue.node_id);
|
|
210
|
+
console.log(`Issue #${n} → Code Review / PR`);
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
await moveItem(pr.node_id);
|
|
214
|
+
console.log(`PR #${prNumber} → Code Review / PR (no linked issue)`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
# Review Approved → Add Label
|
|
218
|
+
move-card-on-review-approved:
|
|
219
|
+
name: Approved PR → Add Label
|
|
220
|
+
if: github.event_name == 'pull_request_review' && github.event.review.state == 'approved'
|
|
221
|
+
runs-on: ubuntu-latest
|
|
222
|
+
env:
|
|
223
|
+
PROJECT_PAT: ${{ secrets.PROJECT_PAT }}
|
|
224
|
+
steps:
|
|
225
|
+
- name: Swap PR labels
|
|
226
|
+
if: ${{ env.PROJECT_PAT != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
227
|
+
uses: actions/github-script@v7
|
|
228
|
+
with:
|
|
229
|
+
github-token: ${{ env.PROJECT_PAT }}
|
|
230
|
+
script: |
|
|
231
|
+
const prNumber = context.payload.pull_request.number;
|
|
232
|
+
const { owner, repo } = context.repo;
|
|
233
|
+
try { await github.rest.issues.removeLabel({ owner, repo, issue_number: prNumber, name: 'pr:in-review' }); } catch {}
|
|
234
|
+
await github.rest.issues.addLabels({ owner, repo, issue_number: prNumber, labels: ['pr:approved'] }).catch(() => {});
|
|
235
|
+
|
|
236
|
+
# PR Merged → Production
|
|
237
|
+
move-card-on-pr-merge:
|
|
238
|
+
name: Merged PR → Production
|
|
239
|
+
if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == true
|
|
240
|
+
runs-on: ubuntu-latest
|
|
241
|
+
env:
|
|
242
|
+
PROJECT_PAT: ${{ secrets.PROJECT_PAT }}
|
|
243
|
+
steps:
|
|
244
|
+
- name: Move issue to Production
|
|
245
|
+
if: ${{ env.PROJECT_PAT != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
246
|
+
uses: actions/github-script@v7
|
|
247
|
+
with:
|
|
248
|
+
github-token: ${{ env.PROJECT_PAT }}
|
|
249
|
+
script: |
|
|
250
|
+
const prNumber = context.payload.pull_request.number;
|
|
251
|
+
const { owner, repo } = context.repo;
|
|
252
|
+
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
|
|
253
|
+
const body = pr.body ?? '';
|
|
254
|
+
const linkedIssues = [...body.matchAll(/(?:Closes?|Fixes?|Resolves?)\s+#(\d+)/gi)].map(m => parseInt(m[1]));
|
|
255
|
+
|
|
256
|
+
const moveItem = async (nodeId) => {
|
|
257
|
+
const { addProjectV2ItemById: { item } } = await github.graphql(`
|
|
258
|
+
mutation($p: ID!, $c: ID!) {
|
|
259
|
+
addProjectV2ItemById(input: {projectId: $p, contentId: $c}) { item { id } }
|
|
260
|
+
}`, { p: process.env.PROJECT_ID, c: nodeId });
|
|
261
|
+
await github.graphql(`
|
|
262
|
+
mutation($p: ID!, $i: ID!, $f: ID!, $v: ProjectV2FieldValue!) {
|
|
263
|
+
updateProjectV2ItemFieldValue(input: {projectId: $p, itemId: $i, fieldId: $f, value: $v}) {
|
|
264
|
+
projectV2Item { id }
|
|
265
|
+
}
|
|
266
|
+
}`, { p: process.env.PROJECT_ID, i: item.id, f: process.env.STATUS_FIELD_ID,
|
|
267
|
+
v: { singleSelectOptionId: process.env.STATUS_PRODUCTION } });
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
if (linkedIssues.length > 0) {
|
|
271
|
+
for (const n of linkedIssues) {
|
|
272
|
+
const { data: issue } = await github.rest.issues.get({ owner, repo, issue_number: n });
|
|
273
|
+
await moveItem(issue.node_id);
|
|
274
|
+
console.log(`Issue #${n} → Production`);
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
await moveItem(pr.node_id);
|
|
278
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: Protect Workflows
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, synchronize, reopened, labeled]
|
|
6
|
+
paths:
|
|
7
|
+
- '.github/**'
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
block-unauthorized-edits:
|
|
11
|
+
name: Protect .github directory
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- name: Check for Human Approval
|
|
15
|
+
if: ${{ !contains(github.event.pull_request.labels.*.name, 'human-approved') }}
|
|
16
|
+
run: |
|
|
17
|
+
echo "🚨 Modifications in the .github/ directory detected."
|
|
18
|
+
echo "As a security measure, this repository blocks workflow edits by default."
|
|
19
|
+
echo "If you are an AI Agent, do not add the approval label yourself."
|
|
20
|
+
echo "If you are the human owner, please add the 'human-approved' label to this PR to allow merging."
|
|
21
|
+
exit 1
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
name: AI QA Agent
|
|
2
|
+
on:
|
|
3
|
+
pull_request:
|
|
4
|
+
types: [opened, synchronize, reopened]
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
pull-requests: write
|
|
8
|
+
contents: read
|
|
9
|
+
issues: read
|
|
10
|
+
models: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
qa:
|
|
14
|
+
name: AC Coverage Verification (GitHub Models)
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
env:
|
|
17
|
+
PROJECT_PAT: ${{ secrets.PROJECT_PAT }}
|
|
18
|
+
PROJECT_ID: "{{PROJECT_ID}}"
|
|
19
|
+
STATUS_FIELD_ID: "{{STATUS_FIELD_ID}}"
|
|
20
|
+
STATUS_CODE_REVIEW_PR: "{{ID_CODE_REVIEW_PR}}"
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
with:
|
|
24
|
+
fetch-depth: 0
|
|
25
|
+
|
|
26
|
+
- name: Verify AC Coverage via GitHub Models
|
|
27
|
+
env:
|
|
28
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
29
|
+
run: |
|
|
30
|
+
set -e
|
|
31
|
+
|
|
32
|
+
PR_NUMBER="${{ github.event.pull_request.number }}"
|
|
33
|
+
BASE="${{ github.event.pull_request.base.sha }}"
|
|
34
|
+
HEAD="${{ github.event.pull_request.head.sha }}"
|
|
35
|
+
|
|
36
|
+
# Get PR diff (truncated to 8000 chars to stay within context limits)
|
|
37
|
+
DIFF=$(git diff "$BASE" "$HEAD" | head -c 64000)
|
|
38
|
+
|
|
39
|
+
# Extract linked issues from PR body
|
|
40
|
+
PR_BODY=$(gh pr view "$PR_NUMBER" --json body --jq '.body // ""')
|
|
41
|
+
ISSUE_NUMS=$(echo "$PR_BODY" | grep -oiE '(Closes?|Fixes?|Resolves?)\s+#([0-9]+)' | grep -oE '[0-9]+' || true)
|
|
42
|
+
|
|
43
|
+
# Build acceptance criteria context
|
|
44
|
+
AC_CONTEXT=""
|
|
45
|
+
if [ -n "$ISSUE_NUMS" ]; then
|
|
46
|
+
for NUM in $ISSUE_NUMS; do
|
|
47
|
+
ISSUE_BODY=$(gh issue view "$NUM" --json body --jq '.body // ""' 2>/dev/null || echo "")
|
|
48
|
+
AC_CONTEXT="${AC_CONTEXT}\\n\\n--- Issue #${NUM} ---\\n${ISSUE_BODY}"
|
|
49
|
+
done
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
if [ -z "$AC_CONTEXT" ]; then
|
|
53
|
+
AC_CONTEXT="No linked issue found. Evaluate if the PR description is self-contained."
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# Serialize prompt as JSON string and call GitHub Models API (30s timeout)
|
|
57
|
+
PROMPT_JSON=$(printf '%s' "You are a senior QA engineer. Review whether this PR diff satisfies the Acceptance Criteria below.\n\nACCEPTANCE CRITERIA:\n${AC_CONTEXT}\n\nPR DIFF:\n${DIFF}\n\nFirst line of your response must be exactly one word: PASS or FAIL. Second line: brief explanation (max 3 sentences)." | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))')
|
|
58
|
+
|
|
59
|
+
RESPONSE=$(curl -sf -X POST \
|
|
60
|
+
"https://models.github.ai/inference/chat/completions" \
|
|
61
|
+
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
|
|
62
|
+
-H "Content-Type: application/json" \
|
|
63
|
+
-d "{\"model\":\"gpt-4o-mini\",\"messages\":[{\"role\":\"user\",\"content\":${PROMPT_JSON}}]}" \
|
|
64
|
+
--max-time 30 || echo "API_ERROR")
|
|
65
|
+
|
|
66
|
+
if [ "$RESPONSE" = "API_ERROR" ]; then
|
|
67
|
+
gh pr edit "$PR_NUMBER" --add-label "infra:qa-broken"
|
|
68
|
+
gh pr comment "$PR_NUMBER" --body "🤖 **QA Agent:** Could not reach GitHub Models API. Manual review required."
|
|
69
|
+
exit 0
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
VERDICT=$(echo "$RESPONSE" | python3 -c 'import json,sys,re; d=json.load(sys.stdin); t=d.get("choices",[{}])[0].get("message",{}).get("content","").strip(); first=t.split("\n")[0].upper() if t else ""; print("FAIL" if re.search(r"\bFAIL\b",first) else "PASS" if re.search(r"\bPASS\b",first) else "API_ERROR")')
|
|
73
|
+
EXPLANATION=$(echo "$RESPONSE" | python3 -c 'import json,sys; d=json.load(sys.stdin); t=d.get("choices",[{}])[0].get("message",{}).get("content","").strip(); lines=t.split("\n",1); print(lines[1].strip() if len(lines)>1 else "")')
|
|
74
|
+
|
|
75
|
+
if echo "$VERDICT" | grep -q "^PASS"; then
|
|
76
|
+
gh pr edit "$PR_NUMBER" --add-label "qa:approved"
|
|
77
|
+
gh pr comment "$PR_NUMBER" --body "🤖 **QA Agent:** AC coverage verified. ${EXPLANATION}"
|
|
78
|
+
elif echo "$VERDICT" | grep -q "^FAIL"; then
|
|
79
|
+
gh pr edit "$PR_NUMBER" --add-label "qa:needs-work"
|
|
80
|
+
gh pr comment "$PR_NUMBER" --body "🤖 **QA Agent:** AC coverage insufficient. ${EXPLANATION}"
|
|
81
|
+
else
|
|
82
|
+
gh pr edit "$PR_NUMBER" --add-label "infra:qa-broken"
|
|
83
|
+
gh pr comment "$PR_NUMBER" --body "🤖 **QA Agent:** Could not parse GitHub Models response. Manual review required."
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
- name: Move board card to Code Review on qa:approved
|
|
87
|
+
if: ${{ env.PROJECT_PAT != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
88
|
+
uses: actions/github-script@v7
|
|
89
|
+
with:
|
|
90
|
+
github-token: ${{ env.PROJECT_PAT }}
|
|
91
|
+
script: |
|
|
92
|
+
const prNumber = context.payload.pull_request.number;
|
|
93
|
+
const { owner, repo } = context.repo;
|
|
94
|
+
|
|
95
|
+
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
|
|
96
|
+
if (!pr.labels.some(l => l.name === 'qa:approved')) {
|
|
97
|
+
console.log('qa:approved not on PR — skipping board move');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const body = pr.body ?? '';
|
|
102
|
+
const linkedIssues = [...body.matchAll(/(?:Closes?|Fixes?|Resolves?)\s+#(\d+)/gi)]
|
|
103
|
+
.map(m => parseInt(m[1]));
|
|
104
|
+
|
|
105
|
+
const moveItem = async (nodeId) => {
|
|
106
|
+
const { addProjectV2ItemById: { item } } = await github.graphql(`
|
|
107
|
+
mutation($p: ID!, $c: ID!) {
|
|
108
|
+
addProjectV2ItemById(input: {projectId: $p, contentId: $c}) { item { id } }
|
|
109
|
+
}`, { p: process.env.PROJECT_ID, c: nodeId });
|
|
110
|
+
await github.graphql(`
|
|
111
|
+
mutation($p: ID!, $i: ID!, $f: ID!, $v: ProjectV2FieldValue!) {
|
|
112
|
+
updateProjectV2ItemFieldValue(input: {projectId: $p, itemId: $i, fieldId: $f, value: $v}) {
|
|
113
|
+
projectV2Item { id }
|
|
114
|
+
}
|
|
115
|
+
}`, { p: process.env.PROJECT_ID, i: item.id, f: process.env.STATUS_FIELD_ID,
|
|
116
|
+
v: { singleSelectOptionId: process.env.STATUS_CODE_REVIEW_PR } });
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
if (linkedIssues.length > 0) {
|
|
120
|
+
for (const n of linkedIssues) {
|
|
121
|
+
const { data: issue } = await github.rest.issues.get({ owner, repo, issue_number: n });
|
|
122
|
+
await moveItem(issue.node_id);
|
|
123
|
+
console.log(`Issue #${n} → Code Review / PR`);
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
await moveItem(pr.node_id);
|
|
127
|
+
console.log(`PR #${prNumber} → Code Review / PR (no linked issue)`);
|
|
128
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# {{PROJECT_NAME}} — AI Agent Instructions
|
|
2
|
+
|
|
3
|
+
This template is the contract between the project and any external AI agent
|
|
4
|
+
(Claude Code, Cursor, Copilot, Jules, 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. **Human-in-the-Loop** — No external side-effect actions without explicit human approval.
|
|
26
|
+
2. **Immutable Audit-Log** — It's strictly forbidden to UPDATE/DELETE on audit_log; INSERT only.
|
|
27
|
+
3. **Credential Isolation** — Decryption occurs only in a specific service.
|
|
28
|
+
-->
|
|
29
|
+
|
|
30
|
+
## Mandatory Workflow
|
|
31
|
+
|
|
32
|
+
0. **Identity**: Always prefix your GitHub comments with `🤖 **Agent:** ` to distinguish yourself.
|
|
33
|
+
1. **Initial State**: When beginning work on a new issue, your very first action must be to apply the `stage:exploration` label using the GitHub CLI (`gh issue edit <N> --add-label "stage:exploration"`).
|
|
34
|
+
2. Read the issue entirely — understand its type (US/BUG/TASK/SPIKE) and the Acceptance Criteria.
|
|
35
|
+
3. Read `docs/pdlc.md` — understand the PDLC and the Definition of Done in this project.
|
|
36
|
+
4. Read all files mentioned in the issue's technical context.
|
|
37
|
+
5. Implement the **minimum viable change** that satisfies the ACs — do not refactor beyond scope.
|
|
38
|
+
6. Run tests: `{{TEST_COMMAND}}`
|
|
39
|
+
7. Run typecheck (if applicable): `{{TYPECHECK_COMMAND}}`
|
|
40
|
+
8. Create a Pull Request with `Closes #N` in the body — automation moves the board.
|
|
41
|
+
|
|
42
|
+
### Spec format (Upstream Agents)
|
|
43
|
+
|
|
44
|
+
When detailing a solution in an issue body, you must **always** include both the user story and the acceptance criteria. Never append only the ACs to an existing text; rewrite the full issue body in this standard format:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
**As** [user],
|
|
48
|
+
**I want** [action],
|
|
49
|
+
**so that** [benefit].
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Acceptance Criteria
|
|
54
|
+
|
|
55
|
+
**AC1 — ...**
|
|
56
|
+
- Given ...
|
|
57
|
+
- When ...
|
|
58
|
+
- Then ...
|
|
59
|
+
|
|
60
|
+
**AC2 — ...**
|
|
61
|
+
...
|
|
62
|
+
|
|
63
|
+
## Files to modify
|
|
64
|
+
- `path/to/file.ts` — what changes
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## What NOT to do
|
|
68
|
+
|
|
69
|
+
- Never commit directly to `main`.
|
|
70
|
+
- Never open a PR without passing the tests.
|
|
71
|
+
- Never implement beyond the immediate scope of the issue.
|
|
72
|
+
- Never create future-proofing abstractions for hypothetical features.
|
|
73
|
+
{{EXTRA_DONT}}
|
|
74
|
+
|
|
75
|
+
## Project Standards
|
|
76
|
+
|
|
77
|
+
- **Tests:** `{{TEST_COMMAND}}`
|
|
78
|
+
- **Lint/Types:** `{{LINT_COMMAND}}`
|
|
79
|
+
- **Typecheck:** `{{TYPECHECK_COMMAND}}`
|
|
80
|
+
- **Build:** `{{BUILD_COMMAND}}`
|
|
81
|
+
{{EXTRA_PATTERNS}}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
| Column | Meaning | Who moves the card |
|
|
6
6
|
|---|---|---|
|
|
7
|
-
| 💡 Idea | Backlog —
|
|
7
|
+
| 💡 Idea — don't move manually to Exploration | Backlog — tell agent: "work on issue #XX" | Don't move manually |
|
|
8
8
|
| 🔍 Exploration | Claude is analyzing code and context | Label `stage:exploration` |
|
|
9
9
|
| 🧠 Brainstorming | Claude proposed approaches, awaiting PM gate | Label `stage:brainstorming` |
|
|
10
10
|
| 📐 Detail Solution | Claude is writing the technical spec | Label `stage:detailing` |
|
|
@@ -77,6 +77,10 @@ REPO = {{REPO_OWNER}}/{{REPO_NAME}}
|
|
|
77
77
|
| `spec:approved` | Issue | Green | Gate 2 — agent is cleared to implement |
|
|
78
78
|
| `pr:in-review` | PR | Yellow | Awaiting code review |
|
|
79
79
|
| `pr:approved` | PR | Green | Code review approved |
|
|
80
|
+
| `type:us` | Issue | Blue | New feature or behavioral change — full flow |
|
|
81
|
+
| `type:task` | Issue | Yellow | Operational/non-functional change — skips brainstorming |
|
|
82
|
+
| `type:bug` | Issue | Red | Something broken — skips brainstorming |
|
|
83
|
+
| `type:spike` | Issue | Gray | Research/evaluation — never reaches Development |
|
|
80
84
|
|
|
81
85
|
## Approval Gates
|
|
82
86
|
|
|
@@ -90,10 +94,16 @@ This triggers the implementation agent via `agent-trigger.yml`.
|
|
|
90
94
|
|
|
91
95
|
## Shortcuts by Type
|
|
92
96
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
The `type:*` label is the authoritative signal — set automatically by the agent via type inference (see `adapters/claude-code/skill.md`). Title prefixes (`🔧 TASK:`, `👤 US:`) are hints for humans; the label drives the flow.
|
|
98
|
+
|
|
99
|
+
| Label | Flow |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `type:us` | Full flow — exploration → brainstorming → Gate 1 → detailing → approval |
|
|
102
|
+
| `type:task` | Skips brainstorming — exploration → detailing → approval |
|
|
103
|
+
| `type:bug` | Skips brainstorming — exploration → detailing → approval |
|
|
104
|
+
| `type:spike` | Skips brainstorming — exploration → detailing → conclusion comment (never reaches Development) |
|
|
105
|
+
|
|
106
|
+
If no `type:*` label present and agent confidence < 85%, defaults to `type:us` (safe fallback — never skips gates by omission).
|
|
97
107
|
|
|
98
108
|
## Definition of Done
|
|
99
109
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agentic-pdlc
|
|
3
|
+
description: Orchestrates the Agentic Product Development Life Cycle (PDLC) upstream stages (Idea -> Spec) and includes an interactive Setup Mode to initialize the framework.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Agentic PDLC Orchestrator
|
|
7
|
+
|
|
8
|
+
You are the upstream brainstorm partner and orchestrator for the Agentic PDLC framework. Your role is primarily to define technical specs based on user ideas.
|
|
9
|
+
|
|
10
|
+
## SETUP MODE
|
|
11
|
+
|
|
12
|
+
If the user invokes you in a new project, you must first check if the PDLC artifacts are present in the repository.
|
|
13
|
+
Specifically, check for:
|
|
14
|
+
- `AGENTS.md`
|
|
15
|
+
- `docs/pdlc.md`
|
|
16
|
+
- `.github/CODEOWNERS`
|
|
17
|
+
- `.github/workflows/project-automation.yml`
|
|
18
|
+
- `.github/workflows/agent-trigger.yml`
|
|
19
|
+
- `.github/workflows/pdlc-health-check.yml`
|
|
20
|
+
|
|
21
|
+
If any of these files are missing, you are in **Setup Mode**. Do not proceed with feature requests until setup is complete.
|
|
22
|
+
1. **Language Detection:** Analyze the user's previous prompts and preferred language. Conduct this entire Setup Mode and ask all your interactive questions in that same language.
|
|
23
|
+
2. Acknowledge that the framework is not yet set up.
|
|
24
|
+
3. Interactively ask the user for required values **one group at a time**:
|
|
25
|
+
- **Project basics:** Project Name, Description, Technical Stack (Structure), and GitHub Username (for CODEOWNERS security).
|
|
26
|
+
- **Commands:** Test command, Lint command, Build command.
|
|
27
|
+
- **Invariants:** Critical business rules agents must never violate (e.g. Human-in-the-loop).
|
|
28
|
+
- **Board IDs:** PROJECT_ID, STATUS_FIELD_ID, column option IDs (provide standard PDLC options: Idea, Exploration, Brainstorming, Detail Solution, Approval, Development, Testing, Code Review / PR, Production). Allow user to answer "skip", which means you leave the placeholders intact.
|
|
29
|
+
- **Architecture Violation:** Ask "Does your project use an automated architecture auditing tool (e.g., a CI job that creates issues with an `architecture-violation` label)?". If yes, uncomment the `move-violation-to-board` job inside `project-automation.yml`. If no, ask if they would like help implementing one, reminding them that it significantly improves their agentic development process. If they decline, you can leave the job commented out.
|
|
30
|
+
- **QA Agent (Variant B):** Ask "Do you plan to use an AI QA Agent (e.g. QAWolf or a secondary script) to verify your PRs before Code Review?". If yes, explain you will adopt Variant B: you will change `STATUS_CODE_REVIEW_PR` to `STATUS_TESTING` inside the `move-card-on-pr-open` job in `project-automation.yml` and uncomment the `move-card-on-qa-pass` job. If no, leave the workflow as Variant A (default) and delete the optional `.github/workflows/qa-agent.yml` template.
|
|
31
|
+
- **Implementation agent handle:** e.g., `@google-labs-jules`, or "none".
|
|
32
|
+
3. Generate and write the missing files replacing the `{{SCREAMING_SNAKE_CASE}}` placeholders using the templates logic you know (usually they reside in standard Agentic PDLC templates).
|
|
33
|
+
4. Offer to run the `gh` commands for labels (`spec:approved`, `pr:in-review`, `pr:approved`, `architecture-violation`).
|
|
34
|
+
5. Commit everything with the message: `chore: setup agentic-pdlc framework`.
|
|
35
|
+
6. **IMPORTANT:** Delete this setup prompt file (e.g., `.agentic-setup.md`, `.agentic-setup-prompt.md`, or `.agentic-pdlc/SETUP_PROMPT.md`) from the root or `.agentic-pdlc/` directory to clean up the workspace.
|
|
36
|
+
7. Conclude Setup Mode.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## EXECUTION MODE
|
|
41
|
+
|
|
42
|
+
If `AGENTS.md` and `docs/pdlc.md` are present, you are in **Execution Mode**.
|
|
43
|
+
|
|
44
|
+
### 1. Daily Upstream Loop
|
|
45
|
+
Your job is to move issues from "Idea" to "Detail Solution".
|
|
46
|
+
When asked to work on a feature, you will:
|
|
47
|
+
- Explore the code context.
|
|
48
|
+
- Present architectural approaches (Brainstorming).
|
|
49
|
+
- Stop and wait for the human PM's explicit approval (Gate 1).
|
|
50
|
+
|
|
51
|
+
### 2. Creating the Spec
|
|
52
|
+
Once approved, you will detail the solution directly into the GitHub Issue body. Focus on precise Acceptance Criteria.
|
|
53
|
+
**IMPORTANT:** You must always rewrite the full issue body to include both the user story and the Acceptance Criteria. Do not simply append the ACs to the existing text. Use this format:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
**As** [user],
|
|
57
|
+
**I want** [action],
|
|
58
|
+
**so that** [benefit].
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Acceptance Criteria
|
|
63
|
+
...
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 3. Handoff
|
|
67
|
+
Do not write code for downstream features! Your goal is to refine the Spec, so the human Tech Lead can label the issue `spec:approved`. This label triggers the downstream agent via `agent-trigger.yml`.
|
|
68
|
+
|
|
69
|
+
### 4. Moving the Board (Upstream States)
|
|
70
|
+
As you actively work with the user advancing the feature, you MUST use the GitHub CLI to update internal state labels. This triggers GitHub Actions behind the scenes.
|
|
71
|
+
- Starting context evaluation: Run `gh issue edit <N> --add-label "stage:exploration"`
|
|
72
|
+
- Presenting architecture/approaches: Run `gh issue edit <N> --add-label "stage:brainstorming"`
|
|
73
|
+
- Starting to write the technical spec: Run `gh issue edit <N> --add-label "stage:detailing"`
|