create-agentic-pdlc 2.1.6 → 2.2.1
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/SETUP.md +28 -0
- package/adapters/claude-code/skill.md +63 -15
- package/adapters/hooks/pdlc-stage-gate.sh +44 -0
- package/bin/cli.js +113 -11
- 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/docs/pdlc.md +15 -5
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Add to Board on Open
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issues:
|
|
5
|
+
types: [opened]
|
|
6
|
+
|
|
7
|
+
env:
|
|
8
|
+
PROJECT_ID: "{{PROJECT_ID}}"
|
|
9
|
+
STATUS_FIELD_ID: "{{STATUS_FIELD_ID}}"
|
|
10
|
+
STATUS_IDEA: "{{ID_IDEA}}"
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
add-to-board:
|
|
14
|
+
name: Auto-add new issue to board
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
env:
|
|
17
|
+
PROJECT_PAT: ${{ secrets.PROJECT_PAT }}
|
|
18
|
+
steps:
|
|
19
|
+
- name: Add issue to project board
|
|
20
|
+
if: ${{ env.PROJECT_PAT != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
21
|
+
uses: actions/github-script@v7
|
|
22
|
+
with:
|
|
23
|
+
github-token: ${{ env.PROJECT_PAT }}
|
|
24
|
+
script: |
|
|
25
|
+
const nodeId = context.payload.issue.node_id;
|
|
26
|
+
const number = context.payload.issue.number;
|
|
27
|
+
const { addProjectV2ItemById: { item } } = await github.graphql(`
|
|
28
|
+
mutation($p: ID!, $c: ID!) {
|
|
29
|
+
addProjectV2ItemById(input: {projectId: $p, contentId: $c}) { item { id } }
|
|
30
|
+
}`, { p: process.env.PROJECT_ID, c: nodeId });
|
|
31
|
+
await github.graphql(`
|
|
32
|
+
mutation($p: ID!, $i: ID!, $f: ID!, $v: ProjectV2FieldValue!) {
|
|
33
|
+
updateProjectV2ItemFieldValue(input: {projectId: $p, itemId: $i, fieldId: $f, value: $v}) {
|
|
34
|
+
projectV2Item { id }
|
|
35
|
+
}
|
|
36
|
+
}`, { p: process.env.PROJECT_ID, i: item.id, f: process.env.STATUS_FIELD_ID,
|
|
37
|
+
v: { singleSelectOptionId: process.env.STATUS_IDEA } });
|
|
38
|
+
console.log(`Issue #${number} added to board → Idea`);
|
|
@@ -16,16 +16,20 @@ jobs:
|
|
|
16
16
|
issues: write
|
|
17
17
|
pull-requests: write
|
|
18
18
|
contents: read
|
|
19
|
+
env:
|
|
20
|
+
PROJECT_PAT: ${{ secrets.PROJECT_PAT }}
|
|
21
|
+
PROJECT_ID: "{{PROJECT_ID}}"
|
|
22
|
+
STATUS_FIELD_ID: "{{STATUS_FIELD_ID}}"
|
|
23
|
+
STATUS_DEVELOPMENT: "{{ID_DEVELOPMENT}}"
|
|
19
24
|
steps:
|
|
20
25
|
- name: Update Labels
|
|
21
|
-
if: ${{ !contains('{{IMPLEMENTATION_AGENT_LABEL}}', '{{') }}
|
|
22
26
|
uses: actions/github-script@v7
|
|
23
27
|
with:
|
|
24
28
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
25
29
|
script: |
|
|
26
30
|
const { owner, repo } = context.repo;
|
|
27
31
|
const issue_number = context.payload.issue.number;
|
|
28
|
-
|
|
32
|
+
|
|
29
33
|
try {
|
|
30
34
|
await github.rest.issues.removeLabel({
|
|
31
35
|
owner,
|
|
@@ -36,14 +40,40 @@ jobs:
|
|
|
36
40
|
} catch (error) {
|
|
37
41
|
console.log('Label stage:approval not found or could not be removed');
|
|
38
42
|
}
|
|
39
|
-
|
|
43
|
+
|
|
44
|
+
const agentLabel = '{{IMPLEMENTATION_AGENT_LABEL}}';
|
|
45
|
+
const labelsToAdd = ['stage:development'];
|
|
46
|
+
if (!agentLabel.includes('{{')) labelsToAdd.push(agentLabel, 'agent:working');
|
|
47
|
+
|
|
40
48
|
await github.rest.issues.addLabels({
|
|
41
49
|
owner,
|
|
42
50
|
repo,
|
|
43
51
|
issue_number,
|
|
44
|
-
labels:
|
|
52
|
+
labels: labelsToAdd
|
|
45
53
|
});
|
|
46
54
|
|
|
55
|
+
- name: Move board card to Development
|
|
56
|
+
if: ${{ env.PROJECT_PAT != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
57
|
+
continue-on-error: true
|
|
58
|
+
uses: actions/github-script@v7
|
|
59
|
+
with:
|
|
60
|
+
github-token: ${{ env.PROJECT_PAT }}
|
|
61
|
+
script: |
|
|
62
|
+
const nodeId = context.payload.issue.node_id;
|
|
63
|
+
const number = context.payload.issue.number;
|
|
64
|
+
const { addProjectV2ItemById: { item } } = await github.graphql(`
|
|
65
|
+
mutation($p: ID!, $c: ID!) {
|
|
66
|
+
addProjectV2ItemById(input: {projectId: $p, contentId: $c}) { item { id } }
|
|
67
|
+
}`, { p: process.env.PROJECT_ID, c: nodeId });
|
|
68
|
+
await github.graphql(`
|
|
69
|
+
mutation($p: ID!, $i: ID!, $f: ID!, $v: ProjectV2FieldValue!) {
|
|
70
|
+
updateProjectV2ItemFieldValue(input: {projectId: $p, itemId: $i, fieldId: $f, value: $v}) {
|
|
71
|
+
projectV2Item { id }
|
|
72
|
+
}
|
|
73
|
+
}`, { p: process.env.PROJECT_ID, i: item.id, f: process.env.STATUS_FIELD_ID,
|
|
74
|
+
v: { singleSelectOptionId: process.env.STATUS_DEVELOPMENT } });
|
|
75
|
+
console.log(`Issue #${number} → Development`);
|
|
76
|
+
|
|
47
77
|
- name: Comment on issue to trigger agent and prevent race conditions
|
|
48
78
|
if: ${{ !contains('{{IMPLEMENTATION_AGENT_LABEL}}', '{{') }}
|
|
49
79
|
uses: actions/github-script@v7
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: PDLC Stage Gate
|
|
2
|
+
on:
|
|
3
|
+
pull_request:
|
|
4
|
+
types: [opened, synchronize, reopened, labeled, unlabeled]
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
pull-requests: read
|
|
8
|
+
issues: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
stage-gate:
|
|
12
|
+
name: PDLC Stage Gate
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- name: Check stage:approval
|
|
16
|
+
env:
|
|
17
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
18
|
+
run: |
|
|
19
|
+
set -e
|
|
20
|
+
REPO="${{ github.repository }}"
|
|
21
|
+
PR_NUMBER="${{ github.event.pull_request.number }}"
|
|
22
|
+
|
|
23
|
+
PR_LABELS=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json labels --jq '[.labels[].name] | join(" ")')
|
|
24
|
+
if echo "$PR_LABELS" | grep -qw "hotfix"; then
|
|
25
|
+
echo "✅ Hotfix label — stage gate bypassed."
|
|
26
|
+
exit 0
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
PR_BODY=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json body --jq '.body // ""')
|
|
30
|
+
ISSUE_NUMS=$(echo "$PR_BODY" | grep -oiE '(Closes?|Fixes?|Resolves?)\s+#([0-9]+)' | grep -oE '[0-9]+' || true)
|
|
31
|
+
|
|
32
|
+
if [ -z "$ISSUE_NUMS" ]; then
|
|
33
|
+
echo "❌ No linked issues in PR body."
|
|
34
|
+
echo " Add 'Closes #N' to PR body, or add 'hotfix' label to PR for emergencies."
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
for NUM in $ISSUE_NUMS; do
|
|
39
|
+
LABELS=$(gh issue view "$NUM" --repo "$REPO" --json labels --jq '[.labels[].name] | join(" ")' 2>/dev/null || echo "")
|
|
40
|
+
if echo "$LABELS" | grep -qw "stage:approval" || echo "$LABELS" | grep -qw "spec:approved" || echo "$LABELS" | grep -qw "stage:development"; then
|
|
41
|
+
echo "✅ Issue #$NUM approved"
|
|
42
|
+
else
|
|
43
|
+
STAGE=$(echo "$LABELS" | tr ' ' '\n' | grep "^stage:" | head -1 || echo "none")
|
|
44
|
+
echo "❌ Issue #$NUM missing approval (current: $STAGE)"
|
|
45
|
+
echo " Required: stage:approval OR spec:approved OR stage:development label on the issue."
|
|
46
|
+
echo " Emergency bypass: add 'hotfix' label to this PR."
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
done
|
|
50
|
+
|
|
51
|
+
echo "✅ All linked issues approved."
|
|
@@ -6,7 +6,7 @@ on:
|
|
|
6
6
|
pull_request_review:
|
|
7
7
|
types: [submitted]
|
|
8
8
|
issues:
|
|
9
|
-
types: [labeled]
|
|
9
|
+
types: [labeled, edited]
|
|
10
10
|
|
|
11
11
|
env:
|
|
12
12
|
PROJECT_ID: "{{PROJECT_ID}}"
|
|
@@ -87,6 +87,32 @@ jobs:
|
|
|
87
87
|
await moveItem(node_id, targetStatusId);
|
|
88
88
|
console.log(`Issue #${number} moved to ${stageName}`);
|
|
89
89
|
|
|
90
|
+
auto-move-to-approval:
|
|
91
|
+
name: Spec complete → stage:approval
|
|
92
|
+
if: >
|
|
93
|
+
github.event_name == 'issues' &&
|
|
94
|
+
github.event.action == 'edited' &&
|
|
95
|
+
contains(github.event.issue.labels.*.name, 'stage:detailing')
|
|
96
|
+
runs-on: ubuntu-latest
|
|
97
|
+
env:
|
|
98
|
+
PROJECT_PAT: ${{ secrets.PROJECT_PAT }}
|
|
99
|
+
steps:
|
|
100
|
+
- name: Check spec markers and swap labels
|
|
101
|
+
if: ${{ env.PROJECT_PAT != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
102
|
+
uses: actions/github-script@v7
|
|
103
|
+
with:
|
|
104
|
+
github-token: ${{ env.PROJECT_PAT }}
|
|
105
|
+
script: |
|
|
106
|
+
const body = context.payload.issue.body ?? '';
|
|
107
|
+
if (!body.includes('## Acceptance Criteria') || !body.includes('## Files to modify')) return;
|
|
108
|
+
|
|
109
|
+
const { owner, repo } = context.repo;
|
|
110
|
+
const issue_number = context.payload.issue.number;
|
|
111
|
+
|
|
112
|
+
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['stage:approval'] }).catch(() => {});
|
|
113
|
+
await github.rest.issues.removeLabel({ owner, repo, issue_number, name: 'stage:detailing' }).catch(() => {});
|
|
114
|
+
console.log(`Issue #${issue_number} auto-moved to stage:approval`);
|
|
115
|
+
|
|
90
116
|
# OPTIONAL: Uncomment to enable architecture-violation → Idea
|
|
91
117
|
# move-violation-to-board:
|
|
92
118
|
# name: architecture-violation → 💡 Idea
|
|
@@ -115,16 +141,15 @@ jobs:
|
|
|
115
141
|
# });
|
|
116
142
|
# console.log(`Issue #${number} moved to Idea`);
|
|
117
143
|
|
|
118
|
-
# PR Opened → Move linked issue to
|
|
119
|
-
# 💡 VARIANT B (QA Agent): If using an AI QA agent, change `STATUS_CODE_REVIEW_PR` to `STATUS_TESTING` on line ~158
|
|
144
|
+
# PR Opened → Move linked issue to Testing (Variant B — QA Agent enabled)
|
|
120
145
|
move-card-on-pr-open:
|
|
121
|
-
name: Open PR →
|
|
146
|
+
name: Open PR → Testing
|
|
122
147
|
if: github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'reopened')
|
|
123
148
|
runs-on: ubuntu-latest
|
|
124
149
|
env:
|
|
125
150
|
PROJECT_PAT: ${{ secrets.PROJECT_PAT }}
|
|
126
151
|
steps:
|
|
127
|
-
- name: Move linked issue to
|
|
152
|
+
- name: Move linked issue to Testing
|
|
128
153
|
if: ${{ env.PROJECT_PAT != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
129
154
|
uses: actions/github-script@v7
|
|
130
155
|
with:
|
|
@@ -153,7 +178,7 @@ jobs:
|
|
|
153
178
|
}
|
|
154
179
|
}`, {
|
|
155
180
|
p: process.env.PROJECT_ID, i: item.id, f: process.env.STATUS_FIELD_ID,
|
|
156
|
-
v: { singleSelectOptionId: process.env.
|
|
181
|
+
v: { singleSelectOptionId: process.env.STATUS_TESTING }
|
|
157
182
|
});
|
|
158
183
|
};
|
|
159
184
|
|
|
@@ -161,15 +186,60 @@ jobs:
|
|
|
161
186
|
for (const n of linkedIssues) {
|
|
162
187
|
const { data: issue } = await github.rest.issues.get({ owner, repo, issue_number: n });
|
|
163
188
|
await moveItem(issue.node_id);
|
|
164
|
-
console.log(`Issue #${n} →
|
|
189
|
+
console.log(`Issue #${n} → Testing`);
|
|
165
190
|
}
|
|
166
191
|
} else {
|
|
167
192
|
await moveItem(pr.node_id);
|
|
168
|
-
console.log(`PR #${prNumber} →
|
|
193
|
+
console.log(`PR #${prNumber} → Testing (no linked issue)`);
|
|
169
194
|
}
|
|
170
195
|
|
|
171
196
|
await github.rest.issues.addLabels({ owner, repo, issue_number: prNumber, labels: ['pr:in-review'] }).catch(() => {});
|
|
172
197
|
|
|
198
|
+
# QA Approved → Move linked issue to Code Review / PR
|
|
199
|
+
move-card-on-qa-pass:
|
|
200
|
+
name: qa:approved → Code Review / PR
|
|
201
|
+
if: github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'qa:approved'
|
|
202
|
+
runs-on: ubuntu-latest
|
|
203
|
+
env:
|
|
204
|
+
PROJECT_PAT: ${{ secrets.PROJECT_PAT }}
|
|
205
|
+
steps:
|
|
206
|
+
- name: Move linked issue to Code Review / PR
|
|
207
|
+
if: ${{ env.PROJECT_PAT != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
208
|
+
uses: actions/github-script@v7
|
|
209
|
+
with:
|
|
210
|
+
github-token: ${{ env.PROJECT_PAT }}
|
|
211
|
+
script: |
|
|
212
|
+
const prNumber = context.payload.pull_request.number;
|
|
213
|
+
const { owner, repo } = context.repo;
|
|
214
|
+
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
|
|
215
|
+
const body = pr.body ?? '';
|
|
216
|
+
const linkedIssues = [...body.matchAll(/(?:Closes?|Fixes?|Resolves?)\s+#(\d+)/gi)].map(m => parseInt(m[1]));
|
|
217
|
+
const moveItem = async (nodeId) => {
|
|
218
|
+
const { addProjectV2ItemById: { item } } = await github.graphql(`
|
|
219
|
+
mutation($p: ID!, $c: ID!) {
|
|
220
|
+
addProjectV2ItemById(input: {projectId: $p, contentId: $c}) { item { id } }
|
|
221
|
+
}`, { p: process.env.PROJECT_ID, c: nodeId });
|
|
222
|
+
await github.graphql(`
|
|
223
|
+
mutation($p: ID!, $i: ID!, $f: ID!, $v: ProjectV2FieldValue!) {
|
|
224
|
+
updateProjectV2ItemFieldValue(input: {projectId: $p, itemId: $i, fieldId: $f, value: $v}) {
|
|
225
|
+
projectV2Item { id }
|
|
226
|
+
}
|
|
227
|
+
}`, {
|
|
228
|
+
p: process.env.PROJECT_ID, i: item.id, f: process.env.STATUS_FIELD_ID,
|
|
229
|
+
v: { singleSelectOptionId: process.env.STATUS_CODE_REVIEW_PR }
|
|
230
|
+
});
|
|
231
|
+
};
|
|
232
|
+
if (linkedIssues.length > 0) {
|
|
233
|
+
for (const n of linkedIssues) {
|
|
234
|
+
const { data: issue } = await github.rest.issues.get({ owner, repo, issue_number: n });
|
|
235
|
+
await moveItem(issue.node_id);
|
|
236
|
+
console.log(`Issue #${n} → Code Review / PR`);
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
await moveItem(pr.node_id);
|
|
240
|
+
console.log(`PR #${prNumber} → Code Review / PR (no linked issue)`);
|
|
241
|
+
}
|
|
242
|
+
|
|
173
243
|
# Review Approved → Add Label
|
|
174
244
|
move-card-on-review-approved:
|
|
175
245
|
name: Approved PR → Add Label
|
|
@@ -189,52 +259,6 @@ jobs:
|
|
|
189
259
|
try { await github.rest.issues.removeLabel({ owner, repo, issue_number: prNumber, name: 'pr:in-review' }); } catch {}
|
|
190
260
|
await github.rest.issues.addLabels({ owner, repo, issue_number: prNumber, labels: ['pr:approved'] }).catch(() => {});
|
|
191
261
|
|
|
192
|
-
# OPTIONAL: Uncomment for Variant B (QA Agent enabled)
|
|
193
|
-
# When qa:approved is added to a PR, move linked issue to Code Review / PR
|
|
194
|
-
# move-card-on-qa-pass:
|
|
195
|
-
# name: qa:approved → Code Review / PR
|
|
196
|
-
# if: github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'qa:approved'
|
|
197
|
-
# runs-on: ubuntu-latest
|
|
198
|
-
# env:
|
|
199
|
-
# PROJECT_PAT: ${{ secrets.PROJECT_PAT }}
|
|
200
|
-
# steps:
|
|
201
|
-
# - name: Move linked issue to Code Review / PR
|
|
202
|
-
# if: ${{ env.PROJECT_PAT != '' && env.PROJECT_ID != '{{PROJECT_ID}}' }}
|
|
203
|
-
# uses: actions/github-script@v7
|
|
204
|
-
# with:
|
|
205
|
-
# github-token: ${{ env.PROJECT_PAT }}
|
|
206
|
-
# script: |
|
|
207
|
-
# const prNumber = context.payload.pull_request.number;
|
|
208
|
-
# const { owner, repo } = context.repo;
|
|
209
|
-
# const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
|
|
210
|
-
# const body = pr.body ?? '';
|
|
211
|
-
# const linkedIssues = [...body.matchAll(/(?:Closes?|Fixes?|Resolves?)\s+#(\d+)/gi)].map(m => parseInt(m[1]));
|
|
212
|
-
# const moveItem = async (nodeId) => {
|
|
213
|
-
# const { addProjectV2ItemById: { item } } = await github.graphql(`
|
|
214
|
-
# mutation($p: ID!, $c: ID!) {
|
|
215
|
-
# addProjectV2ItemById(input: {projectId: $p, contentId: $c}) { item { id } }
|
|
216
|
-
# }`, { p: process.env.PROJECT_ID, c: nodeId });
|
|
217
|
-
# await github.graphql(`
|
|
218
|
-
# mutation($p: ID!, $i: ID!, $f: ID!, $v: ProjectV2FieldValue!) {
|
|
219
|
-
# updateProjectV2ItemFieldValue(input: {projectId: $p, itemId: $i, fieldId: $f, value: $v}) {
|
|
220
|
-
# projectV2Item { id }
|
|
221
|
-
# }
|
|
222
|
-
# }`, {
|
|
223
|
-
# p: process.env.PROJECT_ID, i: item.id, f: process.env.STATUS_FIELD_ID,
|
|
224
|
-
# v: { singleSelectOptionId: process.env.STATUS_CODE_REVIEW_PR }
|
|
225
|
-
# });
|
|
226
|
-
# };
|
|
227
|
-
# if (linkedIssues.length > 0) {
|
|
228
|
-
# for (const n of linkedIssues) {
|
|
229
|
-
# const { data: issue } = await github.rest.issues.get({ owner, repo, issue_number: n });
|
|
230
|
-
# await moveItem(issue.node_id);
|
|
231
|
-
# console.log(`Issue #${n} → Code Review / PR`);
|
|
232
|
-
# }
|
|
233
|
-
# } else {
|
|
234
|
-
# await moveItem(pr.node_id);
|
|
235
|
-
# console.log(`PR #${prNumber} → Code Review / PR (no linked issue)`);
|
|
236
|
-
# }
|
|
237
|
-
|
|
238
262
|
# PR Merged → Production
|
|
239
263
|
move-card-on-pr-merge:
|
|
240
264
|
name: Merged PR → Production
|
|
@@ -7,20 +7,20 @@ permissions:
|
|
|
7
7
|
pull-requests: write
|
|
8
8
|
contents: read
|
|
9
9
|
issues: read
|
|
10
|
+
models: read
|
|
10
11
|
|
|
11
12
|
jobs:
|
|
12
13
|
qa:
|
|
13
|
-
name: AC Coverage Verification (
|
|
14
|
+
name: AC Coverage Verification (GitHub Models)
|
|
14
15
|
runs-on: ubuntu-latest
|
|
15
16
|
steps:
|
|
16
17
|
- uses: actions/checkout@v4
|
|
17
18
|
with:
|
|
18
19
|
fetch-depth: 0
|
|
19
20
|
|
|
20
|
-
- name: Verify AC Coverage via
|
|
21
|
+
- name: Verify AC Coverage via GitHub Models
|
|
21
22
|
env:
|
|
22
23
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
23
|
-
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
|
24
24
|
run: |
|
|
25
25
|
set -e
|
|
26
26
|
|
|
@@ -29,7 +29,7 @@ jobs:
|
|
|
29
29
|
HEAD="${{ github.event.pull_request.head.sha }}"
|
|
30
30
|
|
|
31
31
|
# Get PR diff (truncated to 8000 chars to stay within context limits)
|
|
32
|
-
DIFF=$(git diff "$BASE" "$HEAD" | head -c
|
|
32
|
+
DIFF=$(git diff "$BASE" "$HEAD" | head -c 64000)
|
|
33
33
|
|
|
34
34
|
# Extract linked issues from PR body
|
|
35
35
|
PR_BODY=$(gh pr view "$PR_NUMBER" --json body --jq '.body // ""')
|
|
@@ -48,23 +48,24 @@ jobs:
|
|
|
48
48
|
AC_CONTEXT="No linked issue found. Evaluate if the PR description is self-contained."
|
|
49
49
|
fi
|
|
50
50
|
|
|
51
|
-
# Serialize prompt as JSON string and call
|
|
52
|
-
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\
|
|
51
|
+
# Serialize prompt as JSON string and call GitHub Models API (30s timeout)
|
|
52
|
+
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()))')
|
|
53
53
|
|
|
54
|
-
RESPONSE=$(curl -
|
|
55
|
-
"https://
|
|
54
|
+
RESPONSE=$(curl -sf -X POST \
|
|
55
|
+
"https://models.github.ai/inference/chat/completions" \
|
|
56
|
+
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
|
|
56
57
|
-H "Content-Type: application/json" \
|
|
57
|
-
-d "{\"
|
|
58
|
+
-d "{\"model\":\"gpt-4o-mini\",\"messages\":[{\"role\":\"user\",\"content\":${PROMPT_JSON}}]}" \
|
|
58
59
|
--max-time 30 || echo "API_ERROR")
|
|
59
60
|
|
|
60
61
|
if [ "$RESPONSE" = "API_ERROR" ]; then
|
|
61
62
|
gh pr edit "$PR_NUMBER" --add-label "infra:qa-broken"
|
|
62
|
-
gh pr comment "$PR_NUMBER" --body "🤖 **QA Agent:** Could not reach
|
|
63
|
+
gh pr comment "$PR_NUMBER" --body "🤖 **QA Agent:** Could not reach GitHub Models API. Manual review required."
|
|
63
64
|
exit 0
|
|
64
65
|
fi
|
|
65
66
|
|
|
66
|
-
VERDICT=$(echo "$RESPONSE" | python3 -c 'import json,sys; d=json.load(sys.stdin); t=d.get("
|
|
67
|
-
EXPLANATION=$(echo "$RESPONSE" | python3 -c 'import json,sys; d=json.load(sys.stdin); t=d.get("
|
|
67
|
+
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")')
|
|
68
|
+
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 "")')
|
|
68
69
|
|
|
69
70
|
if echo "$VERDICT" | grep -q "^PASS"; then
|
|
70
71
|
gh pr edit "$PR_NUMBER" --add-label "qa:approved"
|
|
@@ -74,5 +75,5 @@ jobs:
|
|
|
74
75
|
gh pr comment "$PR_NUMBER" --body "🤖 **QA Agent:** AC coverage insufficient. ${EXPLANATION}"
|
|
75
76
|
else
|
|
76
77
|
gh pr edit "$PR_NUMBER" --add-label "infra:qa-broken"
|
|
77
|
-
gh pr comment "$PR_NUMBER" --body "🤖 **QA Agent:** Could not parse
|
|
78
|
+
gh pr comment "$PR_NUMBER" --body "🤖 **QA Agent:** Could not parse GitHub Models response. Manual review required."
|
|
78
79
|
fi
|
package/templates/docs/pdlc.md
CHANGED
|
@@ -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` |
|
|
@@ -80,6 +80,10 @@ REPO = {{REPO_OWNER}}/{{REPO_NAME}}
|
|
|
80
80
|
| `qa:approved` | PR | Green | QA Agent passed — AC coverage verified |
|
|
81
81
|
| `qa:needs-work` | PR | Red | QA Agent failed — PR needs changes |
|
|
82
82
|
| `infra:qa-broken` | PR | Orange | QA Agent error — manual review required |
|
|
83
|
+
| `type:us` | Issue | Blue | New feature or behavioral change — full flow |
|
|
84
|
+
| `type:task` | Issue | Yellow | Operational/non-functional change — skips brainstorming |
|
|
85
|
+
| `type:bug` | Issue | Red | Something broken — skips brainstorming |
|
|
86
|
+
| `type:spike` | Issue | Gray | Research/evaluation — never reaches Development |
|
|
83
87
|
|
|
84
88
|
## Approval Gates
|
|
85
89
|
|
|
@@ -93,10 +97,16 @@ This triggers the implementation agent via `agent-trigger.yml`.
|
|
|
93
97
|
|
|
94
98
|
## Shortcuts by Type
|
|
95
99
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
+
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.
|
|
101
|
+
|
|
102
|
+
| Label | Flow |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `type:us` | Full flow — exploration → brainstorming → Gate 1 → detailing → approval |
|
|
105
|
+
| `type:task` | Skips brainstorming — exploration → detailing → approval |
|
|
106
|
+
| `type:bug` | Skips brainstorming — exploration → detailing → approval |
|
|
107
|
+
| `type:spike` | Skips brainstorming — exploration → detailing → conclusion comment (never reaches Development) |
|
|
108
|
+
|
|
109
|
+
If no `type:*` label present and agent confidence < 85%, defaults to `type:us` (safe fallback — never skips gates by omission).
|
|
100
110
|
|
|
101
111
|
## Definition of Done
|
|
102
112
|
|